BT410 Phase 5.3.25: scoring + the COMPLETE mission lifecycle -- egg to GAME-RC

Scoring roles bound (binary ctor tail @004c0bc8): the mission role dict ->
Player::scenarioRole; TEST.EGG's Role::Default = the baked dfltrole
(lives=1000, killBonus=500) -- authored data flowing.  The three score
handlers are live per the decomp derivations (@004c02e4 / @004c0200 /
@004c02a8 + CalcInflictedScore @004c052c; tonnage/bias staged); the
producers post damage scores per delivered hit and the kill credit from
the death transition with the latched killing-blow magnitude.  First live
kill: award=3.43, kills=1, score=123.6.

Out of lives -> the mission END: the binary's +10s review post sits in the
one decomp gap, so the staged stand-in enters the engine's own cascade
(StopMissionMessage +10s) -- EndingMission -> the 3s fade -> the second
Stop -> Application::Stop -> RunMissions exits.  The BTL4/L4 stop layers
run authentically (plasma score display off, egress lamps).

VERIFIED END-TO-END: a 2-lives egg (role-page return=2 override) -- death
#1 debits and respawns healed; death #2 is OUT OF LIVES; +10s later the
mission ends and BTL4OPT.EXE exits CLEANLY to DOS (GAME-RC prints).  The
full 1995 pod mission loop -- egg, boot, spawn, fight, score, die,
respawn, die, mission end, exit -- now runs from reconstructed source.
Zero faults; fight/smoke/novice regressions green.  Dev knob:
BT_PLAYER_PASSIVE=1 (piloted mechs hold force-fire).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-24 17:18:41 -05:00
co-authored by Claude Fable 5
parent 623c872c92
commit f65a3259ce
9 changed files with 653 additions and 248 deletions
+205 -15
View File
@@ -55,6 +55,14 @@
# include <mission.hpp>
#endif
#if !defined(APPMSG_HPP)
# include <appmsg.hpp>
#endif
#if !defined(SCNROLE_HPP)
# include <scnrole.hpp>
#endif
//
//#############################################################################
// Shared data support
@@ -207,6 +215,38 @@ BTPlayer::BTPlayer(
objectiveMech = NULL;
lastPerformance = lastConsoleUpdate = lastUpdate;
//
// Bind the SCORING ROLE (the binary ctor tail @004c0bc8: look the role
// up in the mission's role dictionary by the MakeMessage roleName and
// stash it at this+0x208). BTL4Mission::SetPlayerData populated the
// dictionary before any player exists; the role carries the score
// modifiers AND the lives counter (returnFromDeath). A missing page
// leaves the base ctor's NULL (dev shape: infinite lives, raw-damage
// scores).
//
if (btMission != NULL)
{
scenarioRole =
Cast_Object(BTMission *, btMission)->GetScenarioRole(
creation_message->roleName);
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[role] '" << creation_message->roleName << "'";
if (scenarioRole != NULL)
{
DEBUG_STREAM
<< " lives=" << scenarioRole->GetReturnFromDeath()
<< " killBonus=" << scenarioRole->GetKillBonus()
<< " dmgMod=" << scenarioRole->GetDamageInflictedModifier();
}
else
{
DEBUG_STREAM << " NOT FOUND (role-less: infinite lives)";
}
DEBUG_STREAM << endl << flush;
}
}
Check_Fpu();
}
@@ -576,43 +616,193 @@ void
++deathCount;
message->deathCount = deathCount;
if (GetScenarioRole() != NULL)
if (scenarioRole != NULL)
{
GetScenarioRole()->SetReturnFromDeath(
GetScenarioRole()->GetReturnFromDeath() - 1);
scenarioRole->SetReturnFromDeath(
scenarioRole->GetReturnFromDeath() - 1);
}
if (GetScenarioRole() == NULL
|| GetScenarioRole()->GetReturnFromDeath() > 0)
if (scenarioRole == NULL
|| scenarioRole->GetReturnFromDeath() > 0)
{
Time when = Now();
when += 5.0f;
application->Post(HighEventPriority, this, message, when);
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[vdead] death #" << deathCount
<< " lives="
<< ((scenarioRole != NULL)
? scenarioRole->GetReturnFromDeath() : -1)
<< " -- respawn posted +5s" << endl << flush;
}
}
if (getenv("BT_MECH_LOG"))
else
{
DEBUG_STREAM << "[vdead] death #" << deathCount
<< " -- respawn posted +5s" << endl << flush;
//
// OUT OF LIVES. The binary posts an id-0x18 message at +10s (the
// mission-review kick; its exact receiver sits in the one decomp
// gap 4c05c4..4c083c). STAGED stand-in with the engine's own
// end-of-mission entry: StopMissionMessage at +10s -- the full
// authentic cascade runs from there (EndingMission -> the player's
// 3s fade -> the second Stop -> Application::Stop -> RunMissions
// exits -> GAME-RC). The review/playback launch joins the
// mission-review wave.
//
Application::StopMissionMessage stop_mission(NullExitCodeID);
Time when = Now();
when += 10.0f;
application->Post(HighEventPriority, application, &stop_mission, when);
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[vdead] death #" << deathCount
<< " OUT OF LIVES -- mission end posted +10s"
<< endl << flush;
}
}
}
void
BTPlayer::ScoreMessageHandler(ScoreMessage *)
BTPlayer::ScoreMessageHandler(ScoreMessage *message)
{
Fail("BTPlayer::ScoreMessageHandler -- btplayer.cpp not yet reconstructed");
Check(this);
Check(message);
//
// Binary @004c02e4. Master-only; everything is ignored once the
// mission is ending. Type 0 has its own handler; type 1 = points LOST
// for damage taken; type 2 = the kill credit. The tonnage ratio
// (mech+0x4bc) and mech damage bias (+0x354) are STAGED at 1.0/0.0
// (members not reconstructed): awards degrade to the role-scaled
// damage figures, raw damage with no role (the dev shape).
//
if (GetSimulationState() == MissionEndingState)
{
return;
}
HostManager *host_manager = application->GetHostManager();
Entity *sender_mech =
(Entity *)host_manager->GetEntityPointer(message->senderMechID);
switch (message->scoreType)
{
case DamageReceivedScore:
if (sender_mech != NULL && sender_mech != playerVehicle
&& scenarioRole != NULL)
{
message->scoreAward =
scenarioRole->CalcDamageReceivedScore(message->damageAmount);
// (the console VTV-damaged feed joins the console wave)
Player::ScoreMessageHandler(message);
}
break;
case KillScore:
{
Scalar award = CalcInflictedScore(
message->damageAmount, sender_mech, message->scoreAward);
if (sender_mech != NULL && sender_mech == playerVehicle)
{
//
// Suicide: no credit, no kill count.
//
award = -award;
}
else
{
++killCount;
// (the victim-side status ticker joins the console wave)
}
message->scoreAward = award;
Player::ScoreMessageHandler(message);
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[score] KILL award=" << award
<< " kills=" << killCount
<< " score=" << currentScore << endl << flush;
}
}
break;
default:
//
// Type 0 belongs to the ScoreInflicted handler (the binary asserts
// here); drop it quietly in the opt shape.
//
break;
}
Check_Fpu();
}
void
BTPlayer::ScoreInflictedMessageHandler(ScoreMessage *)
BTPlayer::ScoreInflictedMessageHandler(ScoreMessage *message)
{
Fail("BTPlayer::ScoreInflictedMessageHandler -- btplayer.cpp not yet reconstructed");
Check(this);
Check(message);
//
// Binary @004c0200 (type 0 only): points EARNED for damage delivered.
// Accumulates DIRECTLY into currentScore (not via the base handler);
// negated for self-hits. Tonnage ratio staged 1.0.
//
if (GetSimulationState() == MissionEndingState)
{
return;
}
HostManager *host_manager = application->GetHostManager();
Entity *target_mech =
(Entity *)host_manager->GetEntityPointer(message->senderMechID);
Scalar award = CalcInflictedScore(message->damageAmount, target_mech, 0.0f);
if (target_mech != NULL && target_mech == playerVehicle)
{
award = -award;
}
currentScore += award;
Check_Fpu();
}
void
BTPlayer::ScoreUpdateMessageHandler(ScoreMessage *)
BTPlayer::ScoreUpdateMessageHandler(ScoreMessage *message)
{
Fail("BTPlayer::ScoreUpdateMessageHandler -- btplayer.cpp not yet reconstructed");
Check(this);
Check(message);
//
// Binary @004c02a8: the network score resync -- fold the carried award
// straight through the base handler.
//
Player::ScoreMessageHandler(message);
Check_Fpu();
}
//
//#############################################################################
// CalcInflictedScore (binary @004c052c) -- the shared damage -> points
// converter. factor = -friendlyFirePenalty on a team hit, else
// (mechDamageBias x roleDamageBias + 1); STAGED: mech damage bias (+0x354)
// unreconstructed = 0, so factor degrades to 1 (or the friendly penalty).
//#############################################################################
//
Scalar
BTPlayer::CalcInflictedScore(
Scalar damage_amount,
Entity * /*other_mech -- the team / bias source, staged*/,
Scalar bias
)
{
Check(this);
if (scenarioRole == NULL)
{
return damage_amount + bias;
}
return (damage_amount + bias)
* scenarioRole->GetDamageInflictedModifier();
}
//
+295 -229
View File
@@ -1,229 +1,295 @@
//===========================================================================//
// File: btplayer.hpp //
// Project: BattleTech //
// Contents: Implementation details for the BT player //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
#if !defined(BTPLAYER_HPP)
# define BTPLAYER_HPP
# if !defined(PLAYER_HPP)
# include <player.hpp>
# endif
//##################### Forward Class Declarations #######################
class Mech;
class BTTeam;
class Mission;
//###########################################################################
//####################### BTPlayer Make Message #########################
//###########################################################################
class BTPlayer__MakeMessage:
public Player__MakeMessage
{
public:
CString roleName;
CString teamName;
BTPlayer__MakeMessage(
Receiver::MessageID message_ID,
size_t length,
Entity::ClassID class_ID,
const EntityID &owner_ID,
ResourceDescription::ResourceID resource_ID,
LWord instance_flags,
const Origin &origin,
int player_bitmap_index,
CString role_name,
CString team_name
):
Player__MakeMessage(
message_ID,
length,
class_ID,
owner_ID,
resource_ID,
instance_flags,
origin,
player_bitmap_index
)
{
roleName = role_name;
teamName = team_name;
}
};
//###########################################################################
//############################# BTPlayer ################################
//###########################################################################
class BTPlayer:
public Player
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
public:
static Derivation ClassDerivations;
static SharedData DefaultData;
static const HandlerEntry
MessageHandlerEntries[];
static MessageHandlerSet
MessageHandlers;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Message Support
//
public:
enum {
ScoreInflictedMessageID = Player::NextMessageID,
ScoreUpdateMessageID,
NextMessageID
};
protected:
void
DropZoneReplyMessageHandler(DropZone__ReplyMessage *message);
void
VehicleDeadMessageHandler(VehicleDeadMessage *message);
void
ScoreMessageHandler(ScoreMessage *message);
void
ScoreInflictedMessageHandler(ScoreMessage *message);
void
ScoreUpdateMessageHandler(ScoreMessage *message);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Vehicle creation
//
protected:
virtual void
CreatePlayerVehicle(Origin mech_location);
void
InitializePlayerLink();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction
//
public:
typedef BTPlayer__MakeMessage MakeMessage;
static BTPlayer*
Make(MakeMessage *creation_message);
BTPlayer(
MakeMessage *creation_message,
SharedData &shared_data = DefaultData
);
~BTPlayer();
Logical
TestInstance() const;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Experience support
//
public:
Enumeration
GetExperienceLevel();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Model Support
//
public:
typedef void
(BTPlayer::*Performance)(Scalar time_slice);
void
SetPerformance(Performance performance)
{
Check(this);
activePerformance = (Simulation::Performance)performance;
}
void
PlayerSimulation(Scalar time_slice);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Local Data
//
protected:
Mission
*btMission;
char
teamName[64];
BTTeam
*teamEntity;
Logical
freeForAll;
Logical
consoleAttached;
Logical
suppressConsole;
//
// EXPERIENCE-LEVEL game-mode flags, derived in the constructor from the
// mission's experience level (novice / standard / veteran / expert) and
// advanced-damage technician flag. TRUE SEMANTICS (the early "display
// toggles" reading of this block was wrong -- see BTPLAYER.NOTES.md):
// binary rows for (simLive, heatModelOn, levelFlag26c, levelFlag270) =
// nov 0,0,0,0 / std 1,0,1,1 / vet 1,1,1,1 / exp 1,1,0,1.
// simLive -- the novice lockout: jams, searchlight, powersub
// short events (CheckForJam's LiveFireEnabled gate);
// heatModelOn -- THE heat-model master switch (HeatModelActive):
// weapon-fire heat, the heat sim, coolant, movement
// heat. Standard mode has NO heat consequences --
// authentic, not a gap;
// advancedDamageOn/2 -- the egg's technician flag (an int pair in the
// binary; NOT part of the level switch);
// experienceLevel -- the raw 0..3 (the ==0 novice-lockout predicate).
//
Logical
simLive;
Logical
heatModelOn;
Logical
advancedDamageOn;
Logical
advancedDamageOn2;
Logical
levelFlag26c;
Logical
levelFlag270;
int
experienceLevel;
public:
Logical
IsSimLive() const { Check(this); return simLive; }
Logical
IsHeatModelOn() const { Check(this); return heatModelOn; }
Logical
IsAdvancedDamageOn() const { Check(this); return advancedDamageOn; }
protected:
int
killCount;
int
padScore;
Mech
*objectiveMech;
Time
lastConsoleUpdate;
Logical
deathPending;
};
#endif
//===========================================================================//
// File: btplayer.hpp //
// Project: BattleTech //
// Contents: Implementation details for the BT player //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
#if !defined(BTPLAYER_HPP)
# define BTPLAYER_HPP
# if !defined(PLAYER_HPP)
# include <player.hpp>
# endif
//##################### Forward Class Declarations #######################
class Mech;
class BTTeam;
class Mission;
//###########################################################################
//####################### BTPlayer Make Message #########################
//###########################################################################
class BTPlayer__MakeMessage:
public Player__MakeMessage
{
public:
CString roleName;
CString teamName;
BTPlayer__MakeMessage(
Receiver::MessageID message_ID,
size_t length,
Entity::ClassID class_ID,
const EntityID &owner_ID,
ResourceDescription::ResourceID resource_ID,
LWord instance_flags,
const Origin &origin,
int player_bitmap_index,
CString role_name,
CString team_name
):
Player__MakeMessage(
message_ID,
length,
class_ID,
owner_ID,
resource_ID,
instance_flags,
origin,
player_bitmap_index
)
{
roleName = role_name;
teamName = team_name;
}
};
//###########################################################################
//###########################################################################
//###################### BTPlayer Score Message #########################
//###########################################################################
//
// The BT wire layout (extends the engine ScoreMessage's scoreAward
// @+0x1c): scoreType @+0x20, damageAmount @+0x24 (the input every award
// derives from), the point-source handles @+0x28..0x30 (console VTV
// feed -- headless zeros), senderMechID @+0x34 (the inflicting / victim
// mech, resolved via the host manager).
//
class BTPlayer__ScoreMessage:
public Player__ScoreMessage
{
public:
int scoreType;
Scalar damageAmount;
int pointSenderHi;
int pointSenderLo;
int auxID;
EntityID senderMechID;
BTPlayer__ScoreMessage(
Receiver::MessageID message_ID,
size_t length,
Scalar score,
int score_type,
Scalar damage_amount,
const EntityID &sender_mech_ID
):
Player__ScoreMessage(message_ID, length, score),
scoreType(score_type),
damageAmount(damage_amount),
pointSenderHi(0),
pointSenderLo(0),
auxID(0),
senderMechID(sender_mech_ID)
{}
};
//############################# BTPlayer ################################
//###########################################################################
class BTPlayer:
public Player
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
public:
static Derivation ClassDerivations;
static SharedData DefaultData;
static const HandlerEntry
MessageHandlerEntries[];
static MessageHandlerSet
MessageHandlers;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Message Support
//
public:
enum {
ScoreInflictedMessageID = Player::NextMessageID,
ScoreUpdateMessageID,
NextMessageID
};
//
// The score types the BT ScoreMessage carries (branch selector
// @004c02e4; type 0 has its own handler @004c0200).
//
enum {
DamageInflictedScore = 0,
DamageReceivedScore = 1,
KillScore = 2
};
public:
typedef BTPlayer__ScoreMessage ScoreMessage;
//
// The shared damage -> points converter (binary @004c052c):
// (damage + bias) x role damageInflictedModifier x factor, where a
// friendly hit flips to -friendlyFirePenalty. Role-less players
// degrade to raw damage (the dev-mission shape).
//
Scalar
CalcInflictedScore(
Scalar damage_amount,
Entity *other_mech,
Scalar bias
);
protected:
void
DropZoneReplyMessageHandler(DropZone__ReplyMessage *message);
void
VehicleDeadMessageHandler(VehicleDeadMessage *message);
void
ScoreMessageHandler(ScoreMessage *message);
void
ScoreInflictedMessageHandler(ScoreMessage *message);
void
ScoreUpdateMessageHandler(ScoreMessage *message);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Vehicle creation
//
protected:
virtual void
CreatePlayerVehicle(Origin mech_location);
void
InitializePlayerLink();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction
//
public:
typedef BTPlayer__MakeMessage MakeMessage;
static BTPlayer*
Make(MakeMessage *creation_message);
BTPlayer(
MakeMessage *creation_message,
SharedData &shared_data = DefaultData
);
~BTPlayer();
Logical
TestInstance() const;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Experience support
//
public:
Enumeration
GetExperienceLevel();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Model Support
//
public:
typedef void
(BTPlayer::*Performance)(Scalar time_slice);
void
SetPerformance(Performance performance)
{
Check(this);
activePerformance = (Simulation::Performance)performance;
}
void
PlayerSimulation(Scalar time_slice);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Local Data
//
protected:
Mission
*btMission;
char
teamName[64];
BTTeam
*teamEntity;
Logical
freeForAll;
Logical
consoleAttached;
Logical
suppressConsole;
//
// EXPERIENCE-LEVEL game-mode flags, derived in the constructor from the
// mission's experience level (novice / standard / veteran / expert) and
// advanced-damage technician flag. TRUE SEMANTICS (the early "display
// toggles" reading of this block was wrong -- see BTPLAYER.NOTES.md):
// binary rows for (simLive, heatModelOn, levelFlag26c, levelFlag270) =
// nov 0,0,0,0 / std 1,0,1,1 / vet 1,1,1,1 / exp 1,1,0,1.
// simLive -- the novice lockout: jams, searchlight, powersub
// short events (CheckForJam's LiveFireEnabled gate);
// heatModelOn -- THE heat-model master switch (HeatModelActive):
// weapon-fire heat, the heat sim, coolant, movement
// heat. Standard mode has NO heat consequences --
// authentic, not a gap;
// advancedDamageOn/2 -- the egg's technician flag (an int pair in the
// binary; NOT part of the level switch);
// experienceLevel -- the raw 0..3 (the ==0 novice-lockout predicate).
//
Logical
simLive;
Logical
heatModelOn;
Logical
advancedDamageOn;
Logical
advancedDamageOn2;
Logical
levelFlag26c;
Logical
levelFlag270;
int
experienceLevel;
public:
Logical
IsSimLive() const { Check(this); return simLive; }
Logical
IsHeatModelOn() const { Check(this); return heatModelOn; }
Logical
IsAdvancedDamageOn() const { Check(this); return advancedDamageOn; }
protected:
int
killCount;
int
padScore;
Mech
*objectiveMech;
Time
lastConsoleUpdate;
Logical
deathPending;
};
#endif
@@ -193,3 +193,36 @@ return -- ends the 2s probe loop). Out-of-lives -> mission review post =
scoring wave. The fresh-spawn branch does NOT yet run the shared
Reset/translocation-alarm tail (mech already placed by the MakeMessage;
joins the cockpit wave).
## 5.3.25 — scoring roles + the COMPLETE mission lifecycle
- The ctor binds Player::scenarioRole from the mission role dictionary by
the MakeMessage roleName (binary @004c0bc8 tail; the dict was populated
by BTL4Mission::SetPlayerData before any player exists). TEST.EGG's
Role::Default -> baked 'dfltrole': lives=1000, killBonus=500, dmgMod=1
(authored data flowing). Egg override: `return=N` goes on the ROLE page
([Role::Default]), NOT the player page (BTL4MSSN.CPP:307 reads role_node).
- Score handlers live: Score @004c02e4 (type 1 DamageReceived via
role->CalcDamageReceivedScore; type 2 Kill via CalcInflictedScore + the
suicide negate + killCount, then the base applies scoreAward),
ScoreInflicted @004c0200 (type 0, accumulates DIRECTLY into currentScore,
self-hit negated), ScoreUpdate @004c02a8 (base fold-through).
CalcInflictedScore @004c052c staged: tonnage ratios (mech+0x4bc) and
mechDamageBias (+0x354) unreconstructed -> 1.0/0.0; friendly-fire factor
joins the team wave; console VTV feeds join the console wave.
- Producers (the authentic emitters sit in the unexported master-perf
writer 0x4a9770-0x4ab188; the port shape is used): SendDamage + the
missile fuse post type-0 damage scores to the shooter's pilot; the mech
death transition posts the type-2 kill to the killer's pilot with the
latched killing-blow magnitude (Mech::lastInflictingDamage).
- OUT OF LIVES: the binary's +10s id-0x18 review post lives in the one
decomp gap (4c05c4..4c083c); STAGED stand-in = StopMissionMessage at
+10s -- the ENGINE cascade is fully authentic from there: EndingMission
-> MissionEnding fade (3s) -> the second Stop -> Application::Stop ->
RunMissions exits -> GAME-RC. The BTL4/L4 layers run authentically
(plasma score display off; egress lamps).
- VERIFIED END-TO-END (lifecycle2.conf, KILLTEST.EGG return=2,
BT_PLAYER_PASSIVE=1): lives=2 -> death#1 debit+respawn -> death#2 OUT OF
LIVES -> plasma off -> clean GAME-RC exit. THE FULL 1995 POD MISSION
LOOP, egg to DOS exit, from reconstructed source. Dev knobs:
BT_PLAYER_PASSIVE (piloted mechs hold force-fire).
+12 -1
View File
@@ -482,7 +482,18 @@ void
{
forceFire = (getenv("BT_FORCE_FIRE") != NULL) ? 1 : 0;
}
if (forceFire)
//
// BT_PLAYER_PASSIVE=1: the piloted mech holds fire (the lifecycle
// test -- let the enemy win).
//
static int playerPassive = -1;
if (playerPassive < 0)
{
playerPassive = (getenv("BT_PLAYER_PASSIVE") != NULL) ? 1 : 0;
}
if (forceFire
&& !(playerPassive && owner != NULL
&& owner->GetPlayerLink() != NULL))
{
fireImpulse = (GetWeaponState() == LoadedState) ? 1.0f : 0.0f;
}
+35 -1
View File
@@ -55,6 +55,8 @@
#include <mechdmg.hpp> // Mech::DamageZone -- the hull zone fill (Pass 3)
#include <dmgtable.hpp> // DamageLookupTable -- the cylinder hit table
#include <player.hpp> // Player::VehicleDeadMessage -- the death notification
#include <btplayer.hpp> // BTPlayer::ScoreMessage -- the kill credit
#include <hostmgr.hpp> // HostManager::GetEntityPointer -- killer resolve
//
//#############################################################################
@@ -191,6 +193,7 @@ Mech::Mech(
lastInflictingID = EntityID::Null;
damageLookupTable = NULL;
deathTransitionDone = 0;
lastInflictingDamage = 0.0f;
//
// Look-view angles: defaults until the GameModel read below overrides them
@@ -649,7 +652,8 @@ void
<< endl << flush;
}
lastInflictingID = message->inflictingEntity;
lastInflictingID = message->inflictingEntity;
lastInflictingDamage = message->damageData.damageAmount;
//
// The cylinder resolve (binary @0x4a0264 tail): an UNAIMED hit arrives
@@ -1015,6 +1019,36 @@ void
pilot->Dispatch(&dead);
}
//
// The KILL CREDIT: resolve the last attacker and post the
// type-2 ScoreMessage to the KILLER's pilot, carrying the
// killing-blow magnitude (the whole award derives from it --
// binary emitter in the unexported master-perf writer; the
// kill-bonus bias rides scoreAward, staged 0). MASTER-only,
// and an unpiloted killer (the dev enemy) earns nothing.
//
if (GetInstance() != Entity::ReplicantInstance
&& !(lastInflictingID == EntityID::Null))
{
Entity *killer = (Entity *)application->GetHostManager()->
GetEntityPointer(lastInflictingID);
if (killer != NULL && killer != (Entity *)this
&& killer->IsDerivedFrom(Mech::ClassDerivations)
&& killer->GetPlayerLink() != NULL)
{
BTPlayer::ScoreMessage
kill_score(
Player::ScoreMessageID,
sizeof(BTPlayer::ScoreMessage),
0.0f,
BTPlayer::KillScore,
lastInflictingDamage,
GetEntityID()
);
killer->GetPlayerLink()->Dispatch(&kill_score);
}
}
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[death] mech " << GetEntityID()
+3 -1
View File
@@ -455,6 +455,8 @@
Entity *targetEntity;
EntityID lastInflictingID; // binary mech+0x43c -- last
// attacker (zone LOD router)
Scalar lastInflictingDamage; // the killing-blow
// magnitude (kill score)
int deathTransitionDone; // the once-per-death latch
// (binary movementMode 2||9)
DamageLookupTable *damageLookupTable; // binary mech+0x444 -- the
@@ -473,7 +475,7 @@
// mech2/mech3/mech4 simulation. Reserved until that path is
// reconstructed with named fields.
//
int reservedState[197];
int reservedState[196];
};
#endif
+24
View File
@@ -30,6 +30,10 @@
# include <random.hpp>
#endif
#if !defined(BTPLAYER_HPP)
# include <btplayer.hpp>
#endif
Derivation
MechWeapon::ClassDerivations(
PoweredSubsystem::ClassDerivations,
@@ -452,6 +456,26 @@ void
);
target->Dispatch(&hit);
//
// The DAMAGE SCORE: post the type-0 ScoreInflicted to OUR pilot (the
// authentic producer sits in the unexported master-perf writer; the
// port shape posts per delivered hit). senderMechID = the VICTIM.
//
if (owner != NULL && owner->GetInstance() != Entity::ReplicantInstance
&& owner->GetPlayerLink() != NULL)
{
BTPlayer::ScoreMessage
damage_score(
BTPlayer::ScoreInflictedMessageID,
sizeof(BTPlayer::ScoreMessage),
0.0f,
BTPlayer::DamageInflictedScore,
damage.damageAmount,
target->GetEntityID()
);
owner->GetPlayerLink()->Dispatch(&damage_score);
}
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[dmg] '" << GetName()
+34
View File
@@ -42,6 +42,14 @@
# include <app.hpp>
#endif
#if !defined(HOSTMGR_HPP)
# include <hostmgr.hpp>
#endif
#if !defined(BTPLAYER_HPP)
# include <btplayer.hpp>
#endif
//
// Guidance tuning (binary .rdata @004bf594..@004bf5b0, BT411-decoded).
// STAGED-DEFAULT flight envelope: lifetime / thrust come from the missile
@@ -347,6 +355,32 @@ void
);
victim->Dispatch(&hit);
//
// The DAMAGE SCORE to the launching pilot (type 0, victim as
// sender), resolved through the latched launcher id.
//
{
Entity *launcher_entity =
(Entity *)application->GetHostManager()->
GetEntityPointer(launcherEntityID);
if (launcher_entity != NULL
&& launcher_entity->GetInstance()
!= Entity::ReplicantInstance
&& launcher_entity->GetPlayerLink() != NULL)
{
BTPlayer::ScoreMessage
damage_score(
BTPlayer::ScoreInflictedMessageID,
sizeof(BTPlayer::ScoreMessage),
0.0f,
BTPlayer::DamageInflictedScore,
damageData.damageAmount,
victim->GetEntityID()
);
launcher_entity->GetPlayerLink()->Dispatch(&damage_score);
}
}
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[msl] DETONATE on zone " << zone
+12 -1
View File
@@ -278,7 +278,18 @@ void
{
forceFire = (getenv("BT_FORCE_FIRE") != NULL) ? 1 : 0;
}
if (forceFire)
//
// BT_PLAYER_PASSIVE=1: the piloted mech holds fire (the lifecycle
// test -- let the enemy win).
//
static int playerPassive = -1;
if (playerPassive < 0)
{
playerPassive = (getenv("BT_PLAYER_PASSIVE") != NULL) ? 1 : 0;
}
if (forceFire
&& !(playerPassive && owner != NULL
&& owner->GetPlayerLink() != NULL))
{
fireImpulse = (GetWeaponState() == LoadedState) ? 1.0f : 0.0f;
}