Files
TeslaRel410/restoration/source410/BT/BTPLAYER.CPP
T
CydandClaude Fable 5 e682f64acb BT410 5.3.126: the enemy had no hands -- the lifecycle kill-cycle crash was a missing controls mapper, and the whole mission lifecycle is green again
The crash that has killed lifecycle2.conf since somewhere in 5.3.26..120
was never in the missile code.  The map named Missile::MoveAndCollide
because it resolves the nearest preceding symbol; the instruction bytes
named the truth: mov eax,[ebx+0x128]; mov eax,[eax]; mov eax,[eax+0xFC]
= mech->subsystemArray[0]->member through a NULL slot 0.

Roster slot 0 is the CONTROLS MAPPER, and the BT_SPAWN_ENEMY dev enemy
never got one -- SetMappingSubsystem is only called on the player's
viewpoint mech.  The binary never NULL-checks that slot (mech2's
advancers, the master perf's duck evaluation and the effectiveness
census all deref it bare, correctly: a real mech always has its mapper),
so the enemy faulted the first frame it reached one of those reads.
Nothing dereferenced a non-player mech's mapper before the gait arc,
which is exactly why it lay hidden.

The object was proven by the fault-address rule this project already
learned: the faulting member offset MOVED 0xFC -> 0x100 across the
5.3.125 build, which added ejectLatch to MechControlsMapper -- a 4-byte
layout shift in the very class being dereferenced.

The dev enemy now installs a plain MechControlsMapper (inert without
device input), preserving the binary's contract.  VERIFIED end to end:
kill -> respawn -> second kill -> OUT OF LIVES -> mission end -> clean
DOS exit, zero exceptions -- the full lifecycle is green for the first
time since 5.3.25.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 00:13:29 -05:00

859 lines
26 KiB
C++

