//===========================================================================// // File: messmgr.cpp // // Project: BattleTech Brick: Mech subsystems // // Contents: SubsystemMessageManager -- the per-mech damage/explosion hub // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// #include #pragma hdrstop #if !defined(MESSMGR_HPP) # include #endif #if !defined(MECH_HPP) # include #endif #if !defined(MECHWEAP_HPP) # include // ExplosionResourceIDOf (weapon @0x3e4) #endif #if !defined(EXPLODE_HPP) # include // Explosion::MakeMessage #endif #if !defined(APP_HPP) # include // application->Post / GetRegistry / priorities #endif #if !defined(REGISTRY_HPP) # include // complete Registry type for the Post upcast #endif Derivation SubsystemMessageManager::ClassDerivations( Subsystem::ClassDerivations, "SubsystemMessageManager" ); SubsystemMessageManager::SharedData SubsystemMessageManager::DefaultData( SubsystemMessageManager::ClassDerivations, Subsystem::MessageHandlers, Subsystem::AttributeIndex, Subsystem::StateCount ); SubsystemMessageManager::SubsystemMessageManager( Mech *owner, int subsystem_ID, SubsystemResource *sub_res, SharedData &shared_data ): Subsystem(owner, subsystem_ID, sub_res, shared_data), weaponExplosions(NULL, True), // @0049bca4: chain ctor (0, 1) -- // UNIQUE-keyed (one plug per explosion // resource; the consolidator also // Find()s before adding) damageInformation(NULL, False) // (0, 0) -- duplicate keys allowed { Check(owner); Check_Pointer(sub_res); // // [T3] The binary ctor also allocates a 0x160-byte engine-internal // helper object at this+0xE0 (FUN_0041de1c(obj, this, 0); released in // the dtor by virtual op 3). It is not declared in the surviving // MESSMGR.HPP and nothing in the reconstructed damage path touches it; // STAGED until its class is identified. // // // The per-frame Performance (binary @0049bca4 copies the member-pointer // triple @0050b6fc..0050b704 == &ConsolidateAndSendDamage). // SetPerformance(&SubsystemMessageManager::ConsolidateAndSendDamage); // // The two streamed resource fields (res+0x30 / res+0x34). // terrainHitExplosionID = sub_res->terrainHitExplosionID; rendererCompensateTime = sub_res->rendererCompensateTime; // // The common-damage record starts empty (first hit of a frame fills it). // commonDamageInformation.entityHit = NULL; commonDamageInformation.damageZoneIndex = -1; commonDamageInformation.impactPoint = Point3D::Identity; Check_Fpu(); } SubsystemMessageManager::~SubsystemMessageManager() { } Logical SubsystemMessageManager::TestInstance() const { return IsDerivedFrom(ClassDerivations); } // //############################################################################# // AddDamageMessage -- binary @0049b6d8 (172 bytes), verified against the raw // decomp (part_012.c). // // Called by the damage paths while a frame runs: the FIRST hit of the frame // wins each field of the common record (entity, impact point, zone), and // every message queues a DamageInformation plug into the damage chain KEYED // BY ITS DAMAGE AMOUNT (the chain key is the Scalar the consolidator later // reads back with GetValue()). //############################################################################# // void SubsystemMessageManager::AddDamageMessage( Entity *damaged_entity, Entity__TakeDamageMessage *message ) { Check(this); Check(damaged_entity); Check_Pointer(message); if (commonDamageInformation.entityHit == NULL) { commonDamageInformation.entityHit = damaged_entity; } // // The binary's origin test is an epsilon compare against the zero point // (FUN_004084fc(point, origin, 0.0001)). // if (Close_Enough(commonDamageInformation.impactPoint, Point3D::Identity, 0.0001f)) { commonDamageInformation.impactPoint = message->damageData.impactPoint; } if (commonDamageInformation.damageZoneIndex == -1) { commonDamageInformation.damageZoneIndex = message->damageZone; } DamageInformation *info = new DamageInformation; Register_Object(info); info->damageType = message->damageData.damageType; info->subsystemID = message->inflictingSubsystemID; damageInformation.AddValue(info, message->damageData.damageAmount); } // //############################################################################# // ConsolidateAndSendDamage -- binary @0049b784 (796 bytes), the per-frame // Performance, verified against the raw decomp. // // If any damage queued this frame: build ONE TakeDamageStreamMessage for the // common hit entity (impact point, zone, shooter = the owning mech), append // {count, [damageType, damageAmount, subsystemID]...} through a dynamic // message -- the wire format is fixed by the ENGINE'S OWN PARSER // (Entity::TakeDamageStreamMessageHandler, entity.cpp: count first, then the // triple per record) -- bundle each firing weapon's explosion resource into // the explosion queue (dedup'd), Dispatch the stream at the hit entity, and // flush the queued explosions. Always: purge the damage chain and reset the // common record for the next frame. //############################################################################# // void SubsystemMessageManager::ConsolidateAndSendDamage(Scalar) { Check(this); Mech *owner = GetEntity(); Check(owner); VChainIteratorOf damageWalk(&damageInformation); damageWalk.First(); if (damageWalk.GetCurrent() != NULL) { // // The engine parser reads the record count first, so count the // chain up front. // int entry_count = 0; for (damageWalk.First(); damageWalk.GetCurrent() != NULL; damageWalk.Next()) { ++entry_count; } // // The consolidated stream (binary: Receiver__DynamicMessage ctor // FUN_00419e18(TakeDamageStreamMessageID == 0x13, sizeof == 0x34), // header fields written in place at +0x1c/+0x28/+0x2c). // Receiver::DynamicMessage stream_message( Entity::TakeDamageStreamMessageID, sizeof(Entity::TakeDamageStreamMessage) ); { Entity::TakeDamageStreamMessage *header = (Entity::TakeDamageStreamMessage *) stream_message.GetMessagePointer(); header->impactPoint = commonDamageInformation.impactPoint; header->damageZoneIndex = commonDamageInformation.damageZoneIndex; header->shootingEntity = owner->GetEntityID(); } stream_message << entry_count; for (damageWalk.First(); damageWalk.GetCurrent() != NULL; damageWalk.Next()) { DamageInformation *info = damageWalk.GetCurrent(); stream_message << (int)info->damageType << (Scalar)damageWalk.GetValue() << (int)info->subsystemID; // // Bundle the FIRING weapon's explosion (binary: blind read of // roster[subsystemID]+0x3e4). [T1] The binary neither range- // checks the id nor verifies the subsystem is a weapon; damage // with an unthreaded id (-1) or a non-weapon inflicter would // read garbage there. Guarded here -- a guarded miss queues // nothing, which is also what a Null resource does. // Subsystem *firing = NULL; if ( info->subsystemID >= 0 && info->subsystemID < owner->GetSubsystemCount() ) { firing = owner->GetSubsystem(info->subsystemID); } if ( firing != NULL && firing->IsDerivedFrom(MechWeapon::ClassDerivations) ) { ResourceDescription::ResourceID explosion_ID = ((MechWeapon *)firing)->ExplosionResourceIDOf(); if ( explosion_ID != ResourceDescription::NullResourceID && weaponExplosions.Find(explosion_ID) == NULL ) { ResourceIDPlug *plug = new ResourceIDPlug(explosion_ID); Register_Object(plug); weaponExplosions.AddValue(plug, explosion_ID); } } } // // [T3] The binary computes a terrain-hit flag here: // entityHit->IsDerivedFrom(). The class at // that BSS address is UNRESOLVED (no static-ctor reference survives // in the decomp parts) -- but the flag is PROVABLY INERT: // CreateWeaponExplosions (@0049baa0) never reads its first // argument, and terrainHitExplosionID is never consumed anywhere in // the shipped cluster. A declared, resourced, and abandoned 1995 // feature; False preserves shipped behavior exactly. // Logical terrain_hit = False; if (commonDamageInformation.entityHit != NULL) { CreateWeaponExplosions( terrain_hit, commonDamageInformation.entityHit->GetEntityID(), commonDamageInformation.impactPoint ); commonDamageInformation.entityHit->Dispatch( stream_message.GetMessagePointer()); } } // // Teardown every frame (binary FUN_00417858(iter, 1)): unregister and // delete the consumed plugs, then reset the common record. // damageWalk.First(); damageWalk.DeletePlugs(); commonDamageInformation.entityHit = NULL; commonDamageInformation.damageZoneIndex = -1; commonDamageInformation.impactPoint = Point3D::Identity; Check_Fpu(); } // //############################################################################# // CreateWeaponExplosions -- binary @0049baa0 (516 bytes), verified against // the raw decomp. The authentic signature is the surviving MESSMGR.HPP's; // note terrain_hit is never read by the shipped body (see the [T3] note in // the consolidator). // // Flush the queued explosion resources: for each, post an // Explosion::MakeMessage at the Registry -- the byte-for-byte template from // the binary: MakeMessageID (3), sizeof 0x5c, ExplosionClassID (0x31 -- the // VDATA enum ordinal matches the binary exactly), no owner, the queued // resource, instance flags 0x100, an identity-rotation Origin at the impact // point, the hit entity, and the owning mech as creator. Deliveries stagger // 0.1s apart (the first at now+0.1) so several impacts in one frame read as // separate blasts. Then purge the queue. //############################################################################# // void SubsystemMessageManager::CreateWeaponExplosions( Logical /*terrain_hit*/, EntityID entity_hit, Point3D explode_position ) { Check(this); Mech *owner = GetEntity(); Check(owner); VChainIteratorOf explosionWalk(&weaponExplosions); Origin explosion_origin(Origin::Identity); explosion_origin.linearPosition = explode_position; Scalar time_step = 0.0f; for ( explosionWalk.First(); explosionWalk.GetCurrent() != NULL; explosionWalk.Next() ) { ResourceDescription::ResourceID explosion_ID = explosionWalk.GetValue(); time_step += 0.1f; Time when = Now(); when += time_step; Explosion::MakeMessage make_message( Entity::MakeMessageID, sizeof(Explosion::MakeMessage), RegisteredClass::ExplosionClassID, EntityID(), explosion_ID, 0x100, explosion_origin, entity_hit, owner->GetEntityID() ); Check(application); application->Post( CreationEventPriority, application->GetRegistry(), &make_message, when ); } explosionWalk.First(); explosionWalk.DeletePlugs(); Check_Fpu(); } // //############################################################################# // CreateStreamedSubsystem -- binary @0049be10 (324 bytes). Chains the base // Subsystem record, stamps classID 0x0BD3 / size 0x38, resolves the // REQUIRED "TerrainHitExplosion" model family, reads the REQUIRED // "RendererCompensateTime". (The runtime never consumes either field -- // see the inert terrain-hit note above -- but the authored stream carries // both, so the converter writes both.) //############################################################################# // Logical SubsystemMessageManager::CreateStreamedSubsystem( ResourceFile *resource_file, NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories ) { if ( !Subsystem::CreateStreamedSubsystem( model_file, model_name, subsystem_name, subsystem_resource, subsystem_file, directories ) ) { return False; } subsystem_resource->classID = RegisteredClass::SubsystemMessageManagerClassID; subsystem_resource->subsystemModelSize = 0x38; const char *terrain_hit_name; if ( !subsystem_file->GetEntry(subsystem_name, "TerrainHitExplosion", &terrain_hit_name) ) { DEBUG_STREAM << subsystem_name << " missing TerrainHitExplosion!" << endl; return False; } ResourceDescription *description = resource_file->FindResourceDescription( terrain_hit_name, ResourceDescription::ModelListResourceType); if (description == NULL) { DEBUG_STREAM << subsystem_name << " cannot find " << terrain_hit_name << " in resource file!" << endl; return False; } subsystem_resource->terrainHitExplosionID = description->resourceID; if ( !subsystem_file->GetEntry(subsystem_name, "RendererCompensateTime", &subsystem_resource->rendererCompensateTime) ) { DEBUG_STREAM << subsystem_name << " missing RendererCompensateTime" << endl; return False; } Check_Fpu(); return True; }