Files
RP412/MUNGA/ENTITY.cpp
T
CydandClaude Opus 5 d9149a6a8c A quiet pod coasts a second, then waits to be told
Build 2's first two items, L4 and L5, picked ahead of the rest of the
latency tier because the field data argues for them: a five-pod race on
4.12.233 kept time well - send interval median and p95 both ~32 ms - but
every pod saw gaps of one to two seconds, and corrections as large as
2702 metres against a mean under a metre. That is not a timing problem,
it is a pod flying most of the way across the map on stale velocity and
being yanked back through whatever it passed.

L5, the extrapolation clamp. Both dead reckoners carried
lastPerformance - lastUpdate into the projection unbounded once past the
expected update. They now stop at kMaximumExtrapolationSeconds, one
second, so a replicant coasts and then parks. A pod that stops and snaps
once reads as the dropped connection it is; a pod sliding confidently
through scenery reads as a broken game, and is far more expensive to
collide with. It matters more in the accelerated reckoner, which also
carries a*t*t/2 and so grows the error as the SQUARE of the silence.
RP412NETCOAST sets the seconds, 0 restores the unbounded coast for an
A/B on one connection. MUST MATCH across a race, same class as
RP412NETPREDICT - it moves replicants, so it decides where they collide.

L4, the stale-record guard. Simulation now remembers the sender's stamp
on the last record it accepted, and the record walk skips one stamped
earlier - such a record winds lastUpdate backwards and has the reckoner
extrapolate from a position the sender has already left. A regression
larger than five seconds is not a late record but a different stream (a
rejoin, a restart, a clock that was set), so that resyncs instead. The
counter NetRaceStats::staleCount has been sitting there reading zero
with a comment saying "until the stale guard ships"; it is now fed.

The guard is asked by the caller rather than done inside
ReadUpdateRecord, because that is virtual and not every override chains
to the base.

Verified: clean Release build; two-pod loopback race green, both pods
scoring, and stale 0 on both - which is the reading that says the guard
does not fire on an ordered stream. The clamp is NOT demonstrated by
that run: the harness parks its pods, so there is no velocity to coast
on. Pod B did see a 9.167 s gap in it, which on a moving pod is the
shape of the field's kilometre corrections.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 21:35:22 -05:00

1904 lines
48 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);
//
// Skip a record the sender stamped before the one we already
// applied - see Simulation::AcceptUpdateStamp. Counted, because
// on an ordered stream this should never fire and the number
// saying so is the point of it.
//
if (simulation->AcceptUpdateStamp(update->timeStamp))
{
simulation->ReadUpdateRecord(update);
}
else
{
netRaceStats.staleCount++;
}
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;
//
// RP412CAMLOG: how hard does an arriving update MOVE this
// entity, and how often?
//
// Between updates a replicant is dead-reckoned from
// updateOrigin over (lastPerformance - lastUpdate). When the
// next one lands the basis is replaced, so the drawn position
// jumps by however far the prediction had drifted. Render
// interpolation cannot hide that: it smooths within a step,
// and this is a discontinuity in the stepped values
// themselves. If the interval is a few per second and the
// correction is tens of centimetres, that is a tick a few
// times a second - which is the symptom being chased.
//
// Reported per entity, on a five second clock, so a busy race
// does not bury the log.
//
//
// Race totals for the RP412NETLOG summary - per entity,
// ungated (one subtract and a length per arriving update),
// where the CamLog block below is windowed, gated, and
// shared across all replicants.
//
if (GetInstance() == ReplicantInstance)
{
Vector3D race_drift;
race_drift.Subtract(
update->localOrigin.linearPosition,
localOrigin.linearPosition
);
Scalar race_distance = race_drift.Length();
netRaceStats.correctionCount++;
netRaceStats.correctionTotal += race_distance;
if (race_distance > netRaceStats.correctionWorst)
{
netRaceStats.correctionWorst = race_distance;
}
}
if (RPCameraLog() && GetInstance() == ReplicantInstance)
{
static Scalar next_say = 0.0f;
static int corrections = 0;
static Scalar worst = 0.0f;
static Scalar total = 0.0f;
Vector3D drift;
drift.Subtract(
update->localOrigin.linearPosition,
localOrigin.linearPosition
);
Scalar distance = drift.Length();
++corrections;
total += distance;
if (distance > worst)
{
worst = distance;
}
if ((Scalar) Now() >= next_say)
{
if (next_say > 0.0f && corrections > 0)
{
DEBUG_STREAM << "CamLog: replicant corrections - "
<< corrections << " in 5s ("
<< (corrections / 5) << "/s), mean "
<< (total / corrections) << "m, worst "
<< worst << "m\n" << std::flush;
}
next_say = ((Scalar) Now()) + 5.0f;
corrections = 0;
worst = 0.0f;
total = 0.0f;
}
}
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;
//
// Set outside the step loop, so renderPreviousOrigin now
// describes a step that never happened. Draw plainly until
// a real one does.
//
renderStepTaken = False;
}
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;
renderStepTaken = False;
// 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::SnapshotRenderOrigin()
{
Check(this);
renderPreviousOrigin = localOrigin;
renderStepTaken = True;
}
void
Entity::SetRenderStepFraction(Scalar fraction)
{
Check(this);
renderStepFraction = fraction;
}
void
Entity::GetRenderToWorld(LinearMatrix *out)
{
Check(this);
Check_Pointer(out);
//
// No fixed step at all, interpolation switched off, or this entity has
// not taken a step yet. Hand back exactly what every caller used before
// this existed.
//
// The test used to be on the fraction rather than on renderStepTaken,
// and that was wrong in a way that showed. Drawing at fraction f means
// drawing at the start of the step plus f of it, so f = 0 means the
// START of the step - while this early return hands back localToWorld,
// which is its END. The two are a whole step apart, about a metre at
// racing speed.
//
// behind is a whole number of milliseconds against a 20ms step, so it
// lands on exactly zero about one frame in twenty. On those frames a
// pod was drawn a full step ahead of itself and then snapped back on
// the next one: a jump three times a second at 59fps, regular because
// the beat between frame rate and step rate is regular, and worst when
// a pod crosses the view quickly. It never appeared in any simulation
// trace because the simulation was right - only the drawing was wrong.
//
// Interpolating at f = 0 is continuous with everything either side of
// it. Each frame advances the drawn position by frame_time / step
// whether or not a step boundary falls between the two, which is the
// entire point of doing it.
//
if (!RenderInterpolationEnabled() || !renderStepTaken)
{
*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