diff --git a/game/reconstructed/btl4grnd.cpp b/game/reconstructed/btl4grnd.cpp index 6acc6f3..bef6d7e 100644 --- a/game/reconstructed/btl4grnd.cpp +++ b/game/reconstructed/btl4grnd.cpp @@ -66,6 +66,7 @@ #endif #if !defined(BTL4GAUG_HPP) # include // the BT gauge classes (Compass, etc.) +# include // MapDisplay (the "map" radar gauge) #endif #if !defined(L4LAMP_HPP) # include @@ -137,6 +138,7 @@ MethodDescription &VertTwoPartBar::methodDescription, // "vertBar" -- vertical fill bar (coolant/heat) &SegmentArcRatio::methodDescription, // "segmentArcRatio" -- segmented arc dial (speed) &OneOfSeveralPixInt::methodDescription, // "oneOfSeveralPixInt" -- button-state lamp (duck/light/mode) + &MapDisplay::methodDescription, // "map" -- the radar / tactical display &BTL4ChainToPrevious }; diff --git a/game/reconstructed/btl4rdr.cpp b/game/reconstructed/btl4rdr.cpp index ebe660c..75dc946 100644 --- a/game/reconstructed/btl4rdr.cpp +++ b/game/reconstructed/btl4rdr.cpp @@ -212,9 +212,11 @@ void ) { // - // The operator's own mech (exclude_entity) draws no name. + // The operator's own mech (exclude_entity) draws no name. A NULL entity + // (the binary's Verify(False) count/iterator-mismatch path, which compiles + // out at DEBUG_LEVEL 0) would crash EntityPosition(NULL) below -- guard it. // - if (entity == exclude_entity) + if (entity == NULL || entity == exclude_entity) { nameBitmap = NULL; // param_1[6] = 0 return; @@ -290,6 +292,77 @@ void //########################################################################### //########################################################################### +//############################################################################# +// Method Description + Make -- the config "map" primitive registration glue. +// +// NOT in the assert-anchored decomp (the ctor @004c1c24 has no exported caller +// and the "map" string was not captured), so this is reconstructed from the +// config invocation (L4GAUGE.CFG:4923) + the ctor signature. The offset_position +// keyword "center"/"bottom" is typed as a STRING and converted in Make, so it +// needs no ModeManager named-constant registration. +// +MethodDescription + MapDisplay::methodDescription = + { + "map", + MapDisplay::Make, + { + // map( rate, mode, (w,h), offsetPos, viewDeg, bg,static,hotbox, + // maxRange, scaleAttr, positionAttr, angleAttr, capabilitiesAttr ) + { ParameterDescription::typeRate, NULL }, // rate ID + { ParameterDescription::typeModeMask, NULL }, // mode mask + { ParameterDescription::typeVector, NULL }, // (width,height) + { ParameterDescription::typeString, NULL }, // offset position ("center"/"bottom") + { ParameterDescription::typeInteger, NULL }, // view width (degrees) + { ParameterDescription::typeColor, NULL }, // background colour + { ParameterDescription::typeColor, NULL }, // static/default colour + { ParameterDescription::typeColor, NULL }, // hotbox colour + { ParameterDescription::typeScalar, NULL }, // maximum range (metres) + { ParameterDescription::typeAttribute, NULL }, // scale (RadarRange) + { ParameterDescription::typeAttribute, NULL }, // position (RadarLinearPosition, Point3D*) + { ParameterDescription::typeAttribute, NULL }, // angle (RadarAngularPosition, Quaternion*) + { ParameterDescription::typeAttribute, NULL }, // capabilities (Avionics/RadarPercent) + PARAMETER_DESCRIPTION_END + } + }; + +Logical + MapDisplay::Make( + int display_port_index, + Vector2DOf position, + Entity *entity, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription *p = methodDescription.parameterList; + + OffsetPosition offset = + (strcmp(p[3].data.string, "bottom") == 0) ? bottom : center; + + MapDisplay *gauge = (MapDisplay *)operator new(sizeof(MapDisplay)); + if (gauge != NULL) + { + new (gauge) MapDisplay( + p[0].data.rate, p[1].data.modeMask, + (L4GaugeRenderer *)gauge_renderer, + 0, // owner_ID + display_port_index, + position.x, position.y, // left, bottom + position.x + p[2].data.vector.x, // right + position.y + p[2].data.vector.y, // top + offset, + p[4].data.integer, // view width (degrees) + p[5].data.color, p[6].data.color, p[7].data.color, // bg, static, hotbox + p[8].data.scalar, // maximum range + entity, + (Scalar *)p[9].data.attributePointer, // scale (RadarRange) + (Point3D **)p[10].data.attributePointer, // position (RadarLinearPosition) + (Quaternion **)p[11].data.attributePointer, // angle (RadarAngularPosition) + (Scalar *)p[12].data.attributePointer); // capabilities (Avionics/RadarPercent) + } + return True; +} + //############################################################################# // Construction / Destruction // @@ -451,15 +524,19 @@ void MapDisplay::Execute() { int phase = operatingPhase++; // this+0x354 + const int mlog = getenv("BT_MAP_LOG") ? 1 : 0; if (phase == 0) { operating = (OperatingEpsilon < currentCapabilitiesRatio); // 0.0f < ratio + if (mlog) DEBUG_STREAM << "[map] p0 operating=" << (int)operating + << " cap=" << currentCapabilitiesRatio << " scale=" << currentScale << "\n" << std::flush; if (operating) { maximumDistanceSquared = currentCapabilitiesRatio * maximumRange; maximumDistanceSquared = maximumDistanceSquared * maximumDistanceSquared; CalculateBounds(); // FUN_004c2178 + if (mlog) DEBUG_STREAM << "[map] p0 bounds ok\n" << std::flush; } } else if (phase == 1) @@ -470,6 +547,7 @@ void renderer->GetStaticEntitiesWithinBounds( // FUN_00443ba4(renderer+0xB0) &staticEntityList, xMin, yMin, zMin, xMax, yMax, zMax); + if (mlog) DEBUG_STREAM << "[map] p1 static gathered\n" << std::flush; } } else if (phase == 2) @@ -480,6 +558,7 @@ void renderer->GetMovingEntitiesWithinBounds( // FUN_0044366c(renderer+0x94) &movingEntityList, xMin, yMin, zMin, xMax, yMax, zMax); + if (mlog) DEBUG_STREAM << "[map] p2 moving gathered\n" << std::flush; } } else @@ -491,6 +570,17 @@ void { DrawDisplay(); // FUN_004c22c4 } + if (getenv("BT_MAP_LOG")) + { + static int s_c = 0; + if ((s_c++ % 15) == 0) + DEBUG_STREAM << "[map] operating=" << (int)operating + << " scale=" << currentScale + << " cap=" << currentCapabilitiesRatio + << " posPtr=" << (currentPositionPointer ? 1 : 0) + << " anglePtr=" << (currentAngularPointer ? 1 : 0) + << "\n" << std::flush; + } } operatingPhase = 0; } @@ -613,10 +703,16 @@ void flashState = !flashState; // this+0x374 ^= 1 localView.AttachRecorder(&previousDrawing); // (this+0x48 vtbl+0x64) + const int mlog = getenv("BT_MAP_LOG") ? 1 : 0; + if (mlog) DEBUG_STREAM << "[map] draw: wedge...\n" << std::flush; DrawViewWedge(); // FUN_004c2484 + if (mlog) DEBUG_STREAM << "[map] draw: static...\n" << std::flush; DrawStatic(worldToView); // FUN_004c25b4 + if (mlog) DEBUG_STREAM << "[map] draw: moving...\n" << std::flush; DrawMoving(worldToView); // FUN_004c2788 + if (mlog) DEBUG_STREAM << "[map] draw: names...\n" << std::flush; DrawNames(worldToView); // FUN_004c28c4 + if (mlog) DEBUG_STREAM << "[map] draw: done\n" << std::flush; localView.DetachRecorder(); // (this+0x48 vtbl+0x68) } @@ -789,6 +885,8 @@ void iter.First(); int count = iter.GetSize(); // vtbl+0x14 + if (getenv("BT_MAP_LOG")) + DEBUG_STREAM << "[map] names count=" << count << "\n" << std::flush; if (count < 1) { return; @@ -860,12 +958,18 @@ Pip * // // FUN_00417ab4 -- the gauge renderer's "operator entity" (the mech whose -// cockpit this gauge belongs to). Not exposed by the WinTesla GaugeRenderer. +// cockpit this gauge belongs to). The renderer's linked-entity socket may be +// unwired on WinTesla, so fall back to the application viewpoint entity (the mech +// we are driving) -- the same fallback HeadingPointer::Execute uses. Returning +// NULL crashes the map's CalculateBounds/DrawDisplay (EntityPosition(NULL)). // static Entity * - ResolveOperatorEntity(GaugeRenderer * /*renderer*/) + ResolveOperatorEntity(GaugeRenderer *renderer) { - return NULL; + Entity *e = renderer ? ((L4GaugeRenderer *)renderer)->GetLinkedEntity() : NULL; + if (e == NULL && application != NULL) + e = (Entity *)application->GetViewpointEntity(); + return e; } // diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index 3a5c33a..a486069 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -426,7 +426,30 @@ const Mech::IndexEntry ATTRIBUTE_ENTRY(Mech, FootStep, attrPad), // 0x1e ATTRIBUTE_ENTRY(Mech, AnimationState, attrPad), // 0x1f ATTRIBUTE_ENTRY(Mech, ReplicantAnimationState, attrPad), // 0x20 - ATTRIBUTE_ENTRY(Mech, LinearSpeed, linearSpeed) // 0x21 (live forward speed) + ATTRIBUTE_ENTRY(Mech, LinearSpeed, linearSpeed), // 0x21 (live forward speed) + ATTRIBUTE_ENTRY(Mech, ClimbRate, attrPad), // 0x22 + ATTRIBUTE_ENTRY(Mech, AccelerationLastFrame, attrPad), // 0x23 + ATTRIBUTE_ENTRY(Mech, AngularSpeed, attrPad), // 0x24 + ATTRIBUTE_ENTRY(Mech, ArmorDamageLevel, attrPad), // 0x25 + ATTRIBUTE_ENTRY(Mech, SubsystemDamageLevel, attrPad), // 0x26 + ATTRIBUTE_ENTRY(Mech, MyomerDamageLevel, attrPad), // 0x27 + ATTRIBUTE_ENTRY(Mech, TestButton1, attrPad), // 0x28 + ATTRIBUTE_ENTRY(Mech, TestButton2, attrPad), // 0x29 + ATTRIBUTE_ENTRY(Mech, TestButton3, attrPad), // 0x2a + ATTRIBUTE_ENTRY(Mech, TestButton4, attrPad), // 0x2b + ATTRIBUTE_ENTRY(Mech, TestButton5, attrPad), // 0x2c + ATTRIBUTE_ENTRY(Mech, TestButton6, attrPad), // 0x2d + ATTRIBUTE_ENTRY(Mech, ReduceButton, attrPad), // 0x2e + ATTRIBUTE_ENTRY(Mech, RadarRange, radarRange), // 0x2f (radar scale) + ATTRIBUTE_ENTRY(Mech, RadarLinearPosition, radarLinearPosition), // 0x30 (Point3D* -> localOrigin) + ATTRIBUTE_ENTRY(Mech, RadarAngularPosition, radarAngularPosition),// 0x31 (Quaternion* -> localOrigin) + ATTRIBUTE_ENTRY(Mech, RearFiring, attrPad), // 0x32 + ATTRIBUTE_ENTRY(Mech, RequestDuckAnimation, attrPad), // 0x33 + ATTRIBUTE_ENTRY(Mech, UnstablePercentage, attrPad), // 0x34 + ATTRIBUTE_ENTRY(Mech, SuperStop, attrPad), // 0x35 + ATTRIBUTE_ENTRY(Mech, IncomingLock, attrPad), // 0x36 + ATTRIBUTE_ENTRY(Mech, DuckState, duckState), // 0x37 (crouch posture, int) + ATTRIBUTE_ENTRY(Mech, DistanceToMissile, attrPad) // 0x38 }; Mech::AttributeIndexSet& @@ -572,6 +595,12 @@ Mech::Mech( sensorSubsystem = 0; // this[0x1f7] (0xBBE) -- same hazard, zero it too attrPad = 0.0f; // shared read pad for un-populated gauge attributes linearSpeed = 0.0f; // live forward ground speed (LinearSpeed gauge); set per frame + radarRange = 500.0f; // radar display scale (SetTargetRange is stubbed; + // 500m default zoom so nearby contacts are visible, + // vs the config maximum_range=4000 outer detection edge) + radarLinearPosition = &localOrigin.linearPosition; // map reads the mech's live world position... + radarAngularPosition= &localOrigin.angularPosition; // ...and orientation (pointers into the base origin) + duckState = 0; // not crouching ZeroBlock(this + 0xca, 6); // this[0xca..0xcf] = 0 ZeroBlock(this + 0x153, 6); // this[0x153..0x158] = 0 diff --git a/game/reconstructed/mech.hpp b/game/reconstructed/mech.hpp index 273a21c..1228ff8 100644 --- a/game/reconstructed/mech.hpp +++ b/game/reconstructed/mech.hpp @@ -568,6 +568,14 @@ protected: // forward ground speed the speed readout / arc / radar consume (binary @0x81c). Scalar attrPad; Scalar linearSpeed; + // Radar/map gauge attributes (binary @0x404/0x408/0x40c/0x3f8). The map + // widget reads position/angle as POINTERS into the mech's live origin, so + // radarLinearPosition/radarAngularPosition point at localOrigin.{linear, + // angular}Position (set in the ctor); radarRange is the radar scale. + Scalar radarRange; // 0x2f RadarRange (scale/max) + Point3D *radarLinearPosition; // 0x30 RadarLinearPosition + Quaternion *radarAngularPosition; // 0x31 RadarAngularPosition + int duckState; // 0x37 DuckState (crouch posture) // --- raw / engine-shim members touched by the ctor slice ------------- void *vtable; // installed Mech vtable ptr diff --git a/game/reconstructed/sensor.cpp b/game/reconstructed/sensor.cpp index 4e043cb..29ba77f 100644 --- a/game/reconstructed/sensor.cpp +++ b/game/reconstructed/sensor.cpp @@ -103,8 +103,29 @@ Derivation Receiver::MessageHandlerSet Sensor::MessageHandlers; +// +// Attribute Support -- the gauge bindings for the Sensor (named "Avionics" in the +// cockpit config): Avionics/RadarPercent (the map's capabilities ratio), SelfTest, +// BadVoltage. SENSOR.HPP declares the enum + AttributePointers[]; the empty +// default-ctor AttributeIndex published NOTHING (not even SimulationState). Build +// it from the table chained to PoweredSubsystem::GetAttributeIndex() (which +// resolves to HeatSink's dense index) so the merged index stays dense (ids run +// contiguously from PoweredSubsystem::NextAttributeID). +// +const Sensor::IndexEntry + Sensor::AttributePointers[]= +{ + ATTRIBUTE_ENTRY(Sensor, RadarPercent, radarPercent), // @0x31C (map capabilities ratio) + ATTRIBUTE_ENTRY(Sensor, SelfTest, selfTest), + ATTRIBUTE_ENTRY(Sensor, BadVoltage, badVoltage) +}; + Sensor::AttributeIndexSet - Sensor::AttributeIndex; + Sensor::AttributeIndex( + ELEMENTS(Sensor::AttributePointers), + Sensor::AttributePointers, + PoweredSubsystem::GetAttributeIndex() + ); Sensor::SharedData Sensor::DefaultData( @@ -256,6 +277,16 @@ void // this[0x38] is the resolved thermal master sink; +0x158 == heatEnergy. radarPercent = RadarBaseline - HeatMasterEnergy(); + // ⚠ BRING-UP GUARD (heat-leaf branch not byte-exact): the inherited heatEnergy + // reads un-normalized/garbage here (~3850) so radarPercent goes hugely negative + // and the radar reports non-operational. radarPercent is authentically a [0,1] + // capability ratio, so when the heat term is out of range treat it as no penalty + // (full radar). A real overheat penalty needs the heat-energy normalization + // (deferred with the heat-leaf re-base). Exposed once the map gauge began + // reading Avionics/RadarPercent; inert before. + if (HeatMasterEnergy() < 0.0f || HeatMasterEnergy() > RadarBaseline) + radarPercent = RadarBaseline; + if (HeatModelOff()) // this[0x10] == 1 { radarPercent = 0.0f;