diff --git a/restoration/source410/BT/BTPLAYER.CPP b/restoration/source410/BT/BTPLAYER.CPP index fa120c21..3cb91d39 100644 --- a/restoration/source410/BT/BTPLAYER.CPP +++ b/restoration/source410/BT/BTPLAYER.CPP @@ -55,6 +55,14 @@ # include #endif +#if !defined(APPMSG_HPP) +# include +#endif + +#if !defined(SCNROLE_HPP) +# include +#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(); } // diff --git a/restoration/source410/BT/BTPLAYER.HPP b/restoration/source410/BT/BTPLAYER.HPP index b24d6f11..d7aa3f30 100644 --- a/restoration/source410/BT/BTPLAYER.HPP +++ b/restoration/source410/BT/BTPLAYER.HPP @@ -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 -# 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 +# 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 diff --git a/restoration/source410/BT/BTPLAYER.NOTES.md b/restoration/source410/BT/BTPLAYER.NOTES.md index b250d4c6..26a507c3 100644 --- a/restoration/source410/BT/BTPLAYER.NOTES.md +++ b/restoration/source410/BT/BTPLAYER.NOTES.md @@ -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). diff --git a/restoration/source410/BT/EMITTER.CPP b/restoration/source410/BT/EMITTER.CPP index 860c17f7..a3918b56 100644 --- a/restoration/source410/BT/EMITTER.CPP +++ b/restoration/source410/BT/EMITTER.CPP @@ -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; } diff --git a/restoration/source410/BT/MECH.CPP b/restoration/source410/BT/MECH.CPP index ca2de873..8e559057 100644 --- a/restoration/source410/BT/MECH.CPP +++ b/restoration/source410/BT/MECH.CPP @@ -55,6 +55,8 @@ #include // Mech::DamageZone -- the hull zone fill (Pass 3) #include // DamageLookupTable -- the cylinder hit table #include // Player::VehicleDeadMessage -- the death notification +#include // BTPlayer::ScoreMessage -- the kill credit +#include // 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() diff --git a/restoration/source410/BT/MECH.HPP b/restoration/source410/BT/MECH.HPP index 218445ae..ade9c5d8 100644 --- a/restoration/source410/BT/MECH.HPP +++ b/restoration/source410/BT/MECH.HPP @@ -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 diff --git a/restoration/source410/BT/MECHWEAP.CPP b/restoration/source410/BT/MECHWEAP.CPP index dc6d0699..6249fea4 100644 --- a/restoration/source410/BT/MECHWEAP.CPP +++ b/restoration/source410/BT/MECHWEAP.CPP @@ -30,6 +30,10 @@ # include #endif +#if !defined(BTPLAYER_HPP) +# include +#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() diff --git a/restoration/source410/BT/MISSILE.CPP b/restoration/source410/BT/MISSILE.CPP index 3f4d071b..a16eb266 100644 --- a/restoration/source410/BT/MISSILE.CPP +++ b/restoration/source410/BT/MISSILE.CPP @@ -42,6 +42,14 @@ # include #endif +#if !defined(HOSTMGR_HPP) +# include +#endif + +#if !defined(BTPLAYER_HPP) +# include +#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 diff --git a/restoration/source410/BT/PROJWEAP.CPP b/restoration/source410/BT/PROJWEAP.CPP index 85a8f754..a25bf52f 100644 --- a/restoration/source410/BT/PROJWEAP.CPP +++ b/restoration/source410/BT/PROJWEAP.CPP @@ -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; }