//===========================================================================//
// File: btplayer.cpp //
// 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 //
//===========================================================================//
#include <bt.hpp>
#pragma hdrstop
#if !defined(BTPLAYER_HPP)
# include <btplayer.hpp>
#endif
#if !defined(BTTEAM_HPP)
# include <btteam.hpp>
#endif
#if !defined(BTMSSN_HPP)
# include <btmssn.hpp>
#endif
#if !defined(NTTMGR_HPP)
# include <nttmgr.hpp>
#endif
#if !defined(APP_HPP)
# include <app.hpp>
#endif
#if !defined(DROPZONE_HPP)
# include <dropzone.hpp>
#endif
#if !defined(MECH_HPP)
# include <mech.hpp>
#endif
#if !defined(MECHMPPR_HPP)
# include <mechmppr.hpp>
#endif
#if !defined(RESOURCE_HPP)
# include <resource.hpp>
#endif
#if !defined(HOSTMGR_HPP)
# include <hostmgr.hpp>
#endif
#if !defined(MISSION_HPP)
# include <mission.hpp>
#endif
#if !defined(APPMSG_HPP)
# include <appmsg.hpp>
#endif
#if !defined(SCNROLE_HPP)
# include <scnrole.hpp>
#endif
//
//#############################################################################
// Shared data support
//#############################################################################
//
Derivation
BTPlayer::ClassDerivations(
Player::ClassDerivations,
"BTPlayer"
);
const Receiver::HandlerEntry
BTPlayer::MessageHandlerEntries[]=
{
MESSAGE_ENTRY(BTPlayer,DropZoneReply),
MESSAGE_ENTRY(BTPlayer,VehicleDead),
MESSAGE_ENTRY(BTPlayer,Score),
MESSAGE_ENTRY(BTPlayer,ScoreInflicted),
MESSAGE_ENTRY(BTPlayer,ScoreUpdate)
};
BTPlayer::MessageHandlerSet
BTPlayer::MessageHandlers(
ELEMENTS(BTPlayer::MessageHandlerEntries),
BTPlayer::MessageHandlerEntries,
Player::MessageHandlers
);
BTPlayer::SharedData
BTPlayer::DefaultData(
BTPlayer::ClassDerivations,
BTPlayer::MessageHandlers,
Player::AttributeIndex,
1,
(Entity::MakeHandler)BTPlayer::Make
);
//
//#############################################################################
// Constructor
//
// Copies the team name out of the creation message, resolves the owning
// BTTeam, caches the mission, and derives the game-mode display / heat-model
// flags from the mission's experience level and advanced-damage flag.
//#############################################################################
//
BTPlayer::BTPlayer(
MakeMessage *creation_message,
SharedData &shared_data
):
Player(creation_message, shared_data)
{
consoleAttached = False;
suppressConsole = False;
currentScore = 0;
deathPending = False;
//
// Remember our team name from the creation message.
//
strcpy(teamName, creation_message->teamName);
//
// Resolve the BTTeam entity that owns this team name.
//
teamEntity = NULL;
EntityGroup *teams = application->GetEntityManager()->FindGroup("Teams");
if (teams)
{
ChainIteratorOf<Node*> iterator(teams->groupMembers);
BTTeam *team;
while ((team = (BTTeam *)iterator.ReadAndNext()) != NULL)
{
if (strcmp(team->teamName, teamName) == 0)
{
teamEntity = team;
break;
}
}
}
freeForAll = True;
//
// Cache the mission (experience level / advanced-damage source).
//
BTMission *bt_mission =
Cast_Object(BTMission *, application->GetCurrentMission());
btMission = bt_mission;
if (GetInstance() == ReplicantInstance)
{
//
// Replicant: driven by console-update messages.
//
SetPerformance(&BTPlayer::PlayerSimulation);
}
else
{
//
// Master: derive the game-mode flags from the mission experience level
// (the egg's per-pilot "experience" token). The 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.
// Standard mode is LIVE but has no heat model -- authentic.
//
Check(bt_mission);
experienceLevel = bt_mission->ExperienceLevel();
switch (experienceLevel)
{
case BTMission::NoviceMode:
simLive = 0;
heatModelOn = 0;
levelFlag26c = 0;
levelFlag270 = 0;
break;
case BTMission::StandardMode:
simLive = 1;
heatModelOn = 0;
levelFlag26c = 1;
levelFlag270 = 1;
break;
case BTMission::VeteranMode:
simLive = 1;
heatModelOn = 1;
levelFlag26c = 1;
levelFlag270 = 1;
break;
case BTMission::ExpertMode:
simLive = 1;
heatModelOn = 1;
levelFlag26c = 0;
levelFlag270 = 1;
break;
}
advancedDamageOn = bt_mission->AdvancedDamageOn();
advancedDamageOn2 = advancedDamageOn;
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[exp] master player experience=" << experienceLevel
<< " simLive=" << (int)simLive
<< " heatModelOn=" << (int)heatModelOn
<< " advDamage=" << (int)advancedDamageOn << endl << flush;
}
}
killCount = 0;
padScore = 0;
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();
}
//
//#############################################################################
//#############################################################################
//
BTPlayer::~BTPlayer()
{
Check(this);
Check_Fpu();
}
//
//#############################################################################
// Make
//#############################################################################
//
BTPlayer*
BTPlayer::Make(MakeMessage *creation_message)
{
return new BTPlayer(creation_message);
}
//
//#############################################################################
//#############################################################################
//
Logical
BTPlayer::TestInstance() const
{
return IsDerivedFrom(ClassDerivations);
}
//
//#############################################################################
// GetExperienceLevel
//#############################################################################
//
Enumeration
BTPlayer::GetExperienceLevel()
{
Check(this);
Check(btMission);
return Cast_Object(BTMission *, btMission)->ExperienceLevel();
}
//
//#############################################################################
// DropZoneReplyMessageHandler The spawn / respawn handshake: when the drop
// zone replies with our spawn location we create (or, on respawn, reset) the
// player's vehicle there and bind it to us.
//
// The mech placement / respawn-reset tail depends on the Mech subsystem
// (Mech::MechClassID / Mech::Reset), which is not yet reconstructed; the
// dispatch skeleton below halts at CreatePlayerVehicle -> Mech::Make on the
// first (fresh-drop) reply. See BTPLAYER.NOTES.md.
//#############################################################################
//
void
BTPlayer::DropZoneReplyMessageHandler(DropZone__ReplyMessage *message)
{
Check(this);
Check(message);
if (!playerVehicle)
{
CreatePlayerVehicle(message->dropZoneLocation);
InitializePlayerLink();
//
// Choose the per-vehicle Performance from the vehicle's class. A piloted
// mech runs the combat PlayerSimulation and is a scoring player; a
// camera-ship observer runs CameraShipSimulation and never ranks. This
// is load-bearing for mission flow: switching off HuntForDropZone onto
// PlayerSimulation is what starts the launch-fade countdown that advances
// the application to RunningMission (so the world begins executing).
//
if (playerVehicle->GetClassID() == (Entity::ClassID)MechClassID)
{
SetPerformance(&BTPlayer::PlayerSimulation);
SetScoringPlayerFlag();
}
else
{
SetPerformance((Performance)&Player::CameraShipSimulation);
playerRanking = -1;
}
//
// (Mech placement -- Mech::Reset heal+move to the drop-zone origin, the
// translocation cockpit alarm, and the warp-sphere reveal -- is deferred
// with the mech per-frame simulation; the fresh mech sits at its ctor
// origin for now.)
//
AlwaysExecute();
deathCount = 0;
//
// DEV harness (BT_SPAWN_ENEMY=1): spawn a second, unowned mech 150 u
// down +Z from the drop zone through the SAME make machinery (its
// Entity ctor self-registers with the host manager; no player link, so
// its gates read dev-permissive) and hand it to our mech as the
// TARGET. The authentic target writer is the reticle pick (the
// render/mech4 wave); this gives the fire/damage path a live opponent
// headlessly.
//
if (getenv("BT_SPAWN_ENEMY")
&& playerVehicle->GetClassID() == (Entity::ClassID)MechClassID)
{
HostManager *host_manager = application->GetHostManager();
Check(host_manager);
ResourceDescription *mech_res =
application->GetResourceFile()->FindResourceDescription(
playerMission->GetGameModel(),
ResourceDescription::ModelListResourceType
);
Check(mech_res);
mech_res->Lock();
Origin enemy_location = message->dropZoneLocation;
enemy_location.linearPosition.z += 150.0f;
Mech::MakeMessage
create_enemy(
Mech::MakeMessageID,
sizeof(Mech::MakeMessage),
EntityID(host_manager->GetLocalHostID()),
(Entity::ClassID)MechClassID,
EntityID::Null,
mech_res->resourceID,
Mech::DefaultFlags,
enemy_location,
Motion::Identity,
Motion::Identity,
playerMission->GetBadgeName(),
playerMission->GetColorName(),
((BTMission *)playerMission)->GetPatchName()
);
mech_res->Unlock();
Mech *enemy = Mech::Make(&create_enemy);
Register_Object(enemy);
//
// EVERY mech carries a controls mapper in roster slot 0 -- the
// binary never NULL-checks it (mech2's advancers, the master
// perf's duck/census reads all deref it bare). The dev enemy
// gets the plain base mapper (inert without device input)
// precisely to keep that contract: its absence WAS the
// lifecycle kill-cycle crash (subsystemArray[0]->speedDemand
// through NULL once the enemy started limping; the faulting
// member offset MOVED with a mapper-layout edit, proving the
// object). 0x7dc = MechControlsMapperClassID (BTL4MPPR.HPP:26
// -- cited as a literal so the BT layer stays clear of BT_L4
// includes).
//
enemy->SetMappingSubsystem(
new MechControlsMapper(
enemy,
0,
"ControlsMapper",
(RegisteredClass::ClassID)0x7dc
)
);
((Mech *)playerVehicle)->SetTargetEntity(enemy);
//
// Mutual: the enemy targets us back -- with BT_FORCE_FIRE its
// weapons return fire (its gates read dev-permissive, no player
// link), giving the damage cascade a live INCOMING side too.
//
enemy->SetTargetEntity(playerVehicle);
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[enemy] spawned " << (void *)enemy
<< " at z+150, zones=" << enemy->damageZoneCount
<< " -> target locked" << endl << flush;
}
}
}
else if (deathCount == message->deathCount)
{
if (GetSimulationState() == MissionEndingState)
{
Check_Fpu();
return;
}
//
// RESPAWN: reset the existing dead mech in place (the authentic
// reset-based respawn -- the same entity heals and teleports; the
// old sever-and-recreate model was the 1995 port's "two mechs"
// glitch family). A duplicate reply / 2s probe on a mech that is
// already alive again has nothing to do. The level-2 translocation
// cockpit alarm + warp reveal join the cockpit/render waves.
//
if (playerVehicle != NULL
&& playerVehicle->GetClassID() == (Entity::ClassID)MechClassID
&& ((Mech *)playerVehicle)->IsMechDestroyed())
{
((Mech *)playerVehicle)->Reset(message->dropZoneLocation, True);
deathPending = False;
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[respawn] death #" << deathCount
<< " -- mech reset at the drop zone" << endl << flush;
}
}
}
Check_Fpu();
}
//
//#############################################################################
// CreatePlayerVehicle Build the player's mech from the mission game-model
// resource and bind it as the viewpoint entity. (An observer player gets a
// MUNGA camera ship from the base Player::CreatePlayerVehicle instead.)
//
// Constructs the Mech::MakeMessage and hands it to MakeAndLinkViewpointEntity,
// which routes through the registry to Mech::Make -> the Mech ctor (the mech
// subsystem, not yet reconstructed -- current boot frontier).
//#############################################################################
//
void
BTPlayer::CreatePlayerVehicle(Origin mech_location)
{
Check(this);
Check(application);
ResourceFile *resources = application->GetResourceFile();
Check(resources);
Check_Pointer(playerMission->GetGameModel());
HostManager *host_manager = application->GetHostManager();
Check(host_manager);
//
// Make any MUNGA-level vehicle (a camera ship for an observer player).
//
Player::CreatePlayerVehicle(mech_location);
//
// If the player still has no vehicle, build him a mech and link to it.
//
if (!playerVehicle)
{
ResourceDescription *mech_res =
resources->FindResourceDescription(
playerMission->GetGameModel(),
ResourceDescription::ModelListResourceType
);
Check(mech_res);
mech_res->Lock();
Mech::MakeMessage
create_player(
Mech::MakeMessageID,
sizeof(Mech::MakeMessage),
EntityID(host_manager->GetLocalHostID()),
(Entity::ClassID)MechClassID,
EntityID::Null,
mech_res->resourceID,
Mech::DefaultFlags,
mech_location,
Motion::Identity,
Motion::Identity,
playerMission->GetBadgeName(),
playerMission->GetColorName(),
((BTMission *)playerMission)->GetPatchName()
);
mech_res->Unlock();
playerVehicle = application->MakeAndLinkViewpointEntity(&create_player);
Register_Object(playerVehicle);
}
}
//
//#############################################################################
// InitializePlayerLink Tell our freshly-made vehicle (and its network
// replicants) which player owns it, by dispatching a PlayerLinkMessage that
// carries our own EntityID. Identical to the engine's own
// CameraDirector::CreateCameraShip tail (DIRECTOR.CPP): BTPlayer, Player and
// CameraDirector all derive from Entity, so PlayerLinkMessage /
// PlayerLinkMessageID / GetEntityID resolve through the Entity base.
//#############################################################################
//
void
BTPlayer::InitializePlayerLink()
{
Check(this);
Check(playerVehicle);
PlayerLinkMessage
player_link_message(
PlayerLinkMessageID,
sizeof(PlayerLinkMessage),
GetEntityID()
);
playerVehicle->Dispatch(&player_link_message);
playerVehicle->DispatchToReplicants(&player_link_message);
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[link] InitializePlayerLink -> vehicle playerLink="
<< (void *)playerVehicle->GetPlayerLink() << endl << flush;
}
Check_Fpu();
}
//
//#############################################################################
// Scoring / death handlers -- exercised in combat, not during bring-up boot.
//#############################################################################
//
//
//#############################################################################
// VehicleDeadMessageHandler The BT death / respawn cycle. Three message
// flavours arrive here, keyed on message->deathCount:
// -1 the immediate death notification dispatched by the mech's death
// transition (mech4.cpp) -- the full cycle (scoring-role life debit,
// +5s respawn re-post, warp collapse) is downstream of the per-frame
// mech simulation + scoring roles and is staged for now;
// >= 0 our own 5s respawn re-post, and
// -2 the engine's 2s "did the drop-zone reply arrive?" probe that
// Player::HuntForDropZone posts during a normal spawn.
// For everything but -1 the authentic engine base handler does exactly the
// right thing: it re-hunts a drop zone only when this death is still current
// AND one has not been acquired, so a freshly-spawned player (bring-up boot:
// the -2 probe lands after DropZoneReply already made the mech) falls straight
// through. So delegate the non-death flavours to the base.
//#############################################################################
//
void
BTPlayer::VehicleDeadMessageHandler(VehicleDeadMessage *message)
{
Check(this);
Check(message);
if (GetSimulationState() == MissionEndingState)
{
Check_Fpu();
return;
}
if (message->deathCount != -1)
{
//
// The +5s re-post / the 2s probes: if the mech LIVES again the drop
// zone was acquired and the cycle is moot (1995 semantics: "if the
// player has a vehicle again ... moot"; reset-based, so the gate is
// the mech's own death latch).
//
if (playerVehicle != NULL
&& playerVehicle->GetClassID() == (Entity::ClassID)MechClassID
&& !((Mech *)playerVehicle)->IsMechDestroyed())
{
deathPending = False;
Check_Fpu();
return;
}
Player::VehicleDeadMessageHandler(message);
Check_Fpu();
return;
}
//
// The IMMEDIATE death notification (deathCount == -1, dispatched by the
// mech's once-per-death transition). Binary @004c05c4:
// dedup on deathPending (one death, one cycle), bump + stamp deathCount
// (the base handler's identity gate), debit a life from the scoring
// role, and post the +5s respawn re-post (RespawnDelay @004c0830) --
// which falls into the base handler's drop-zone hunt. Out of lives ->
// the mission-review post (deferred with the scoring wave). The warp
// collapse + cockpit death alarm join the render/cockpit waves.
//
if (deathPending)
{
Check_Fpu();
return;
}
deathPending = True;
++deathCount;
message->deathCount = deathCount;
if (scenarioRole != NULL)
{
scenarioRole->SetReturnFromDeath(
scenarioRole->GetReturnFromDeath() - 1);
}
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;
}
}
else
{
//
// 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 *message)
{
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 *message)
{
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 *message)
{
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();
}
//
//#############################################################################
// PlayerSimulation -- the master player's per-frame Performance (installed by
// DropZoneReply once the mech exists, and by the replicant ctor). Chains the
// authentic engine base Player::PlayerSimulation, which is what actually drives
// mission flow: CalcRanking + ManageApplicationStatus (counts down the launch
// fade and, when it expires, dispatches the second RunMissionMessage that
// advances LaunchingMission -> RunningMission so the world starts executing) +
// vehicle-position copy + status-message service.
//
// The BT-specific tail is the periodic console SCORE-delta flush
// (ConsolePlayerVTVScoreUpdateMessage over the network to the operator
// console). It is a no-op without a live console host and needs the networked
// console-message type; it is deferred to the scoring wave.
//#############################################################################
//
void
BTPlayer::PlayerSimulation(Scalar time_slice)
{
Check(this);
Player::PlayerSimulation(time_slice);
Check_Fpu();
}