RP412PHYSICSHZ names a rate and the simulation advances in whole steps of exactly that size on every machine, whatever the display does. 0 - the default, and the shipped behaviour until the play testers have spoken - is the game as it has always run: the step is however long the last frame took, which makes the frame rate part of the physics. Measured over two seconds of free fall, a 30 fps machine's pod fell three times further than a 144 fps machine's. Two players on the same track were not in the same gravity. With a rate set, the same race is bit-identical across frame rates: 30, 60 and 144 fps produce the same trajectory to the last printed digit, and identical runs reproduce exactly - which was never true of this engine before, at any frame rate. It took three pieces, and every one was found by measuring, not by reading: - Simulation::PerformTo turns lastPerformance into the accumulator it always secretly was: whole steps while time remains, the remainder carried to the next frame. Watchers and update records stay once per frame - stepping is physics, watching is I/O. - Entity::PerformAndWatch interleaves subsystems and entity per STEP. The frame loop ran all subsystems to the frame boundary and then the entity, indistinguishable from correct at one step per frame - which is why thirty years of code never noticed - and wrong at two: the thrusters raycast twice from a vehicle that had not moved, and the hover spring fired twice on one stale height sample. The subsystems are also snapped onto their entity's step grid; each Simulation anchors its grid at its own creation time, a per-run phase no seed could pin. - Mover::BeginStep clears the force accumulator per step. It was cleared once per frame while the thrusters ADD per step, so step two of a frame integrated step one's thrust again - and how many steps a frame holds rides on wall-clock jitter, which is why identical configs measured a quarter-metre apart. The quaternion renormalise counts steps now too, for the same reason. The catch-up clamp is a quarter second of simulation whatever the rate, so a machine that cannot keep up slows down rather than seizing, and does so identically everywhere. The engine's clock counts milliseconds, so rates that do not divide 1000 - 60 among them - quietly run at the neighbouring millisecond step; the log now says so and names the exact ones. 25, 50 and 100 are exact, and all three are verified bit-identical across frame rates and across runs. Verified for a single vehicle settling under gravity and hover. Driving, collisions and the network are the next frontiers, in that order: the collision path writes the victim's state with wall-clock stamps and a hard-coded 0.1 s bounce, which single-player survives and lockstep will not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1694 lines
41 KiB
C++
1694 lines
41 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
|
|
//
|
|
// This is the only point on the receive path that knows WHOSE update
|
|
// this is - the records themselves carry a timestamp but not an owner -
|
|
// so the sender is published here for the net clock to align against.
|
|
// Every record in the message, and the damage zones nested inside them,
|
|
// came from the same machine in the same frame.
|
|
//------------------------------------------------------------------------
|
|
//
|
|
//
|
|
// Only for an entity somebody else owns. Our own clock needs no
|
|
// aligning, and an update we somehow handed ourselves would otherwise
|
|
// drag lastUpdate back by a frame for no reason.
|
|
//
|
|
Check(application);
|
|
Check(application->GetHostManager());
|
|
Logical remote_owner =
|
|
GetOwnerID() != application->GetHostManager()->GetLocalHostID();
|
|
if (remote_owner)
|
|
{
|
|
NetClock_BeginUpdate(GetOwnerID());
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if (remote_owner)
|
|
{
|
|
NetClock_EndUpdate();
|
|
}
|
|
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);
|
|
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)
|
|
{
|
|
//
|
|
//----------------------------------------------------------------
|
|
// Fixed-step: the subsystems and the entity advance TOGETHER,
|
|
// one step at a time, because they read each other mid-flight.
|
|
// The VTV's hover spring is computed from its thrusters'
|
|
// measured heights, and each thruster measures from where the
|
|
// vehicle IS - so thrusters stepped twice against a vehicle
|
|
// that has not moved yet hand back two identical height
|
|
// samples, and the spring fires twice on stale data. Measured,
|
|
// that pod climbs at 30 fps and flies level at 144.
|
|
//
|
|
// So the step loop lives HERE, above both: everyone is walked
|
|
// to the same sub-frame instant before anyone takes the next
|
|
// step. Watchers and the update stream still run once per
|
|
// frame, after the loop - stepping is physics, watching is
|
|
// I/O, and only the first belongs inside.
|
|
//
|
|
// The interleave keys off the ENTITY's own clock so a
|
|
// subsystem created mid-flight (they are made alongside their
|
|
// owner) can never wedge the loop.
|
|
//----------------------------------------------------------------
|
|
//
|
|
Scalar fixed_step = Simulation::FixedStep();
|
|
|
|
if (fixed_step > (Scalar) 0)
|
|
{
|
|
//
|
|
// One grid for the whole vehicle. Every Simulation anchors
|
|
// its own lastPerformance at its creation time, so an
|
|
// entity and its subsystems were stepping on grids offset
|
|
// by a random fraction of a step - deterministic within a
|
|
// run, DIFFERENT between runs, because creation times ride
|
|
// on load timing. The thrusters' measurements then landed
|
|
// a different sub-step distance from the vehicle's
|
|
// integration every launch, which is physics drift no seed
|
|
// can pin. Snap the subsystems onto the entity's grid; the
|
|
// interleave below then keeps everyone in lockstep by
|
|
// construction, and once aligned this assignment is a
|
|
// no-op every frame after.
|
|
//
|
|
for (int i=0; i<subsystemCount; ++i)
|
|
{
|
|
if (subsystemArray[i] &&
|
|
subsystemArray[i]->IsNonReplicantExecutable())
|
|
{
|
|
subsystemArray[i]->SetLastPerformance(
|
|
GetLastPerformance());
|
|
}
|
|
}
|
|
|
|
Time step_till = GetLastPerformance();
|
|
step_till += fixed_step;
|
|
|
|
while (step_till <= till)
|
|
{
|
|
//
|
|
// BeginStep on the ENTITY comes before the subsystems
|
|
// perform: the Mover's force accumulator is cleared
|
|
// here, and the thrusters then ADD this step's forces
|
|
// into a clean slate. The first version left that
|
|
// clear on the per-frame path, so a two-step frame
|
|
// integrated step one's thrust twice - and since how
|
|
// many steps land in a frame rides on wall-clock
|
|
// jitter, no two runs saw the same force history.
|
|
// Identical configs measured 0.23 apart because of it.
|
|
//
|
|
BeginStep();
|
|
for (int i=0; i<subsystemCount; ++i)
|
|
{
|
|
if (subsystemArray[i] &&
|
|
subsystemArray[i]->IsNonReplicantExecutable())
|
|
{
|
|
subsystemArray[i]->BeginStep();
|
|
subsystemArray[i]->PerformTo(step_till);
|
|
}
|
|
}
|
|
Simulation::PerformTo(step_till);
|
|
step_till += fixed_step;
|
|
}
|
|
|
|
for (int i=0; i<subsystemCount; ++i)
|
|
{
|
|
if (subsystemArray[i] &&
|
|
subsystemArray[i]->IsNonReplicantExecutable())
|
|
{
|
|
subsystemArray[i]->WatchAndWrite(update_stream);
|
|
}
|
|
}
|
|
|
|
SET_PERFORM_ENTITY();
|
|
Simulation::WatchAndWrite(update_stream);
|
|
Check_Fpu();
|
|
CLEAR_PERFORM_ENTITY();
|
|
CLEAR_PERFORM_SUBSYSTEMS();
|
|
return;
|
|
}
|
|
|
|
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
|