Files
BT411/engine/MUNGA/ENTITY.cpp
T
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.

Layout:
  engine/   MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
            work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
            models) + image codec; the minimal rp/ headers the audio HAL needs
  game/     reconstructed BT logic + surviving-original BT source + fwd shims
            + WinMain launcher
  content/  full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
  docs/     format specs + reconstruction ledgers
  reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
  tools/    MP console emulator + map/resource scanners

One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:03:40 -05:00

1584 lines
38 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "entity.h"
#include "interest.h"
#include "exptbl.h"
#include "hostmgr.h"
#include "player.h"
#include "audio.h"
#include "fileutil.h"
#include "registry.h"
#include "event.h"
#include "app.h"
#include "nttmgr.h"
#include "subsystm.h"
#include "namelist.h"
#include "notation.h"
#if defined(TRACE_EXECUTE_ENTITY)
static BitTrace Execute_Entity("Execute Entity");
#define SET_EXECUTE_ENTITY() Execute_Entity.Set()
#define CLEAR_EXECUTE_ENTITY() Execute_Entity.Clear()
#else
#define SET_EXECUTE_ENTITY()
#define CLEAR_EXECUTE_ENTITY()
#endif
#if defined(TRACE_PERFORM_ENTITY)
static BitTrace Perform_Entity("Perform Entity");
#define SET_PERFORM_ENTITY() Perform_Entity.Set()
#define CLEAR_PERFORM_ENTITY() Perform_Entity.Clear()
#else
#define SET_PERFORM_ENTITY()
#define CLEAR_PERFORM_ENTITY()
#endif
#if defined(TRACE_PERFORM_SUBSYSTEMS)
static BitTrace Perform_Subsystems("Perform Subsystems");
#define SET_PERFORM_SUBSYSTEMS() Perform_Subsystems.Set()
#define CLEAR_PERFORM_SUBSYSTEMS() Perform_Subsystems.Clear()
#else
#define SET_PERFORM_SUBSYSTEMS()
#define CLEAR_PERFORM_SUBSYSTEMS()
#endif
//#############################################################################
//############################## Entity #################################
//#############################################################################
//#############################################################################
// Virtual Data support
//
Derivation* Entity::GetClassDerivations()
{
static Derivation classDerivations(Simulation::GetClassDerivations(), "Entity");
return &classDerivations;
}
Entity::SharedData
Entity::DefaultData(
Entity::GetClassDerivations(),
Entity::GetMessageHandlers(),
Entity::GetAttributeIndex(),
Entity::StateCount,
Entity::Make
);
//#############################################################################
// Message Support
//
const Receiver::HandlerEntry
Entity::MessageHandlerEntries[]=
{
{
Entity::MakeMessageID,
"Make",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::MakeReadyMessageID,
"MakeReady",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::RemakeEntityMessageID,
"RemakeEntity",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::RemakeReadyMessageID,
"RemakeReady",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::RemakeCompleteMessageID,
"RemakeComplete",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::RemakeIncompleteMessageID,
"RemakeIncomplete",
(Entity::Handler)&Entity::DefaultMessageHandler
},
MESSAGE_ENTRY(Entity, DestroyEntity),
{
Entity::TransferEntityMessageID,
"TransferEntity",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::TransferCompleteMessageID,
"TransferComplete",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::TransferIncompleteMessageID,
"TransferIncomplete",
(Entity::Handler)&Entity::DefaultMessageHandler
},
MESSAGE_ENTRY(Entity, BecomeInteresting),
MESSAGE_ENTRY(Entity, BecomeUninteresting),
MESSAGE_ENTRY(Entity, Update),
{
Entity::SubscribeReplicantMessageID,
"SubscribeReplicant",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::UnsubscribeReplicantMessageID,
"UnsubscribeReplicant",
(Entity::Handler)&Entity::DefaultMessageHandler
},
MESSAGE_ENTRY(Entity, TakeDamage),
MESSAGE_ENTRY(Entity, PlayerLink),
MESSAGE_ENTRY(Entity, TakeDamageStream)
};
Entity::MessageHandlerSet& Entity::GetMessageHandlers()
{
static Entity::MessageHandlerSet messageHandlers(ELEMENTS(Entity::MessageHandlerEntries), Entity::MessageHandlerEntries, Receiver::GetMessageHandlers());
return messageHandlers;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::Receive(Event* event)
{
Check(this);
Check(event);
//
//-------------------------------------------------------------------------
// If the entity is being transferred, post the message to the new owner of
// the entity
//-------------------------------------------------------------------------
//
//
//--------------------------------------------------------------------------
// If the object is not valid, filter the message to determine which ones to
// defer and which ones to immediately process
//--------------------------------------------------------------------------
//
if (!IsValid())
{
//switch case commented out by Ryan Bunker on 1/6/07
//switch (event->messageToSend->messageID)
{
//
//----------------------------------------------------------------------
// The messages which should be allowed through should just break out of
// this switch
//----------------------------------------------------------------------
//
//default:
//
//-------------------
// Defer the messages
//-------------------
//
event->Defer();
return;
}
}
//
//------------------------------------------------
// Go ahead and dispatch the message to the entity
//------------------------------------------------
//
Receiver::Receive(event);
#if defined(USE_FPU_ANALYSIS)
if (!Fpu_Ok())
{
Registry *registry = application->GetRegistry();
Receiver::SharedData *shared_data =
registry->GetStaticData(GetClassID());
Derivation *derivation = shared_data->derivedClasses;
Receiver::MessageHandlerSet *handlers =
shared_data->activeMessageHandlers;
DEBUG_STREAM << derivation->className << ',' << GetInstance() << endl << std::flush;
DEBUG_STREAM << handlers->GetName(event->messageToSend->messageID)
<< endl;
Fail("Event corrupted FPU!");
}
#else
Check_Fpu();
#endif
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::Dispatch(Receiver::Message* incoming)
{
Message *message = (Message*) incoming;
Check(this);
Check(message);
//
// Call Receiver if it is not entity aware!!!!!
//
if (message->messageID < Receiver::NextMessageID)
{
Receiver::Dispatch(message);
}
//
//-----------------------------------------
// Initialize the entity information fields
//-----------------------------------------
//
message->entityID = entityID;
message->interestZoneID = interestZoneID;
//
//-------------------------------------------------------------------
// If this is a replicant instance, reroute the message to the master
//-------------------------------------------------------------------
//
if (GetInstance() == ReplicantInstance)
{
application->SendMessage(
ownerID,
EntityManager::EntityManagerClientID,
message
);
}
//
//--------------------------------------------------------
// If this entity is invalid, post the message as an event
//--------------------------------------------------------
//
else if (!IsValid())
{
// ECH 1/28/96 - application->Post(DefaultEventPriority, this, message);
application->Post(EntityInvalidEventPriority, this, message);
}
//
//--------------------------------------------
// Otherwise, go ahead and receive the message
//--------------------------------------------
//
else
{
Receiver::Receive(message);
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::DispatchToReplicants(Message *what)
{
Check(this);
Check(what);
what->entityID = entityID;
what->interestZoneID = interestZoneID;
Check(application);
InterestManager *int_mgr = application->GetInterestManager();
Check(int_mgr);
int_mgr->EntityBroadcastToReplicants(this, what);
Check_Fpu();
}
//#############################################################################
// Attribute Support
//
const Entity::IndexEntry
Entity::AttributePointers[]=
{
ATTRIBUTE_ENTRY(Entity, LocalToWorld, localToWorld),
ATTRIBUTE_ENTRY(Entity, LocalOrigin, localOrigin),
ATTRIBUTE_ENTRY(Entity, DamageZoneCount, damageZoneCount),
ATTRIBUTE_ENTRY(Entity, DamageZones, damageZones)
};
Entity::AttributeIndexSet& Entity::GetAttributeIndex()
{
static Entity::AttributeIndexSet attributeIndex(ELEMENTS(Entity::AttributePointers),
Entity::AttributePointers,
Simulation::GetAttributeIndex()
);
return attributeIndex;
}
//#############################################################################
// Model Support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::WriteUpdateRecord(
Simulation::UpdateRecord *record,
int update_model
)
{
Check(this);
Check_Pointer(record);
switch (update_model)
{
case DefaultUpdateModelBit:
{
Simulation::WriteUpdateRecord(record, update_model);
//
//----------------------------
// Write out the record update
//----------------------------
//
UpdateRecord *update = (UpdateRecord*)record;
update->localOrigin = localOrigin;
updateOrigin = localOrigin;
update->recordLength = sizeof(*update);
}
break;
case DamageZoneUpdateModelBit:
record->timeStamp = lastPerformance;
record->recordID = (Word)update_model;
WriteDamageUpdateRecord(record);
break;
default:
Simulation::WriteUpdateRecord(record, update_model);
break;
}
record->subsystemID = (Word)(EntitySubsystemID + 1);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::UpdateMessageHandler(UpdateMessage *message)
{
Check(this);
Check(message);
//
//------------------------------------------------------
// Position to the beginning of the update record blocks
//------------------------------------------------------
//
MemoryStream stream(message, message->messageLength, sizeof(*message));
//
//------------------------------------------------------------------------
// Step through each block until there are no more remaining, and send the
// update out the the simulation indicated by the subsystemID
//------------------------------------------------------------------------
//
while (stream.GetBytesRemaining())
{
Simulation::UpdateRecord *update =
(Simulation::UpdateRecord*)stream.GetPointer();
Check_Pointer(update);
Simulation *simulation = GetSimulation((int)update->subsystemID-1);
Check(simulation);
simulation->ReadUpdateRecord(update);
stream.AdvancePointer(update->recordLength);
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::ReadUpdateRecord(Simulation::UpdateRecord *record)
{
Check(this);
Check_Pointer(record);
switch(record->recordID)
{
case DefaultUpdateModelBit:
{
//
//-----------------------
// Read the record update
//-----------------------
//
UpdateRecord *update = (UpdateRecord*)record;
updateOrigin = update->localOrigin;
//
//------------------------------------------------------------------
// If we aren't able to run yet, we must update local origin as well
//------------------------------------------------------------------
//
Check(application);
int app_state = application->GetApplicationState();
if (
app_state < Application::RunningMission
|| app_state == Application::CreatingMission
)
{
localOrigin = updateOrigin;
localToWorld = localOrigin;
}
Simulation::ReadUpdateRecord(record);
}
break;
case DamageZoneUpdateModelBit:
ReadDamageUpdateRecord(record);
break;
default:
Simulation::ReadUpdateRecord(record);
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::ReadDamageUpdateRecord(Simulation::UpdateRecord *update_record)
{
//
//------------------------------------------------------------------------
// Step through each block until there are no more remaining, and send the
// update out the the damageZone indicated by the damageZoneIndex
//------------------------------------------------------------------------
//
MemoryStream stream(
update_record,
update_record->recordLength,
sizeof(*update_record)
);
while (stream.GetBytesRemaining())
{
DamageZone::UpdateRecord *damage_zone_record =
(DamageZone::UpdateRecord*)stream.GetPointer();
int damage_zone_index = damage_zone_record->damageZoneIndex;
damageZones[damage_zone_index]->ReadUpdateRecord(damage_zone_record);
stream.AdvancePointer(damage_zone_record->recordLength);
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#define MAX_UPDATE_LENGTH 1400
static long Update_Buffer[MAX_UPDATE_LENGTH >> 2];
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::WriteDamageUpdateRecord(Simulation::UpdateRecord *update_record)
{
Check(this);
Check_Pointer(update_record);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Make a memory stream this size of a network packet
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MemoryStream stream(
update_record,
sizeof(Update_Buffer) - ((char*)update_record - (char*)Update_Buffer),
sizeof(*update_record)
);
DamageZone::UpdateRecord *record_start =
(DamageZone::UpdateRecord*)stream.GetPointer();
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Update Every DamageZone For NOW
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
for(int ii=0;ii<damageZoneCount;++ii)
{
DamageZone::UpdateRecord *damage_zone_update =
(DamageZone::UpdateRecord*)stream.GetPointer();
damageZones[ii]->WriteUpdateRecord(damage_zone_update);
stream.AdvancePointer(damage_zone_update->recordLength);
}
//
//-----------------------------------------------
// Check to see if an update message is necessary
//-----------------------------------------------
//
if (stream.GetPointer() != record_start)
{
Simulation::UpdateRecord *damage_update =
(Simulation::UpdateRecord*)update_record;
damage_update->recordLength =
(char*)stream.GetPointer() - (char*)update_record;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Entity::UpdateMessage*
Entity::Execute(const Time& till)
{
SET_EXECUTE_ENTITY();
Check(this);
Check(&till);
MemoryStream
stream(Update_Buffer, sizeof(Update_Buffer), sizeof(UpdateMessage));
char *record_start = (char*)stream.GetPointer();
//
//-----------------------------------------------------------------------
// Check to see if the application will let us run. If We are starting a
// mission, only the player class objects can execute
//-----------------------------------------------------------------------
//
Check(application);
// BT bring-up trace (env BT_NET_TRACE): which Execute branch high-ID entities take.
if (getenv("BT_NET_TRACE") && (int)GetEntityID() >= 20)
{
static int s_execTrace = 0;
if ((s_execTrace++ % 240) == 0)
{
DEBUG_STREAM << "[ent-exec] " << GetEntityID()
<< " state=" << (int)application->GetApplicationState()
<< " preRun=" << (int)IsPreRunnable()
<< " inst=" << (int)GetInstance() << "\n" << std::flush;
}
}
if (
application->GetApplicationState() == Application::RunningMission
|| application->GetApplicationState() == Application::EndingMission
|| IsPreRunnable()
)
{
PerformAndWatch(till, &stream);
}
else
{
WriteSimulationUpdate(&stream);
}
#if defined(USE_FPU_ANALYSIS)
if (!Fpu_Ok())
{
Registry *registry = application->GetRegistry();
Receiver::SharedData *shared_data =
registry->GetStaticData(GetClassID());
Derivation *derivation = shared_data->derivedClasses;
DEBUG_STREAM << derivation->className << ',' << GetInstance() << endl << std::flush;
Fail("Simulation corrupted FPU!");
}
#endif
//
//-----------------------------------------------
// Check to see if an update message is necessary
//-----------------------------------------------
//
if (stream.GetPointer() != record_start)
{
UpdateMessage *update = (UpdateMessage*)Update_Buffer;
update->messageID = UpdateMessageID;
update->messageLength = (char*)stream.GetPointer() - (char*)Update_Buffer;
update->messageFlags = 0;
CLEAR_EXECUTE_ENTITY();
return update;
}
//
//-------------------------------------
// Otherwise, return NULL for no update
//-------------------------------------
//
Check_Fpu();
CLEAR_EXECUTE_ENTITY();
return NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Simulation*
Entity::GetSimulation(int index)
{
Check(this);
if (index == EntitySubsystemID)
{
return this;
}
Check_Fpu();
if ((unsigned)index < subsystemCount)
{
return subsystemArray[index];
}
else
{
return NULL;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Subsystem*
Entity::FindSubsystem(const char* name)
{
Check(this);
Check_Pointer(name);
for (int i=0; i<subsystemCount; ++i)
{
if (subsystemArray[i])
{
Check(subsystemArray[i]);
if (!stricmp(subsystemArray[i]->GetName(), name))
{
return subsystemArray[i];
}
}
}
Check_Fpu();
return NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#define MAX_SUB_FILE_NAME (256)
int
Entity::FindSubsytemID
(
#if DEBUG_LEVEL>0
const char *model_name,
const char *subsystem_name
#else
const char *,
const char *subsystem_name
#endif
)
{
Check_Pointer(model_name);
Check_Pointer(subsystem_name);
int subsytem_id = EntitySubsystemID;
int sub_count = 0;
CString sub_file_string;
CString sub_directory_string("models\\");
CString model_name_string("model_name");
CString sub_post_string(".sub");
sub_file_string =
sub_directory_string + model_name_string + sub_post_string;
NotationFile sub_file(sub_file_string);
Check(&sub_file);
NameList *page_list = sub_file.MakePageList();
Register_Object(page_list);
NameList::Entry *entry=page_list->GetFirstEntry();
while (entry != NULL)
{
Check(entry);
Check_Pointer(entry->GetName());
if (!stricmp(subsystem_name, entry->GetName()))
{
subsytem_id = sub_count;
break;
}
++sub_count;
entry = entry->GetNextEntry();
}
Unregister_Object(page_list);
delete page_list;
return subsytem_id;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::PerformAndWatch(
const Time &till,
MemoryStream *update_stream
)
{
Check(this);
Check(&till);
//
//-----------------------------------------
// Execute all the subsystems, then ourself
//-----------------------------------------
//
SET_PERFORM_SUBSYSTEMS();
if (subsystemArray)
{
Check_Pointer(subsystemArray);
//
//---------------------------------------------------
// If this is not a replicant, execute all subsystems
//---------------------------------------------------
//
if (GetInstance() != ReplicantInstance)
{
for (int i=0; i<subsystemCount; ++i)
{
if (subsystemArray[i])
{
Subsystem *subsystem = subsystemArray[i];
Check(subsystem);
if (subsystem->IsNonReplicantExecutable())
{
subsystem->PerformAndWatch(till, update_stream);
#if defined(USE_FPU_ANALYSIS)
if (!Fpu_Ok())
{
Registry *registry = application->GetRegistry();
Receiver::SharedData *shared_data =
registry->GetStaticData(GetClassID());
Derivation *derivation = shared_data->derivedClasses;
DEBUG_STREAM << derivation->className
<< "\nExecuting subsystem[" << i << "], "
<< subsystem->GetName() << endl;
Fail("Simulation corrupted FPU!");
}
#endif
}
}
}
}
//
//--------------------------------------------------------------------
// If this is a replicant, execute only those subsystems which need to
//--------------------------------------------------------------------
//
else
{
for (int i=0; i<subsystemCount; ++i)
{
if (subsystemArray[i])
{
Subsystem *subsystem = subsystemArray[i];
Check(subsystem);
if (subsystem->IsReplicantExecutable())
{
subsystem->PerformAndWatch(till, update_stream);
#if defined(USE_FPU_ANALYSIS)
if (!Fpu_Ok())
{
Registry *registry = application->GetRegistry();
Receiver::SharedData *shared_data =
registry->GetStaticData(GetClassID());
Derivation *derivation = shared_data->derivedClasses;
DEBUG_STREAM << derivation->className
<< " replicant\nExecuting subsystem[" << i << "], "
<< subsystem->GetName() << endl;
Fail("Simulation corrupted FPU!");
}
#endif
}
}
}
}
}
CLEAR_PERFORM_SUBSYSTEMS();
SET_PERFORM_ENTITY();
Simulation::PerformAndWatch(till, update_stream);
Check_Fpu();
CLEAR_PERFORM_ENTITY();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::PlayerLinkMessageHandler(PlayerLinkMessage *message)
{
Check(application);
HostManager *host = application->GetHostManager();
Check(host);
playerLink =(Player*) host->GetEntityPointer(message->playerID);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::TakeDamageStreamMessageHandler(TakeDamageStreamMessage *message)
{
Check(this);
Check(message);
//
//-------------------------------------------------
// Extract the common information out of the stream
//-------------------------------------------------
//
Damage damage_data;
EntityID shooting_entity = message->shootingEntity;
int damage_zone_index = message->damageZoneIndex;
damage_data.impactPoint = message->impactPoint;
//
//---------------------------------------------------
// Figure out how many damage messages there are here
//---------------------------------------------------
//
DynamicMessage damage_stream(
message,
message->messageLength,
sizeof(*message)
);
int entry_count;
damage_stream >> entry_count;
for (int i=0; i<entry_count; ++i)
{
//
//-------------------------------------------------
// Read in damageType, damageAmount and subsystemID
//-------------------------------------------------
//
damage_stream >> damage_data.damageType >> damage_data.damageAmount;
int subsystem;
damage_stream >> subsystem;
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Apply the damage to the appropriate damageZone
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
TakeDamageMessage
message(
TakeDamageMessageID,
sizeof(TakeDamageMessage),
shooting_entity,
damage_zone_index,
damage_data,
subsystem
);
Dispatch(&message);
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::TakeDamageMessageHandler(TakeDamageMessage *message)
{
//
//-------------------------------------
// YOU MUST CHECK FOR -1 DamageZone!!!!
//-------------------------------------
//
if (!damageZones || message->damageZone == -1)
{
return;
}
//
//----------------------------------------------------------------------
// If we have no damage zones allocated, we ignore any damage sent to us
//----------------------------------------------------------------------
//
Verify(message->damageZone < damageZoneCount);
Verify(message->damageZone >= 0 );
Check_Pointer(damageZones);
Check(damageZones[message->damageZone]);
damageZones[message->damageZone]->TakeDamage(message->damageData);
Check_Fpu();
}
//#############################################################################
// Construction and Destruction
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Entity::Entity(
Entity::MakeMessage *creation_message,
Entity::SharedData &virtual_data
):
Simulation(creation_message->classToCreate,virtual_data),
staticVideoSocket(NULL),
dynamicVideoSocket(NULL),
audioSocket(NULL)
{
Check_Pointer(this);
Check(creation_message);
//
// Get host manager pointer
//
HostManager *host_manager;
Check(application);
host_manager = application->GetHostManager();
Check(host_manager);
//
//-----------------------------
// Initialize the entity fields
//-----------------------------
//
playerLink = NULL;
resourceID = creation_message->resourceID;
owningPlayer = (Player*)host_manager->GetEntityPointer(
creation_message->owningPlayerID
);
simulationFlags = creation_message->instanceFlags;
entityID = creation_message->entityID;
ownerID = creation_message->ownerID;
creationTime = Now();
interestZoneID = creation_message->interestZoneID;
interestCount = 0;
// initialize attributes
localOrigin = creation_message->localOrigin;
updateOrigin = localOrigin;
localToWorld = localOrigin;
// initialize camera stuff
cameraOffset = Origin::Identity;
SetInvalidFlag();
// initialize model stuff
lastPerformance = creationTime;
subsystemCount = 0;
subsystemArray = NULL;
damageZones = NULL;
damageZoneCount = 0;
//
//--------------------------------
// Allocate the damageZones array
//--------------------------------
//
//
// Read In DamageZones and Create DamageZoneID's
//
if (resourceID != ResourceDescription::NullResourceID)
{
ResourceDescription *dmg_res =
application->GetResourceFile()->SearchList(
resourceID,
ResourceDescription::DamageZoneStreamResourceType
);
//
//--------------------------------------------
// Allocate damageZones for this entity only
// if they were defined in the resource
//--------------------------------------------
//
if (dmg_res)
{
dmg_res->Lock();
DynamicMemoryStream damage_zone_stream(
dmg_res->resourceAddress,
dmg_res->resourceSize);
damage_zone_stream >> damageZoneCount;
damageZones = new (::DamageZone(*[damageZoneCount]));
Register_Pointer(damageZones);
dmg_res->Unlock();
}
}
//
//---------------------------------------------------------
// Create a new entity ID and assign ownership to this host
// If (this is a master entity) or
// (this is a dynamic hermit) or
// (this is the initial independent)
//---------------------------------------------------------
//
if (
(GetInstance() == MasterInstance) ||
(GetInstance() == HermitInstance && IsDynamic()) ||
(
(GetInstance() == IndependantInstance) &&
(creation_message->ownerID == NullHostID ||
creation_message->ownerID == MapHostID)
)
)
{
entityID = host_manager->MakeUniqueEntityID();
ownerID = host_manager->GetLocalHostID();
creation_message->entityID = entityID;
creation_message->ownerID = ownerID;
}
//
//--------------------------------------------
// Tell the host manager about this new entity
//--------------------------------------------
//
Check(host_manager);
host_manager->NotifyOfEntityCreation(this, creation_message);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Entity*
Entity::Make(Entity::MakeMessage *creation_message)
{
return new Entity(creation_message,DefaultData);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
Entity::CreateMakeMessage(
MakeMessage *creation_message,
#if DEBUG_LEVEL>0
NotationFile *model_file,
#else
NotationFile *,
#endif
const ResourceDirectories *
)
{
Check(creation_message);
Check(model_file);
char *p;
creation_message->messageLength = sizeof(Entity::MakeMessage);
creation_message->messageID = Entity::MakeMessageID;
creation_message->owningPlayerID = EntityID::Null;
creation_message->messageFlags = 0;
creation_message->classToCreate = RegisteredClass::TrivialEntityClassID;
creation_message->instanceFlags = 0; //DefaultFlags;
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "X position coordinate missing!\n";
return False;
}
creation_message->localOrigin.linearPosition.x = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Y position coordinate missing!\n";
return False;
}
creation_message->localOrigin.linearPosition.y = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Z position coordinate missing!\n";
return False;
}
creation_message->localOrigin.linearPosition.z = atof(p);
EulerAngles pyr;
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Pitch missing!\n";
return False;
}
pyr.pitch = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Yaw missing!\n";
return False;
}
pyr.yaw = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Roll missing!\n";
return False;
}
pyr.roll = atof(p);
creation_message->localOrigin.angularPosition = pyr;
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
Entity::CreateMakeMapMessage(
MakeMapMessage *creation_message,
ResourceDescription::ResourceID resource_id,
#if DEBUG_LEVEL>0
NotationFile *model_file,
#else
NotationFile *,
#endif
const ResourceDirectories *
)
{
Check(creation_message);
Check(model_file);
char *p;
creation_message->messageLength = sizeof(Entity::MakeMapMessage);
creation_message->messageID = Entity::MakeMessageID;
creation_message->messageFlags = Entity::MakeMessage::MapStreamMarkerFlag;
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "X position coordinate missing!\n";
return False;
}
creation_message->localOrigin.linearPosition.x = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Y position coordinate missing!\n";
return False;
}
creation_message->localOrigin.linearPosition.y = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Z position coordinate missing!\n";
return False;
}
creation_message->localOrigin.linearPosition.z = atof(p);
EulerAngles pyr = EulerAngles::Identity;
#if 0
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Pitch missing!\n";
return False;
}
pyr.pitch = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Yaw missing!\n";
return False;
}
pyr.yaw = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Roll missing!\n";
return False;
}
pyr.roll = atof(p);
#endif
creation_message->localOrigin.angularPosition = pyr;
creation_message->resourceID = resource_id;
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::DestroyEntityMessageHandler(Message *)
{
CondemnToDeathRow();
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Entity::~Entity()
{
//
// Notify the host manager that we are going away
//
Check(application);
application->GetHostManager()->NotifyOfEntityDestruction(this);
//
// Delete the subsystem array
//
if (subsystemArray)
{
Check_Pointer(subsystemArray);
for (int i=0; i<subsystemCount; ++i)
{
if (subsystemArray[i])
{
Unregister_Object(subsystemArray[i]);
delete subsystemArray[i];
}
}
Unregister_Pointer(subsystemArray);
delete subsystemArray;
}
if (damageZones)
{
Check_Pointer(damageZones);
for (int i=0; i<damageZoneCount; ++i)
{
if (damageZones[i])
{
Unregister_Object(damageZones[i]);
delete damageZones[i];
}
}
Unregister_Pointer(damageZones);
delete damageZones;
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::CondemnToDeathRow()
{
Check(this);
Check(application);
EntityManager *entity_manager = application->GetEntityManager();
Check(entity_manager);
//
//-------------------------------------------------
// Make sure we are not entered into deathrow twice
//-------------------------------------------------
//
if (!IsCondemned())
{
entity_manager->deathRow->Add(this);
SetCondemnedFlag();
NeverExecute();
}
Check_Fpu();
}
//#############################################################################
// Test Support
//
Logical
Entity::TestInstance() const
{
return IsDerivedFrom(*GetClassDerivations());
}
//##########################################################################
// Renderer Support
//
void
Entity::AddStaticVideoComponent(Component *component)
{
Check(component);
Check(&staticVideoSocket);
staticVideoSocket.Add(component);
Check_Fpu();
}
void
Entity::AddDynamicVideoComponent(Component *component)
{
Check(component);
Check(&dynamicVideoSocket);
dynamicVideoSocket.Add(component);
Check_Fpu();
}
void
Entity::AddAudioComponent(Component *component)
{
Check(component);
Check(&audioSocket);
audioSocket.Add(component);
Check_Fpu();
}
Enumeration
Entity::GetAudioRepresentation(Entity *linked_entity)
{
Check(this);
Check(linked_entity);
return
(linked_entity == this) ?
InternalAudioRepresentation : ExternalAudioRepresentation;
}
//#############################################################################
// Interest Support
//
void
Entity::BecomeInteresting()
{
BecomeInterestingMessage
message(
Entity::BecomeInterestingMessageID,
sizeof(BecomeInterestingMessage)
);
Receiver::Dispatch(&message);
Check_Fpu();
}
void Entity::BecomeInterestingMessageHandler(const Receiver::Message*)
{
++interestCount;
Check_Fpu();
}
void
Entity::BecomeUninterestingMessageHandler()
{
--interestCount;
Check_Fpu();
}
Enumeration
Entity::GetInterestPriority(Entity *linked_entity)
{
Check(this);
Check(linked_entity);
return (linked_entity == this) ? HighInterestPriority : LowInterestPriority;
}
int
Entity::GetDamageZoneIndex(const CString &damage_zone_name) const
{
for(int ii=0;ii<damageZoneCount;++ii)
{
if(damageZones[ii]->damageZoneName.Compare(damage_zone_name) == 0)
{
return ii;
}
}
return -1;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
Entity::CreateDamageZoneStream(
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories *directories
)
{
Check(resource_file);
Check_Pointer(model_name);
Check(model_file);
Check_Pointer(directories);
//
//-------------------
// Find the .dmg file
//-------------------
//
const char* dmg_file_cname;
if (
!model_file->GetEntry(
"gamedata",
"DamageZones",
&dmg_file_cname
)
)
{
std::cerr << model_name << " is missing .dmg file specification!\n";
return -1;
}
CString dmg_filename =
MakePathedFilename(directories->modelDirectory, dmg_file_cname);
NotationFile *dmg_file = new NotationFile(dmg_filename);
Register_Object(dmg_file);
int page_count = dmg_file->PageCount();
if (!page_count)
{
std::cerr << dmg_filename << " is empty or missing!\n";
Unregister_Object(dmg_file);
delete dmg_file;
return -1;
}
DynamicMemoryStream damage_stream;
damage_stream << page_count;
NameList *dmg_namelist = dmg_file->MakePageList();
Register_Object(dmg_namelist);
NameList::Entry *dmg_entry = dmg_namelist->GetFirstEntry();
CString dmg_page_name;
while(dmg_entry)
{
dmg_page_name = dmg_entry->GetName();
DamageZone::CreateStreamedDamageZone(
model_file,
model_name,
NULL,
dmg_page_name,
&damage_stream,
dmg_file,
directories
);
dmg_entry = dmg_entry->GetNextEntry();
}
//
//~~~~~~~~~~~~~~~~~~~~~~~
// Add Stream to Resource
//~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription *new_res =
resource_file->AddResourceMemoryStream(
model_name,
ResourceDescription::DamageZoneStreamResourceType,
1,
ResourceDescription::Preload,
&damage_stream
);
Check(new_res);
Unregister_Object(dmg_file);
delete dmg_file;
Unregister_Object(dmg_namelist);
delete dmg_namelist;
return new_res->resourceID;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription::ResourceID
Entity::CreateExplosionTableStream(
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories *directories
)
{
Check(resource_file);
Check_Pointer(model_name);
Check(model_file);
Check_Pointer(directories);
//
//~~~~~~~~~~~~~~~~~
// Find *.dmg file
//~~~~~~~~~~~~~~~~~
//
const char *entry_data;
if (!model_file->GetEntry(
"gamedata",
"DamageZones",
&entry_data
)
)
{
DEBUG_STREAM << model_name << " is missing .dmg file specification!\n" << std::flush;
return -1;
}
CString filename =
MakePathedFilename(directories->modelDirectory, entry_data);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Make a notation file of the DMG Entries
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
NotationFile *dmg_file = new NotationFile(filename);
Register_Object(dmg_file);
if(dmg_file->IsEmpty())
{
DEBUG_STREAM << model_name << " *.dmg file is empty or missing!\n" << std::flush;
Unregister_Object(dmg_file);
delete dmg_file;
return -1;
}
DynamicMemoryStream explosion_stream;
NameList *dmg_namelist = dmg_file->MakePageList();
Register_Object(dmg_namelist);
NameList::Entry *dmg_entry = dmg_namelist->GetFirstEntry();
while(dmg_entry)
{
const char *entry_data;
if (dmg_file->GetEntry(
dmg_entry->GetName(),
"ExplosionTable",
&entry_data
)
)
{
CString filename =
MakePathedFilename(directories->modelDirectory, entry_data);
NotationFile *exp_file = new NotationFile(filename);
Register_Object(exp_file);
if(exp_file->IsEmpty())
{
std::cerr<<model_name<<" : "<<filename<<" is emoty or missing! \n";
Unregister_Object(dmg_file);
delete dmg_file;
Unregister_Object(dmg_namelist);
delete dmg_namelist;
Unregister_Object(exp_file);
delete exp_file;
return ResourceDescription::NullResourceID;
}
else
{
ExplosionTable::CreateStreamedExplosionTable(
resource_file,
model_name,
exp_file,
filename,
&explosion_stream
);
}
Unregister_Object(exp_file);
delete exp_file;
}
else
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Write Out no Explosions for this DamageZone
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int zero_int(0);
explosion_stream << zero_int;
}
dmg_entry = dmg_entry->GetNextEntry();
}
ResourceDescription *new_res =
resource_file->AddResourceMemoryStream(
model_name,
ResourceDescription::ExplosionTableStreamResourceType,
1,
ResourceDescription::Preload,
&explosion_stream
);
Check(new_res);
Unregister_Object(dmg_file);
delete dmg_file;
Unregister_Object(dmg_namelist);
delete dmg_namelist;
return new_res->resourceID;
}
#if defined(TEST_CLASS)
# include "entity.tcp"
#endif