//===========================================================================// // File: mislanch.cpp // // Project: BattleTech Brick: Mech weapons // // Contents: MissileLauncher -- a projectile weapon that fires missile salvos // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// #include #pragma hdrstop #if !defined(MISLANCH_HPP) # include #endif #if !defined(MECH_HPP) # include #endif #if !defined(MISSILE_HPP) # include #endif #if !defined(APP_HPP) # include #endif #if !defined(HOSTMGR_HPP) # include #endif // // STAGED launch speed (the binary derives it from the authored // MuzzleVelocity vector -- the muzzle wave). // static const Scalar kMissileLaunchSpeed = 150.0f; // u/s Derivation MissileLauncher::ClassDerivations( ProjectileWeapon::ClassDerivations, "MissileLauncher" ); MissileLauncher::SharedData MissileLauncher::DefaultData( MissileLauncher::ClassDerivations, Subsystem::MessageHandlers, Subsystem::AttributeIndex, Subsystem::StateCount ); MissileLauncher::MissileLauncher( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &default_data ): ProjectileWeapon(owner, subsystem_ID, subsystem_resource, default_data) { Check(owner); Check_Pointer(subsystem_resource); missileCount = subsystem_resource->missileCount; // // A salvo delivers missileCount missiles; split the per-shot damage across // the salvo. (The per-missile launch mechanics live in FireWeapon, staged.) // if (missileCount > 0) { damageData.burstCount = missileCount; damageData.damageAmount = damageData.damageAmount / (Scalar)missileCount; } Check_Fpu(); } MissileLauncher::~MissileLauncher() { } Logical MissileLauncher::TestInstance() const { return IsDerivedFrom(ClassDerivations); } // //############################################################################# // Damage / death. //############################################################################# // void MissileLauncher::TakeDamage(Damage &damage) { Check(this); ProjectileWeapon::TakeDamage(damage); } void MissileLauncher::DeathReset(Logical full_reset) { Check(this); // // The weapon respawn restore (the bin restocks in its own DeathReset; // the fresh Loading cycle releases the NoAmmo roach-motel). // MechWeapon::DeathReset(full_reset); } // // The missile-salvo discharge. Not yet reconstructed. // void MissileLauncher::FireWeapon() { Check(this); // // PARTIAL (binary @004bcc60): the authentic body launches missileCount // Missile entities toward the owner's target (each carrying the salvo-split // damageData) and dumps the firing heat. The Missile entity spawn needs // the entity-spawn + targeting waves; the view/target gate, ammo pull and // recoil all live in the CALLER (ProjectileWeaponSimulation's Loaded case). // // Ballistic heatCostToFire is stored 1e7-unit-NATIVE in the stream (SRM6 // reads 5.06e7 = +641K on its own sink -- the same design magnitude as the // PPC's +632K); dump it raw, under the heat-model experience gate (novice / // standard fire generates no heat -- authentic). (The EMITTER's authored // value is small and its 1e7 comes from the energy algebra.) if (HeatModelActive()) { AddPendingHeat(heatCostToFire); } // // The salvo launch (binary @004bcc60): ONE cluster Missile entity per // trigger, spawned through the Mover make machinery -- the MakeMessage's // resourceID is this weapon's projectile GameModel (explosionResourceID // @0x3e4), so the Mover base streams the authored mass/drag. The round // carries the salvo-split cluster damage record (burstCount = // missileCount rides into the gyro bounce + splash) and homes via its // Seeker; the proximity fuse delivers the damage ONCE (the arcade // economy, BT411 task #62). Muzzle = the mech origin STAGED (the // authored MuzzleVelocity arc through the MOUNT segment frame is the // muzzle wave). If the projectile model resource is absent the launch // falls back to the 5.3.16 instant-hit delivery. // if (targetWithinRange && owner != NULL && owner->GetTargetEntity() != NULL && getenv("BT_MISSILE_INSTANT") != NULL) { // // DEV opt-out (BT_MISSILE_INSTANT=1): the 5.3.18a instant-hit // cluster message, kept for A/B damage-economy checks. The flying // Missile below is the DEFAULT since 5.3.19 (teardown closed: the // Projectile ctor NULLs the inherited zone slots). // SendDamage(owner->GetTargetEntity(), damageData); } else if (targetWithinRange && owner != NULL && owner->GetTargetEntity() != NULL) { // // The MakeMessage's resourceID must be a resource FAMILY id -- the // Mover base ctor runs its own SearchList(resourceID, GameModel), // and SearchList CRASHES (engine @0x414938) when the id isn't in // the top-level directory (member resources resolve only through // their family head). The streamed explosionModelFile is a small // INDEX (SRM6 reads 17) into a model list -- decoding it into the // real projectile family is the projectile-model brick. Until // then the round spawns on the OWNER's family (its GameModel // member provably streams every boot -- the mech model params); // our staged flight integration reads none of the Mover mass/drag. // { // // DEV one-shot (BT_MODEL_DUMP=1): raw-dump the mech's ModelList // stream -- the decode bench for the explosionModelFile INDEX // (SRM6 reads 17). The owner's make resourceID IS the // ModelList record (CreatePlayerVehicle passes // mech_res->resourceID). // { static int dumped = 0; if (!dumped && getenv("BT_MODEL_DUMP")) { dumped = 1; ResourceDescription *ml = application->GetResourceFile()-> FindResourceDescription(owner->GetResourceID()); if (ml != NULL) { ml->Lock(); unsigned char *raw = (unsigned char *)ml->resourceAddress; int size = ml->resourceSize; DEBUG_STREAM << "[mdl] list res id=" << ml->resourceID << " type=" << ml->resourceType << " size=" << size << " count=" << *(int *)raw << endl; int limit = (size < 640) ? size : 640; for (int bb = 0; bb < limit; bb += 16) { DEBUG_STREAM << "[mdl] +" << bb << ":"; int cc; for (cc = bb; cc < bb + 16 && cc < limit; ++cc) { DEBUG_STREAM << " " << (unsigned)raw[cc]; } DEBUG_STREAM << " "; for (cc = bb; cc < bb + 16 && cc < limit; ++cc) { char ch = (raw[cc] >= 32 && raw[cc] < 127) ? (char)raw[cc] : '.'; DEBUG_STREAM << ch; } DEBUG_STREAM << endl; } // // Identify the members + the streamed // explosionModelFile value by name/type. // int nn = *(int *)raw; if (nn > 0 && nn < 40) { int *ids = (int *)(raw + 4); for (int ee = 0; ee < nn; ++ee) { ResourceDescription *mm = application->GetResourceFile()-> FindResourceDescription(ids[ee]); DEBUG_STREAM << "[mdl] member[" << ee << "] id=" << ids[ee]; if (mm != NULL) { DEBUG_STREAM << " type=" << mm->resourceType << " name=" << mm->resourceName; } DEBUG_STREAM << endl; } } { ResourceDescription *ex = application->GetResourceFile()-> FindResourceDescription( explosionResourceID); DEBUG_STREAM << "[mdl] explosionModelFile=" << explosionResourceID; if (ex != NULL) { DEBUG_STREAM << " -> type=" << ex->resourceType << " name=" << ex->resourceName << " size=" << ex->resourceSize; } else { DEBUG_STREAM << " -> NOT IN DIRECTORY"; } DEBUG_STREAM << endl; // // Walk the explosion family's OWN members. // if (ex != NULL && ex->resourceType == ResourceDescription::ModelListResourceType) { ex->Lock(); int *xr = (int *)ex->resourceAddress; int xn = xr[0]; DEBUG_STREAM << "[mdl] '" << ex->resourceName << "' members=" << xn << endl; for (int xx = 0; xx < xn && xx < 16; ++xx) { ResourceDescription *xm = application->GetResourceFile()-> FindResourceDescription(xr[1 + xx]); DEBUG_STREAM << "[mdl] [" << xx << "] id=" << xr[1 + xx]; if (xm != NULL) { DEBUG_STREAM << " type=" << xm->resourceType << " name=" << xm->resourceName << " size=" << xm->resourceSize; } DEBUG_STREAM << endl; // // The GameModel member: dump as floats // (the missile model record -- lifetime // @+0x44, thrust @+0x48, mode @+0x50). // if (xm != NULL && xm->resourceType == ResourceDescription::GameModelResourceType) { xm->Lock(); float *fv = (float *)xm->resourceAddress; int nf = xm->resourceSize / 4; for (int ff = 0; ff < nf; ++ff) { DEBUG_STREAM << "[mdl] +" << (ff * 4) << " f=" << fv[ff] << " i=" << ((int *)fv)[ff] << endl; } xm->Unlock(); } } ex->Unlock(); } } DEBUG_STREAM << flush; ml->Unlock(); } } } // // Resolve the round's resource FAMILY. The streamed // explosionModelFile IS a family id after all (SRM6 = 17 = // 'mslhit': VideoModel + AudioStreamList + an 8-byte GameModel // {int 498, maxTimeOfFlight 5.0}). Guard: only use it when the // family really carries a GameModel MEMBER -- the Mover base // ctor SearchLists for one and CRASHES on a missing member // (the walk runs off the family list). Fallback = the owner's // family (GameModel provably present). // ResourceDescription::ResourceID round_family = owner->GetResourceID(); if (explosionResourceID != ResourceDescription::NullResourceID) { ResourceDescription *family = application->GetResourceFile()-> FindResourceDescription(explosionResourceID); if (family != NULL && family->resourceType == ResourceDescription::ModelListResourceType) { family->Lock(); int *members = (int *)family->resourceAddress; int member_count = members[0]; Logical family_complete = True; Logical has_game_model = False; for (int mi = 0; mi < member_count; ++mi) { ResourceDescription *member = application->GetResourceFile()-> FindResourceDescription(members[1 + mi]); if (member == NULL) { // // A member id absent from the directory makes // the engine SearchList walk CRASH -- this // family is unusable for the Mover make. // family_complete = False; } else if (member->resourceType == ResourceDescription::GameModelResourceType) { has_game_model = True; } } if (family_complete && has_game_model) { round_family = explosionResourceID; // // Keep the family RESIDENT (no Unlock): dropping a // first-touch family back to lockCount 0 left the // engine's re-Lock inside the Mover ctor reading a // dead list (SearchList returned NULL on members we // just resolved). The binary's launcher holds its // projectile model locked for the mission anyway. // } else { family->Unlock(); } } } HostManager *host_manager = application->GetHostManager(); Check(host_manager); // // The round leaves FROM THE GUN: the mount segment's world // frame (GetMuzzlePoint @004b9948 -- follows the torso twist). // The rotation stays the mech's (the missile aims by velocity). // Origin muzzle = owner->localOrigin; GetMuzzlePoint(muzzle.linearPosition); // // NoCollisionVolumeFlag is LOAD-BEARING: collision volumes are // the Mover DEFAULT (IsCollisionVolume = the flag NOT set), and // the ctor then demands a BoxedSolidStream member -- which a // projectile family doesn't carry (SearchList NULL -> // Lock() page fault). The round's collision is the world // query / proximity fuse, not a boxed solid. // Mover::MakeMessage spawn_round( Entity::MakeMessageID, sizeof(Mover::MakeMessage), EntityID(host_manager->GetLocalHostID()), (Entity::ClassID)MissileClassID, EntityID::Null, round_family, Entity::DefaultFlags | Mover::NoCollisionVolumeFlag, muzzle, Motion::Identity, Motion::Identity ); Missile *round = new Missile(&spawn_round); Register_Object(round); round->Launch( owner, owner->GetTargetEntity(), damageData, kMissileLaunchSpeed ); } } if (getenv("BT_MECH_LOG")) { DEBUG_STREAM << "[fire] '" << GetName() << "' FIRED (salvo of " << missileCount << ", recharge=" << rechargeRate << "s heat+=" << heatCostToFire << ")" << endl << flush; } } // // Model-load-time construction. Not yet reconstructed. // Logical MissileLauncher::CreateStreamedSubsystem( ResourceFile *, NotationFile *, const char *, const char *, SubsystemResource *, NotationFile *, const ResourceDirectories *, int ) { Fail("MissileLauncher::CreateStreamedSubsystem -- mislanch.cpp not yet reconstructed"); return False; }