Three respawn-visual bugs the user saw, each grounded in the engine/decomp:
1. Warp = flat blue BLOB, not the demo's swirly blue/light-blue/white shimmer.
Ground truth (content/VIDEO/MAT/DAY/BTFX.VMF): tsphere_mtl = a SCROLLING bintA
texture (tsphere_scr_tex, SPECIAL SCROLL) + EMISSIVE {0.7,0.5,1} + RAMP "sky"
(dark-blue->white). The swirl IS the scrolling texture; the colour is the
emissive/ramp. Our draw did COLOROP=SELECTARG1/ARG1=TFACTOR, which REPLACES
every texel with one flat colour -> the blob. Fix: MODULATE the (bound,
scrolling) texture by a TFACTOR set to the authentic EMISSIVE hue (0xB380FF)
so the swirl survives and reads blue-white. DrawMesh's cached SetTexture
(L4D3D.cpp:1215) leaves our MODULATE ops standing since textured meshes drew
first. Additive glow, all state saved/restored.
2. First-person view BLACK on the dying/respawning mech until V. SetViewInside's
body-hide + '_cop' canopy suppression + viewSkeleton update were gated !wrecked,
and RebuildMechRenderables (respawn un-wreck) restored the full OUTSIDE torso
with no '_cop' rule -> the cockpit eyepoint ended up wrapped in opaque geometry.
Fix: factor the per-segment mesh selection into ApplyViewSkeleton(viewpoint,
inside) shared by SetViewInside AND RebuildMechRenderables, so respawn re-asserts
the inside skeleton + '_cop' hide; record viewSkeleton even while wrecked. Only
the mech the local camera views FROM gets the inside treatment (a replicant is
always outside).
3. OBSERVER never saw the peer respawn -- the peer's wreck sat forever. The wreck
appears incidentally via rising damage-zone replication -> MechDeathHandler::Tick
-> BTRemakeMechModel (one-way). The un-wreck+warp ran master-only in Mech::Reset.
Fix (reuses the existing damage channel, no stream-framing change): Mech::Reset
also ForceUpdate(DamageZoneUpdateModelFlag) so healed zones cross; Tick handles
the FALLING edge -- on a ReplicantInstance whose zone heals from destroyed, call
BTRebuildMechModel + BTStartWarpEffect once (wasWrecked latch). This is the port
analog of the binary's type-0 graphic-state -> ResetPose un-wreck hook
(Mech::ReadUpdateRecord case 0); the full Mech::WriteUpdateRecord death record
(type 6) is deferred (would touch the netcode framing).
Smoke-verified headless (2-node): victim respawns intact + warp; observer logs
"replicant un-wrecked + warp" at the peer's spawn point; no crash.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
824 lines
29 KiB
C++
824 lines
29 KiB
C++
//===========================================================================//
|
|
// File: btl4vid.hpp //
|
|
// Project: BattleTech Brick: Video Renderer Manager //
|
|
// Contents: BTL4VideoRenderer -- the BattleTech L4 out-the-window 3D WORLD //
|
|
// renderer manager. Builds the per-entity renderable tree for the //
|
|
// main view each time an interesting entity becomes visible: //
|
|
// the player mech + its weapons/effects/reticle, terrain, and //
|
|
// other movers, walking the jointed-mover segment table and the //
|
|
// subsystem roster and submitting renderables to the scene. //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// 02/13/95 CPB Initial coding. //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1994-1996, Virtual World Entertainment, Inc. //
|
|
// PROPRIETARY and CONFIDENTIAL //
|
|
//===========================================================================//
|
|
//
|
|
// RECONSTRUCTED from the shipped binary (BTL4OPT.EXE). NO header survived.
|
|
// Class/member/method names are taken from the embedded assert path
|
|
// "d:\tesla\bt\bt_l4\BTL4VID.CPP", the embedded class name string
|
|
// "BTL4VideoRenderer::Material name ..." (@0051d6f8), and the direct
|
|
// Red Planet analogue RP_L4/RPL4VID.cpp (RPL4VideoRenderer) plus the surviving
|
|
// MUNGA headers MUNGA_L4/L4VIDEO.HPP and MUNGA_L4/L4VIDRND.HPP. Each method
|
|
// cites its originating @ADDR.
|
|
//
|
|
// PORTING NOTES (reconstruction -> WinTesla engine)
|
|
// * BTL4VideoRenderer is the BattleTech analogue of RPL4VideoRenderer; both
|
|
// subclass the concrete L4 manager DPLRenderer (L4VIDEO.HPP). Deriving
|
|
// from DPLRenderer supplies SetFogStyle/AddDynamicRenderable, the fog
|
|
// colour/plane fields, dplMainView/dplMainZone/dplDeathZone and the
|
|
// vehicleReticle slot the recovered code touches.
|
|
// * The 1996 BattleTech build drove a pre-DPL renderable hierarchy whose
|
|
// joint renderables parent on a dpl_DCS* (rather than the WinTesla
|
|
// HierarchicalDrawComponent* parent). That hierarchy did NOT survive in
|
|
// source, so the BT-specific renderables it constructs are declared here
|
|
// (BTRootRenderable / BTHingeRenderable / ... ) with the signatures the
|
|
// recovered code actually calls. They are distinct from the modern
|
|
// L4VIDRND.HPP renderables of the same role (which is why the targeting
|
|
// reticle is BTReticleRenderable, NOT the engine's ReticleRenderable --
|
|
// the BT object is 0x358 bytes and carries up to 10 AddWeapon pip markers).
|
|
//
|
|
|
|
#if !defined(BTL4VID_HPP)
|
|
# define BTL4VID_HPP
|
|
|
|
//
|
|
// Forward declaration so MUNGA_L4/l4vidrnd.h (which references DPLRenderer in
|
|
// HierarchicalDrawComponent before l4video.h defines it) parses cleanly
|
|
// regardless of include order.
|
|
//
|
|
class DPLRenderer;
|
|
|
|
#if !defined(L4VIDEO_HPP)
|
|
# include <l4video.hpp> // DPLRenderer / VideoRenderer / INTERSECT_ALL
|
|
#endif
|
|
#if !defined(L4VIDRND_HPP)
|
|
# include <l4vidrnd.hpp> // VideoComponent / the *Renderable hierarchy
|
|
#endif
|
|
#if !defined(RETICLE_HPP)
|
|
# include <reticle.hpp>
|
|
#endif
|
|
#if !defined(STATE_HPP)
|
|
# include <state.hpp> // StateIndicator
|
|
#endif
|
|
#if !defined(JMOVER_HPP)
|
|
# include <jmover.hpp> // JointedMover
|
|
#endif
|
|
#if !defined(JOINT_HPP)
|
|
# include <joint.hpp> // Joint / JointSubsystem / Hinge
|
|
#endif
|
|
#if !defined(SEGMENT_HPP)
|
|
# include <segment.hpp> // EntitySegment
|
|
#endif
|
|
#if !defined(SUBSYSTM_HPP)
|
|
# include <subsystm.hpp> // Subsystem
|
|
#endif
|
|
#if !defined(NAMELIST_HPP)
|
|
# include <namelist.hpp> // NameList / NameList::Entry
|
|
#endif
|
|
#include <map> // per-mech render-tree bookkeeping (RemakeEntity)
|
|
|
|
class Entity;
|
|
class Mission;
|
|
class ResourceDescription;
|
|
class NotationFile;
|
|
|
|
//
|
|
// BT "scene" handle. In the 1996 build the world-view renderables were
|
|
// built against an opaque scene root (the analogue of RP's dpl_ZONE pair);
|
|
// it is used pointer-only in this module, so a forward declaration suffices.
|
|
//
|
|
class Scene;
|
|
|
|
//
|
|
//===========================================================================//
|
|
// Legacy 2D / material-callback DPL entry points
|
|
//===========================================================================//
|
|
//
|
|
// The dpl2d_ display-list layer and the material-name-substitution callback
|
|
// hook were part of libDPL (the IG-board driver) and were NOT carried into the
|
|
// WinTesla engine. The recovered reticle builder and material setup call them
|
|
// directly; declare them here so the translation unit compiles (resolved at
|
|
// link time against the libDPL shim).
|
|
//
|
|
dpl2d_DISPLAY* dpl2d_NewDisplayList();
|
|
void dpl2d_Begin(dpl2d_DISPLAY *list, int mode);
|
|
void dpl2d_SetColor(dpl2d_DISPLAY *list, Scalar red, Scalar green, Scalar blue);
|
|
void dpl2d_Circle(dpl2d_DISPLAY *list, Scalar x, Scalar y, Scalar radius, int fill);
|
|
void dpl2d_PushMatrix(dpl2d_DISPLAY *list);
|
|
void dpl2d_PopMatrix(dpl2d_DISPLAY *list);
|
|
void dpl2d_MoveTo(dpl2d_DISPLAY *list, Scalar x, Scalar y);
|
|
void dpl2d_End(dpl2d_DISPLAY *list);
|
|
void dpl2d_Compile(dpl2d_DISPLAY *list);
|
|
//
|
|
// The FULL recovered API (phase-02: every recorder named by its debug string;
|
|
// opcode map in phases/phase-02-dpl2d-reticle.md). The Push/Move/Pop trio
|
|
// above is a legacy alias of OpenPolypoint/AddPoint/ClosePolypoint.
|
|
//
|
|
void dpl2d_OpenPolypoint(dpl2d_DISPLAY *list); // opcode 2
|
|
void dpl2d_ClosePolypoint(dpl2d_DISPLAY *list); // opcode 3
|
|
void dpl2d_OpenPolyline(dpl2d_DISPLAY *list); // opcode 4 (closed loop)
|
|
void dpl2d_ClosePolyline(dpl2d_DISPLAY *list); // opcode 5
|
|
void dpl2d_OpenLines(dpl2d_DISPLAY *list); // opcode 6 (open strip)
|
|
void dpl2d_CloseLines(dpl2d_DISPLAY *list); // opcode 7
|
|
void dpl2d_AddPoint(dpl2d_DISPLAY *list, Scalar x, Scalar y); // opcode 8
|
|
void dpl2d_SetLineWidth(dpl2d_DISPLAY *list, Scalar width); // opcode 0x15
|
|
void dpl2d_PushState(dpl2d_DISPLAY *list); // opcode 0x12
|
|
void dpl2d_PopState(dpl2d_DISPLAY *list); // opcode 0x13
|
|
void dpl2d_SetMatrix(dpl2d_DISPLAY *list, const Scalar *six); // opcode 0x10 (2x3)
|
|
void dpl2d_ConcatMatrix(dpl2d_DISPLAY *list, const Scalar *six); // opcode 0x11
|
|
void dpl2d_CallList(dpl2d_DISPLAY *list, dpl2d_DISPLAY *callee); // nested glyphs
|
|
void dpl2d_FullScreenClip(dpl2d_DISPLAY *list);
|
|
|
|
void dpl_SetMaterialNameCallback(char *(*callback)(char *source));
|
|
|
|
//
|
|
//===========================================================================//
|
|
// BattleTech renderable hierarchy (pre-DPL)
|
|
//===========================================================================//
|
|
//
|
|
// Common base: every BT world renderable carries the dpl_DCS / dpl_INSTANCE it
|
|
// creates and offers the graphical (Add) / control (Connect) hook-up the world
|
|
// container uses. Modelled on VideoComponent so a BTRootRenderable can be
|
|
// returned as the VideoComponent* mech root.
|
|
//
|
|
class BTRenderableBase:
|
|
public VideoComponent
|
|
{
|
|
public:
|
|
BTRenderableBase(
|
|
Entity *entity,
|
|
VideoExecutionType execution_type = DynamicVideoExecutionType)
|
|
: VideoComponent(entity, execution_type), myDCS(0), myInstance(0) {}
|
|
|
|
dpl_DCS* GetDCS() { return myDCS; }
|
|
dpl_INSTANCE* GetInstance() { return myInstance; }
|
|
|
|
//
|
|
// Graphical / control hook-up. These hide VideoComponent::Add/Connect
|
|
// (which take VideoComponent*) so the world container can accept any
|
|
// renderable in the BT hierarchy by its common HierarchicalDrawComponent
|
|
// base.
|
|
//
|
|
void Add(HierarchicalDrawComponent *child) { if (child) addChild(child); }
|
|
void Connect(HierarchicalDrawComponent *child) { if (child) addChild(child); }
|
|
|
|
protected:
|
|
dpl_DCS *myDCS;
|
|
dpl_INSTANCE *myInstance;
|
|
};
|
|
|
|
//
|
|
// World container renderable (FUN_00455de4, alloc 0x60). Holds the death /
|
|
// world DCS roots for one entity's tree.
|
|
//
|
|
class BTWorldContainerRenderable:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTWorldContainerRenderable(
|
|
Entity *entity,
|
|
int execution_type,
|
|
int in_death_zone,
|
|
Scene *scene);
|
|
};
|
|
|
|
//
|
|
// Root DCS object renderable (FUN_00453578, alloc 0x64).
|
|
//
|
|
class BTRootRenderable:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTRootRenderable(
|
|
Entity *entity,
|
|
int execution_type,
|
|
d3d_OBJECT *graphical_object,
|
|
Scene *scene,
|
|
int intersect_mode,
|
|
uint32 intersect_mask);
|
|
};
|
|
|
|
//
|
|
// Static / jointed child DCS renderables.
|
|
//
|
|
class BTDCSObjectRenderable:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTDCSObjectRenderable( // FUN_0045848c, alloc 0x50
|
|
Entity *entity,
|
|
Scene *scene,
|
|
d3d_OBJECT *graphical_object,
|
|
int execution_type,
|
|
uint32 intersect_mask,
|
|
LinearMatrix *offset_matrix,
|
|
dpl_DCS *parent_DCS);
|
|
};
|
|
|
|
class BTHingeRenderable:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTHingeRenderable( // FUN_004537e8, alloc 0x78
|
|
Entity *entity,
|
|
int execution_type,
|
|
d3d_OBJECT *graphical_object,
|
|
Scene *scene,
|
|
int exec2,
|
|
uint32 intersect_mask,
|
|
dpl_DCS *parent_DCS,
|
|
LinearMatrix *offset_matrix,
|
|
const Hinge *my_hinge);
|
|
};
|
|
|
|
class BTBallJointRenderable:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTBallJointRenderable( // FUN_004539b4, alloc 0x7c
|
|
Entity *entity,
|
|
int execution_type,
|
|
d3d_OBJECT *graphical_object,
|
|
Scene *scene,
|
|
int exec2,
|
|
uint32 intersect_mask,
|
|
dpl_DCS *parent_DCS,
|
|
LinearMatrix *offset_matrix,
|
|
const EulerAngles *my_euler);
|
|
};
|
|
|
|
class BTBallTranslateJointRenderable:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTBallTranslateJointRenderable( // FUN_00453ac4, alloc 0x8c
|
|
Entity *entity,
|
|
int execution_type,
|
|
d3d_OBJECT *graphical_object,
|
|
Scene *scene,
|
|
int exec2,
|
|
uint32 intersect_mask,
|
|
dpl_DCS *parent_DCS,
|
|
LinearMatrix *offset_matrix,
|
|
const EulerAngles *my_euler,
|
|
const Point3D *my_translation);
|
|
};
|
|
|
|
//
|
|
// Inside-view eyepoint (POV/eye) renderable (FUN_004579a8, alloc 0x5c).
|
|
//
|
|
class BTEyeRenderable:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTEyeRenderable(
|
|
Entity *entity,
|
|
Scene *scene,
|
|
LinearMatrix *offset_matrix,
|
|
dpl_DCS *parent_DCS,
|
|
dpl_VIEW *this_view,
|
|
EulerAngles *eyepoint_rotation);
|
|
};
|
|
|
|
//
|
|
// Death / explosion effect renderable (FUN_00453f18, alloc 0x48).
|
|
//
|
|
class BTDeathEffectRenderable:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTDeathEffectRenderable(
|
|
Entity *entity,
|
|
int execution_type,
|
|
dpl_VIEW *this_view,
|
|
Scene *scene,
|
|
dpl_DCS *parent_DCS,
|
|
StateIndicator *death_state,
|
|
int death_control_state);
|
|
};
|
|
|
|
//
|
|
// Marker (timestamp/beacon) watcher renderable (FUN_00458c58, alloc 0x120).
|
|
//
|
|
class BTMarkerWatcherRenderable:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTMarkerWatcherRenderable(
|
|
Entity *entity,
|
|
int execution_type,
|
|
dpl_VIEW *this_view,
|
|
dpl_DCS *parent_DCS);
|
|
};
|
|
|
|
//
|
|
// Drop-zone translocation effect renderable (FUN_00458d2c, alloc 0x40).
|
|
//
|
|
// THE "BLUE WARP" (task #52): the engine analog is POVTranslocateRenderable
|
|
// (L4VIDRND.cpp:1749), which loads tsphere.bgf and runs a collapse-on-death /
|
|
// expand-on-respawn sphere keyed on the entity's SimulationState dial vs a
|
|
// control state. This was a no-op stub; now reconstructed. The sphere
|
|
// COLLAPSES (scale 100->1 over 1.3s) when the watched player enters the
|
|
// control state (DropZoneAcquired), holds, then EXPANDS (1->150 over 1.0s)
|
|
// when it leaves (VehicleTranslocated) -- revealing the reborn mech. Rotates
|
|
// throughout. Drawn (direct-draw, like the weapon beams) by
|
|
// BTDrawTranslocationSpheres from the render loop.
|
|
//
|
|
class BTTranslocationRenderable:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTTranslocationRenderable(
|
|
Entity *entity,
|
|
int execution_type,
|
|
dpl_VIEW *this_view,
|
|
StateIndicator *effect_trigger,
|
|
Point3D *drop_zone,
|
|
int effect_control_state);
|
|
~BTTranslocationRenderable();
|
|
|
|
// Sphere state machine (public so the file-static render walk in
|
|
// btl4vid.cpp can step + draw each active effect). Mirrors
|
|
// POVTranslocateRenderable's IdleState/InitialCollapse/WaitForReincarnate/
|
|
// ExpandReveal.
|
|
enum { TLoc_Idle = 0, TLoc_Collapse, TLoc_Wait, TLoc_Expand };
|
|
Entity *myWatchedEntity; // the player this sphere belongs to (dedupe key)
|
|
StateIndicator *myTrigger; // the SimulationState dial we watch
|
|
Point3D *myDropZone; // world position to render the sphere at
|
|
unsigned myControlState; // state value that starts the collapse
|
|
int mySphereState; // TLoc_*
|
|
float myTimer; // seconds elapsed in the current timed phase
|
|
float myRotateY; // accumulated spin
|
|
bool mySphereVisible;
|
|
};
|
|
|
|
//
|
|
// Player POV mission start/end fade renderable (FUN_00454394, alloc 0x50).
|
|
// (BT-local twin of the engine's POVStartEndRenderable; the BT ctor takes the
|
|
// main-view handle plus the world/death zones explicitly.)
|
|
//
|
|
class BTPOVStartEndRenderable:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTPOVStartEndRenderable(
|
|
Entity *entity,
|
|
int execution_type,
|
|
dpl_VIEW *this_view,
|
|
dpl_ZONE *main_zone,
|
|
dpl_ZONE *death_zone,
|
|
StateIndicator *effect_trigger,
|
|
float red_fog,
|
|
float green_fog,
|
|
float blue_fog,
|
|
float near_fog,
|
|
float far_fog,
|
|
int start_mission_state,
|
|
int end_mission_state);
|
|
};
|
|
|
|
//
|
|
// Coolant / reservoir / tracer reservoir effect renderable (FUN_00456a68,
|
|
// alloc 0x138).
|
|
//
|
|
class BTTracerEffectRenderable:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTTracerEffectRenderable(
|
|
Entity *entity,
|
|
int execution_type,
|
|
void *state_attr,
|
|
int mode,
|
|
dpl_ZONE *zone,
|
|
dpl_DCS *parent_DCS,
|
|
LinearMatrix *offset_matrix);
|
|
};
|
|
|
|
//
|
|
// Emitter beam renderable (FUN_004593c0, alloc 0x80).
|
|
//
|
|
class BTEmitterBeamRenderable:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTEmitterBeamRenderable(
|
|
Entity *entity,
|
|
int execution_type,
|
|
d3d_OBJECT *graphical_object,
|
|
Scene *scene,
|
|
int exec2,
|
|
uint32 intersect_mask,
|
|
dpl_DCS *parent_DCS,
|
|
LinearMatrix *offset_matrix,
|
|
void *sim_state,
|
|
int flag,
|
|
void *beam_scale,
|
|
void *beam_orient);
|
|
};
|
|
|
|
//
|
|
// PPC beam renderable (FUN_004590d8, alloc 0x88).
|
|
//
|
|
class BTPPCBeamRenderable:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTPPCBeamRenderable(
|
|
Entity *entity,
|
|
int execution_type,
|
|
d3d_OBJECT *graphical_object,
|
|
Scene *scene,
|
|
int exec2,
|
|
uint32 intersect_mask,
|
|
dpl_DCS *parent_DCS,
|
|
LinearMatrix *offset_matrix,
|
|
void *sim_state,
|
|
int flag,
|
|
void *beam_scale,
|
|
void *beam_orient,
|
|
float beam_width);
|
|
};
|
|
|
|
//
|
|
// Searchlight LightOn -> spotlight connector (FUN_0045612c, alloc 0x2c).
|
|
//
|
|
class BTLightConnection:
|
|
public BTRenderableBase
|
|
{
|
|
public:
|
|
BTLightConnection(
|
|
Entity *entity,
|
|
int execution_type,
|
|
dpl_INSTANCE *spot_instance,
|
|
int flag,
|
|
int *light_on);
|
|
};
|
|
|
|
//
|
|
//#############################################################################
|
|
// BTReticleRenderable (BattleTech targeting reticle)
|
|
//#############################################################################
|
|
//
|
|
// File-private to btl4vid.cpp. Parallels the engine ReticleRenderable
|
|
// (L4VIDRND.HPP) but the BT object is 0x358 bytes and carries up to 10 weapon
|
|
// pip / range markers built by AddWeapon. Constructed @004cc40c for the inside
|
|
// view; weapon markers are appended by MakeMechRenderables via AddWeapon.
|
|
//
|
|
// header layout actually touched (this+...):
|
|
// +0x38 weaponCount (cap 10 -- "Tried to display too many weapons")
|
|
// ... parallel per-weapon arrays (see members below)
|
|
// reticle geometry: minRange@0x230, maxRange@0x22c, rangeScale@0x234,
|
|
// originX@0x1fc, originY@0x200, scaleY@0x204, biasX@0x208
|
|
//
|
|
class BTReticleRenderable:
|
|
public VideoRenderable
|
|
{
|
|
public:
|
|
//
|
|
// PORT ctor. The binary ctor (@004cc40c, 15 args) received the mech's
|
|
// TargetReticle attribute + 8 HUD-subsystem attributes; its CALIBRATION
|
|
// however is its own hardcoded constants (0.35/0.25/0.5/0.016/0.008 --
|
|
// the pip-row origin/scale and the two tick-ladder frames), which this
|
|
// port ctor reproduces directly. The one live input the glyphs consume
|
|
// is the target RANGE (drives the range-scale caret); it binds to a
|
|
// Scalar the targeting step updates. The 3D marker chain + the
|
|
// PNAME1-8.bgf pip meshes (the floating 3D target designator) are a
|
|
// separate deferred piece -- see phases/phase-02-dpl2d-reticle.md.
|
|
//
|
|
BTReticleRenderable( // @004cc40c (port signature)
|
|
Entity *entity,
|
|
Scalar *range_attr);
|
|
~BTReticleRenderable();
|
|
|
|
//
|
|
// Per-frame draw (the binary Execute @004cdcf0 is in an UN-EXPORTED
|
|
// gap; the dynamics here are [T3]: draw the master reticle, then each
|
|
// weapon's lit pip when its within-range flag is up, else the dark
|
|
// ring). Called from the render loop, cockpit view only.
|
|
//
|
|
void
|
|
Draw(struct IDirect3DDevice9 *device);
|
|
|
|
int WeaponCount() const { return weaponCount; }
|
|
|
|
//
|
|
// Append a weapon range/pip marker to the reticle. @004cdac0
|
|
// Parameter order/count is the binary's. The RECOVERED Execute
|
|
// (@004cdcf0, task #37) fixed the two state attrs' semantics:
|
|
// param_9 = weapon attr 0x1c, the FIRE-CYCLE state: == const2 (2)
|
|
// -> LOADED (lit pip A); == const3 (3) -> charging (dark
|
|
// ring B). Port source: rechargeLevel (>= 1.0 == loaded).
|
|
// param_12 = weapon attr 1, the DAMAGE state: == const1 (1)
|
|
// -> Destroyed (the pip is HIDDEN entirely).
|
|
// weapon_mode is a GROUP BIT (Front=1/Rear=2/Left=4/Right=8) tested
|
|
// against the Reticle element mask's low nibble.
|
|
//
|
|
void
|
|
AddWeapon(
|
|
Scalar weapon_range, // param_2
|
|
int pip_position, // param_3
|
|
int *within_range_value, // param_4 TargetWithinRange
|
|
int extended_range, // param_5 PipExtendedRange
|
|
Scalar pip_red, // param_6..8 PipColor
|
|
Scalar pip_green,
|
|
Scalar pip_blue,
|
|
Scalar *cycle_ready, // param_9 (port: rechargeLevel)
|
|
int const2, // param_10 (2 = loaded)
|
|
int const3, // param_11 (3 = charging)
|
|
int *sim_state_value, // param_12 weapon attr 1
|
|
int const1, // param_13 (1 = destroyed)
|
|
int weapon_mode); // param_14 (group bit)
|
|
|
|
protected:
|
|
//
|
|
// Per-weapon parallel arrays (cap 10). Slot mapping is the verified
|
|
// store order of AddWeapon @004cdac0 (part_014.c:4827-4837) [T1];
|
|
// semantics RECOVERED from the disassembled Execute @004cdcf0 (task
|
|
// #37 [T1]): the caches are the change-detect state driving the pip
|
|
// list rebuild; the constants are the fire-cycle/damage enum values.
|
|
//
|
|
int weaponCount; // +0x38
|
|
int weaponMode[10]; // +0x3c group bit (F/R/L/R)
|
|
int stateConst3[10]; // +0x64 3 = charging (dark)
|
|
int stateConst2[10]; // +0x8c 2 = loaded (lit)
|
|
int stateConst1[10]; // +0xb4 1 = destroyed (hidden)
|
|
int alarmCache[10]; // +0xdc port: LOADED flag cache
|
|
int simStateCache[10]; // +0x104 port: DESTROYED flag cache
|
|
Scalar *cycleReady[10]; // +0x130 port: rechargeLevel source
|
|
int *simStateAttr[10]; // +0x158 damage state (attr 1)
|
|
int *withinRangePtr[10]; // +0x18c param_4 (stored; Execute
|
|
int withinRangeCache[10]; // +0x1b4 never reads it -- T1)
|
|
dpl2d_DISPLAY *pipDisplayListB[10]; // +0x288
|
|
dpl2d_DISPLAY *pipDisplayListA[10]; // +0x2b0
|
|
|
|
//
|
|
// Calibrated reticle geometry (the ctor's authentic constants).
|
|
//
|
|
Scalar originX; // +0x1fc [0x7f] = 0.35
|
|
Scalar originY; // +0x200 [0x80] = 0.25
|
|
Scalar scaleY; // +0x204 [0x81] = 0.5
|
|
Scalar biasX; // +0x208 [0x82] = 0.016
|
|
Scalar maxRange; // +0x22c [0x8b] = 1200
|
|
Scalar minRange; // +0x230 [0x8c] = 0
|
|
Scalar rangeScale; // +0x234 [0x8f]
|
|
|
|
//
|
|
// The reticle's own display lists (the ctor's [0x97..0xa1, 0xb6..0xba]
|
|
// set, as transcribed) + the live range binding.
|
|
//
|
|
Scalar *rangeAttr2; // [0x89] the target-range source
|
|
dpl2d_DISPLAY *masterList; // [0x98] the composed reticle
|
|
dpl2d_DISPLAY *simpleXList; // [0x99] the minimal cross (PrimaryHudOn off)
|
|
dpl2d_DISPLAY *aimDotList; // [0x9a] centre aim dot
|
|
dpl2d_DISPLAY *rangeCaretR; // [0x9b] right-ladder caret
|
|
dpl2d_DISPLAY *rangeCaretB; // [0x9c] bottom-ladder carets
|
|
dpl2d_DISPLAY *headingList; // [0x9d] heading sub-list
|
|
dpl2d_DISPLAY *bottomAnchor; // [0x9e] bottom-ladder anchor
|
|
dpl2d_DISPLAY *leftArrow; // [0x9f] left range arrow
|
|
dpl2d_DISPLAY *rightArrow; // [0xa0] right range arrow
|
|
dpl2d_DISPLAY *crossList; // [0xa1] the dotted cross
|
|
dpl2d_DISPLAY *subB6; // [0xb6] the composed weapon pips
|
|
dpl2d_DISPLAY *subB7; // [0xb7] the lock-ring slot
|
|
dpl2d_DISPLAY *subB8; // [0xb8] plain ring (simple-X mode)
|
|
dpl2d_DISPLAY *subB9; // [0xb9] ring + cross (the LOCK ring)
|
|
dpl2d_DISPLAY *subBA; // [0xba] the threat-direction trail
|
|
|
|
// Recovered-Execute dynamic state (task #37)
|
|
int pipsBuilt; // subB6 composed at least once
|
|
int lockShown; // subB7 holds the ring (this[0x1dc])
|
|
float lockSpinDeg; // the ring spin angle (this[0x240])
|
|
};
|
|
|
|
//
|
|
// The live reticle draw hook (called from the engine render loop after the
|
|
// 3D scene; draws only in the cockpit view).
|
|
//
|
|
extern void BTDrawReticle(struct IDirect3DDevice9 *device);
|
|
|
|
//
|
|
//#############################################################################
|
|
// BTL4VideoRenderer
|
|
//#############################################################################
|
|
//
|
|
// @ vtable: BattleTech L4 video renderer manager subclass. Base is the MUNGA
|
|
// L4 renderer manager (DPLRenderer, l4video.hpp). Parallels RPL4VideoRenderer.
|
|
//
|
|
class BTL4VideoRenderer:
|
|
public DPLRenderer
|
|
{
|
|
public:
|
|
BTL4VideoRenderer(
|
|
RendererRate calibration_rate,
|
|
RendererComplexity calibration_complexity,
|
|
RendererPriority calibration_priority,
|
|
InterestType interest_type,
|
|
InterestDepth depth_calibration
|
|
);
|
|
~BTL4VideoRenderer();
|
|
|
|
Logical
|
|
TestInstance() const;
|
|
|
|
//
|
|
// Material substitution (mirrors RPL4VideoRenderer).
|
|
//
|
|
void
|
|
SetupMaterialSubstitutionList(Entity *entity); // @004d0cc0
|
|
void
|
|
TearDownMaterialSubstitutionList(); // @004d11e8
|
|
|
|
//
|
|
// RemakeEntity (damage-model swap). The 1996 binary drives a per-entity
|
|
// render state machine (Make / RemakeEntity / DestroyEntity -- the state
|
|
// name strings survive at .rdata 0x4e3f20). Only "Make" was ported (the
|
|
// tree is built ONCE at entity creation, with fixed d3d_OBJECTs).
|
|
// RemakeEntity is the model REFRESH: when a damage zone's graphic state
|
|
// changes (a segment becomes Destroyed/Gone), re-pick each segment's video-
|
|
// object variant by its zone graphic state (EntitySegment::GetVideoObjectName
|
|
// is keyed by {skeleton, graphic_state}) and swap it onto the already-built
|
|
// joint renderable IN PLACE (HierarchicalDrawComponent::Execute re-reads
|
|
// graphicalObject each frame). No teardown: the component dtor does not
|
|
// cascade to children (L4VIDRND.cpp:104) so a rebuild would leak -- the
|
|
// authentic behaviour is an in-place mesh swap.
|
|
//
|
|
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);
|
|
|
|
// Select/load each body segment's displayed mesh for the given view
|
|
// (inside=SkeletonType_A + '_cop' suppression / outside=full skeleton),
|
|
// honoring live damage graphic state. Shared by SetViewInside (V-toggle)
|
|
// and RebuildMechRenderables (respawn) so the cockpit view stays consistent
|
|
// across death/respawn. Returns the count of shown meshes.
|
|
int
|
|
ApplyViewSkeleton(Entity *viewpoint, int inside);
|
|
|
|
protected:
|
|
//
|
|
// Per-mech render-tree bookkeeping so RemakeEntityRenderables can find each
|
|
// segment's joint renderable (the dcs_array in MakeMechRenderables is local
|
|
// and freed). Keyed by Entity*; one entry per built mech. segRenderable
|
|
// maps a segment SLOT (EntitySegment::GetIndex) to its draw component;
|
|
// segGState is the graphic state last applied to that slot so a swap only
|
|
// reloads geometry on an actual state change.
|
|
//
|
|
struct MechRenderTree
|
|
{
|
|
int skeletonType; // EntitySegment::SkeletonType used at build
|
|
int viewSkeleton; // the skeleton currently DISPLAYED (view toggle)
|
|
HierarchicalDrawComponent *rootRenderable;// the tree root (wreck hulk parent)
|
|
int wrecked; // 1 = swapped to the <mech>dbr hulk
|
|
DPLStaticChildRenderable *wreckHulk; // the <mech>dbr piece (sinks)
|
|
DPLStaticChildRenderable *wreckDebris; // the ldbr scatter (sinks)
|
|
float wreckAge; // seconds since the swap
|
|
std::map<int, HierarchicalDrawComponent*> segRenderable; // slot -> joint renderable
|
|
std::map<int, int> segGState; // slot -> last applied graphic state
|
|
};
|
|
std::map<Entity*, MechRenderTree> mMechRenderTrees;
|
|
|
|
public:
|
|
//
|
|
// The wreck's quadratic SINK (the 1996 script's burial: FUN_00456410
|
|
// computes offsetY = rate * t^2; the hulk's authored rate is -0.025 ->
|
|
// the ~7-unit hulk is fully buried ~17s after the kill). Ticked per
|
|
// frame from the dead mech's UpdateDeathState. Returns 0 once the
|
|
// wreck is fully buried (the caller stops the wreck-smoke re-arm),
|
|
// 1 while anything is still visible.
|
|
//
|
|
int
|
|
TickWreck(Entity *victim, float dt);
|
|
|
|
//
|
|
// VIEW TOGGLE: the player's mech builds BOTH cameras -- the authentic
|
|
// cockpit eyepoint (DPLEyeRenderable at 'siteeyepoint', the pod's only
|
|
// view) and the port's external chase camera (a usability addition for
|
|
// the windowed build). SetViewInside switches the renderer's live
|
|
// camera between them (V key via the BTSetViewInside bridge).
|
|
//
|
|
void
|
|
SetViewInside(int inside);
|
|
|
|
protected:
|
|
DPLEyeRenderable *mEyeCockpit; // the authentic cockpit eyepoint
|
|
DPLEyeRenderable *mEyeChase; // the external chase camera
|
|
int mViewInside; // the pilot's chosen view (V/state);
|
|
// survives renderable REBUILDS (the
|
|
// chase-eye build used to stomp
|
|
// mCamera back to chase every remake)
|
|
|
|
public:
|
|
//
|
|
// The death-wreck swap (ExplosionScripts effect 104, reconstructed): the
|
|
// victim's whole visual becomes its burning hulk -- hide every segment
|
|
// mesh and hang "<prefix>dbr.bgf" (gendbr.bgf fallback) on the tree root.
|
|
// The 1996 script hardcoded thrdbr.bgf (a dev shortcut; every mech ships
|
|
// its own *DBR hulk). If the victim's tree isn't built yet, the swap is
|
|
// remembered and applied when MakeMechRenderables builds it.
|
|
//
|
|
void
|
|
SwapToWreck(Entity *victim);
|
|
|
|
protected:
|
|
//
|
|
// Renderer-manager overrides
|
|
//
|
|
void
|
|
LoadMissionImplementation(Mission *mission);
|
|
|
|
void
|
|
MakeEntityRenderables( // @004d0774
|
|
Entity *this_entity,
|
|
ResourceDescription *model_resource,
|
|
ViewFrom type);
|
|
|
|
HierarchicalDrawComponent*
|
|
MakeMechRenderables( // @004cef28
|
|
Entity *entity,
|
|
ResourceDescription *model_resource,
|
|
ViewFrom type);
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// BT renderer helpers (recovered).
|
|
//--------------------------------------------------------------------
|
|
//
|
|
Scene*
|
|
GetScene(); // FUN_0045a724
|
|
|
|
dpl_VIEW*
|
|
GetMainView() { return dplMainView; } // this[0x124]
|
|
dpl_ZONE*
|
|
GetMainZone() { return dplMainZone; } // this[300]
|
|
|
|
d3d_OBJECT*
|
|
LoadObject(const char *object_name); // FUN_00498448
|
|
|
|
void
|
|
AttachToEyeDCS(dpl_DCS *root_dcs, LinearMatrix &local_to_world); // FUN_00489cec
|
|
|
|
void
|
|
AddDynamicRenderable(VideoComponent *renderable, Entity *entity); // FUN_0045a994
|
|
|
|
protected:
|
|
//
|
|
// vtvCount / vtvsExpected analogues (RPL4VideoRenderer).
|
|
//
|
|
int
|
|
mechsExpected,
|
|
mechCount;
|
|
|
|
//
|
|
// True once the linked (player) entity exists, so its root DCS is
|
|
// hooked onto the eye. ([0xb0] in the recovered layout.)
|
|
//
|
|
Logical
|
|
linkedEntityPresent;
|
|
|
|
//
|
|
// Effect zones touched by the recovered code.
|
|
//
|
|
dpl_ZONE
|
|
*scene_zone, // [0x318] coolant / reservoir effects
|
|
*tracer_zone; // [0x2e4] projectile tracers
|
|
};
|
|
|
|
//
|
|
// Sim-side bridge to RemakeEntityRenderables. MechDeathHandler (sim TU) calls
|
|
// this when a mech's damage graphic state changes; defined in btl4vid.cpp, it
|
|
// reaches the live renderer via l4_application->GetVideoRenderer(). A free
|
|
// function so the sim TU needs no renderer header (just this extern).
|
|
//
|
|
extern void BTRemakeMechModel(Entity *entity);
|
|
|
|
//
|
|
// Render bridge for the death-wreck swap (effect 104): called by the engine's
|
|
// ExplosionClassID dispatch with the explosion's entityHit (the dead mech).
|
|
//
|
|
extern void BTSwapMechToWreck(Entity *victim);
|
|
|
|
//
|
|
// Per-frame wreck sink tick (sim -> render bridge; called by the dead mech's
|
|
// UpdateDeathState with the frame dt). Returns 0 once the wreck is fully
|
|
// buried -- the caller stops re-arming the wreck smoke.
|
|
//
|
|
extern int BTWreckSinkTick(Entity *victim, float dt);
|
|
|
|
//
|
|
// View-mode bridge (the V key): 1 = the authentic cockpit eyepoint,
|
|
// 0 = the external chase camera.
|
|
//
|
|
extern void BTSetViewInside(int inside);
|
|
|
|
#endif // BTL4VID_HPP
|
|
|
|
//===========================================================================//
|