Files
TeslaRel410/restoration/source410/BT/BTPLAYER.CPP
T
CydandClaude Fable 5 3610ff1ecd BT410: entity simulation goes LIVE -- mission reaches RunningMission, mech + roster tick per-frame
The world now executes. Entity::Execute only PerformAndWatch's entities in
application state RunningMission, reached by two RunMissionMessages
(WaitingForLaunch->LaunchingMission->RunningMission). The 2nd is dispatched by
Player::ManageApplicationStatus when the launch fade expires -- but that only
runs inside PlayerSimulation, not the launch-phase HuntForDropZone. So the
player must switch Performance onto PlayerSimulation after it spawns.

- BTPLAYER.CPP DropZoneReplyMessageHandler: after CreatePlayerVehicle +
  InitializePlayerLink, choose the per-vehicle Performance by class -- Mech ->
  SetPerformance(PlayerSimulation)+SetScoringPlayerFlag; else CameraShipSimulation.
- BTPLAYER.CPP PlayerSimulation: chains the authentic base Player::PlayerSimulation
  (CalcRanking + ManageApplicationStatus + vehicle-pos copy + status service);
  console SCORE-delta flush deferred to the scoring wave.
- SENSOR.CPP SensorSimulation: minimal-safe partial (radarPercent = 1 -
  GetSubsystemDamageLevel, sensor healthy) so the roster tick path can't Fail
  once RunningMission engages. The heat/electrical gating awaits the power/heat
  sim wave (PoweredSubsystem/Generator/HeatSink/HeatWatcher *Simulation staged).

Verified: BT_MECH_LOG run reaches "[tick] roster live (first Sensor frame)",
two RunMissionHandler transitions, zero Fail/Exception. The mech is executed
each frame; its BODY Performance is still DoNothingOnce (real Mech::Simulate =
Phase 5.3, now the frontier).

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

491 lines
14 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(RESOURCE_HPP)
# include <resource.hpp>
#endif
#if !defined(HOSTMGR_HPP)
# include <hostmgr.hpp>
#endif
#if !defined(MISSION_HPP)
# include <mission.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 (novice / standard / veteran / expert) and advanced-damage
// technician flag. See BTPLAYER.NOTES.md for the flag mapping.
//
Check(bt_mission);
switch (bt_mission->ExperienceLevel())
{
case BTMission::NoviceMode:
showDamageReceived = 0;
showKills = 0;
roleReturnDelay2 = 0;
showScore = 0;
break;
case BTMission::StandardMode:
showDamageReceived = 1;
showKills = 0;
roleReturnDelay2 = 1;
showScore = 1;
break;
case BTMission::VeteranMode:
showDamageReceived = 1;
showKills = 1;
roleReturnDelay2 = 1;
showScore = 1;
break;
case BTMission::ExpertMode:
showDamageReceived = 1;
showKills = 1;
roleReturnDelay2 = 0;
showScore = 1;
break;
}
showDamageInflicted = bt_mission->AdvancedDamageOn();
roleReturnDelay = bt_mission->AdvancedDamageOn();
roleClassIndex = bt_mission->ExperienceLevel();
}
killCount = 0;
padScore = 0;
objectiveMech = NULL;
lastPerformance = lastConsoleUpdate = lastUpdate;
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;
}
else if (deathCount == message->deathCount)
{
if (GetSimulationState() == MissionEndingState)
{
Check_Fpu();
return;
}
//
// Respawn: reset the existing dead mech in place (deferred with the
// Mech subsystem).
//
}
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);
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 (message->deathCount != -1)
{
Player::VehicleDeadMessageHandler(message);
Check_Fpu();
return;
}
Fail("BTPlayer::VehicleDeadMessageHandler(-1) -- death/respawn cycle deferred (mech4/scoring)");
}
void
BTPlayer::ScoreMessageHandler(ScoreMessage *)
{
Fail("BTPlayer::ScoreMessageHandler -- btplayer.cpp not yet reconstructed");
}
void
BTPlayer::ScoreInflictedMessageHandler(ScoreMessage *)
{
Fail("BTPlayer::ScoreInflictedMessageHandler -- btplayer.cpp not yet reconstructed");
}
void
BTPlayer::ScoreUpdateMessageHandler(ScoreMessage *)
{
Fail("BTPlayer::ScoreUpdateMessageHandler -- btplayer.cpp not yet reconstructed");
}
//
//#############################################################################
// 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();
}