//===========================================================================// // 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 #pragma hdrstop #if !defined(BTPLAYER_HPP) # include #endif #if !defined(BTTEAM_HPP) # include #endif #if !defined(BTMSSN_HPP) # include #endif #if !defined(NTTMGR_HPP) # include #endif #if !defined(APP_HPP) # include #endif #if !defined(DROPZONE_HPP) # include #endif #if !defined(MECH_HPP) # include #endif #if !defined(RESOURCE_HPP) # include #endif #if !defined(HOSTMGR_HPP) # include #endif #if !defined(MISSION_HPP) # include #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 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; 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 (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); 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 (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(); }