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>
833 lines
25 KiB
C++
833 lines
25 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
|
|
|
|
#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);
|
|
((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();
|
|
}
|