The simulation advances in whole 50 Hz steps and the renderer draws whenever it can, so the drawn position only changed fifty times a second and was held for however many frames fell inside a step. That is visible as stepping, and it got WORSE the faster the machine: at 240 fps each position is held for nearly five frames, which is why a fast PC looked like a rabbit on crack while TARGETFPS=50 looked perfect. Matching the two rates hid it, but locking the frame rate to the physics rate throws away the entire point of having a fixed step. So drawing now blends. Entity keeps renderPreviousOrigin - its origin at the start of the step it is in, snapshotted by Mover::BeginStep - and renderStepFraction, how far through that step the frame falls, which is the leftover Entity::PerformAndWatch deliberately does not simulate. GetRenderToWorld blends the two with Origin::Lerp, which already did position and shortest-arc quaternion with normalisation. It is RENDER ONLY. localOrigin and localToWorld are untouched, so physics, collision, scoring, the nav map's queries and the network update records all still see exact stepped values. Three call sites. RootRenderable::Execute, which was the single place a vehicle's transform reached the matrix stack - the renderable already ran per frame and simply re-read a value that changed at the physics rate. The eye needed its gate widened as well: it rebuilt the view only when localToWorld CHANGED, so the world would have glided while the camera went on stepping and the judder would have moved rather than gone. And a teleport must stay a cut - that falls out free, because VTV::BeginStep applies a scheduled respawn and THEN calls Mover::BeginStep, so the snapshot lands post-teleport and the blend has nothing to travel. The picture trails the simulation by up to one step, 20 ms at 50 Hz. That is the standard price of interpolating rather than extrapolating, and much the lesser evil: guessing forward overshoots and shimmers every time the guess is corrected. RP412INTERP=0 turns it off so the stepping can be seen again without a rebuild. Determinism proved rather than asserted: a scripted lap (RP412INPUTSCRIPT, throttle and steering and pitch) at 240 fps with interpolation on and off, 90 PHYSTRACE samples over 22 seconds of driving, zero differ. Two earlier attempts at that comparison were invalid and both were my method - the first did not pin RP412SPAWNZONE so the runs began on different pads, and the second had no input script, so a joystick sitting on the desk drove the two runs differently. The template warns about the first of those in as many words. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1770 lines
44 KiB
C++
1770 lines
44 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;
|
|
}
|
|
|
|
//
|
|
// How far past the last completed step the frame we are
|
|
// about to draw falls, as a fraction of one step. This is
|
|
// the leftover the fixed-step loop deliberately does not
|
|
// simulate - see renderPreviousOrigin in entity.h.
|
|
//
|
|
{
|
|
Scalar leftover = till - GetLastPerformance();
|
|
Scalar fraction = (fixed_step > (Scalar) 0)
|
|
? (leftover / fixed_step) : (Scalar) 0;
|
|
if (fraction < (Scalar) 0) fraction = (Scalar) 0;
|
|
if (fraction > (Scalar) 1) fraction = (Scalar) 1;
|
|
renderStepFraction = fraction;
|
|
}
|
|
|
|
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;
|
|
|
|
//
|
|
// Render interpolation starts with nothing to blend: the previous
|
|
// origin IS the current one and no fraction of a step is outstanding,
|
|
// so GetRenderToWorld hands back localToWorld until the first step has
|
|
// actually been taken.
|
|
//
|
|
renderPreviousOrigin = localOrigin;
|
|
renderStepFraction = (Scalar) 0;
|
|
|
|
// 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());
|
|
}
|
|
|
|
//##########################################################################
|
|
// Render interpolation - see entity.h for why, and for why it is render
|
|
// only. RP412INTERP=0 turns it off so the stepping it removes can be seen
|
|
// again on a test machine without a rebuild.
|
|
//##########################################################################
|
|
//
|
|
static Logical
|
|
RenderInterpolationEnabled()
|
|
{
|
|
static int enabled = -1;
|
|
|
|
if (enabled < 0)
|
|
{
|
|
const char *setting = getenv("RP412INTERP");
|
|
enabled = (setting != NULL && atoi(setting) == 0) ? 0 : 1;
|
|
if (!enabled)
|
|
{
|
|
DEBUG_STREAM << "Render: interpolation off (RP412INTERP=0) - "
|
|
<< "drawn motion steps at the physics rate\n" << std::flush;
|
|
}
|
|
}
|
|
return enabled ? True : False;
|
|
}
|
|
|
|
void
|
|
Entity::GetRenderToWorld(LinearMatrix *out)
|
|
{
|
|
Check(this);
|
|
Check_Pointer(out);
|
|
|
|
//
|
|
// Nothing to blend towards: no fraction of a step outstanding, no
|
|
// fixed step at all, or interpolation switched off. Hand back exactly
|
|
// what every caller used before this existed.
|
|
//
|
|
if (renderStepFraction <= (Scalar) 0 || !RenderInterpolationEnabled())
|
|
{
|
|
*out = localToWorld;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Origin::Lerp does the position and the shortest-arc quaternion, and
|
|
// normalises - which over a 20 ms step is indistinguishable from a
|
|
// true slerp and a good deal cheaper.
|
|
//
|
|
Origin blended;
|
|
blended.Lerp(renderPreviousOrigin, localOrigin, renderStepFraction);
|
|
*out = blended;
|
|
Check_Fpu();
|
|
}
|
|
|
|
//##########################################################################
|
|
// 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
|