The drawn pod stalled about fifteen times in 293 frames while the
simulation stepped perfectly smoothly through the same window - and 15 of
293 is 5%, which is exactly the count of frames the earlier trace found
sitting at a render fraction of zero. That was the whole clue.
GetRenderToWorld tested the fraction to decide whether to interpolate at
all:
if (renderStepFraction <= 0 || !RenderInterpolationEnabled())
*out = localToWorld;
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. localToWorld 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 roughly one frame in twenty. On those frames the pod was
drawn a full step ahead of itself and snapped back on the next one. 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 -
which is the symptom as it was first described, and it took this long to
find because every simulation trace was right. Only the drawing was wrong.
Ask renderStepTaken instead, which is what the condition meant all along.
Interpolating at f = 0 is continuous with its neighbours: 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.
The same mistake was in DPLEyeRenderable's rebuild gate, using the
fraction as a proxy for whether interpolation was running. Same fix.
renderStepTaken is cleared where localOrigin is assigned outside the step
loop, so a stale snapshot is never blended from.
Render path only - localOrigin is untouched, so physics, collisions and
determinism are unaffected.
The foreign-eye rejection added in the previous build turned out not to be
the cause: it rejected between zero and three samples per window against
stall counts in the twenties. Keeping it, since sampling one viewpoint
against another was still wrong, but it was not this.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
684 lines
17 KiB
C++
684 lines
17 KiB
C++
#pragma once
|
|
|
|
#include "simulate.h"
|
|
#include "origin.h"
|
|
#include "linmtrx.h"
|
|
|
|
class Player;
|
|
class NotationFile;
|
|
class DamageZone;
|
|
class Damage;
|
|
class Subsystem;
|
|
|
|
#define ENTITY_CONTINUATION
|
|
#include "entity2.h"
|
|
|
|
//##########################################################################
|
|
//########################### Entity #################################
|
|
//##########################################################################
|
|
|
|
class Entity:
|
|
public Simulation
|
|
{
|
|
friend class Entity__StaticVideoSocketIterator;
|
|
friend class Entity__DynamicVideoSocketIterator;
|
|
friend class Entity__AudioSocketIterator;
|
|
friend class Entity__GaugeSocketIterator;
|
|
friend class Entity__AudioLocationIterator;
|
|
|
|
//##########################################################################
|
|
// Shared Data support
|
|
//
|
|
public:
|
|
typedef Entity__SharedData SharedData;
|
|
SharedData*
|
|
GetSharedData();
|
|
|
|
static Derivation *GetClassDerivations();
|
|
|
|
static SharedData DefaultData;
|
|
|
|
//##########################################################################
|
|
// Message Support
|
|
//
|
|
public:
|
|
enum {
|
|
MakeMessageID = Simulation::NextMessageID,
|
|
MakeReadyMessageID,
|
|
RemakeEntityMessageID,
|
|
RemakeReadyMessageID,
|
|
RemakeCompleteMessageID,
|
|
RemakeIncompleteMessageID,
|
|
DestroyEntityMessageID,
|
|
TransferEntityMessageID,
|
|
TransferCompleteMessageID,
|
|
TransferIncompleteMessageID,
|
|
BecomeInterestingMessageID,
|
|
BecomeUninterestingMessageID,
|
|
UpdateMessageID,
|
|
SubscribeReplicantMessageID,
|
|
UnsubscribeReplicantMessageID,
|
|
TakeDamageMessageID,
|
|
TakeDamageStreamMessageID,
|
|
PlayerLinkMessageID,
|
|
NextMessageID
|
|
};
|
|
|
|
typedef Entity__Message Message;
|
|
typedef Entity__MakeMessage MakeMessage;
|
|
typedef Entity__MakeReadyMessage MakeReadyMessage;
|
|
typedef Entity__RemakeEntityMessage RemakeEntityMessage;
|
|
typedef Entity__RemakeReadyMessage RemakeReadyMessage;
|
|
typedef Entity__RemakeCompleteMessage RemakeCompleteMessage;
|
|
typedef Entity__RemakeIncompleteMessage RemakeIncompleteMessage;
|
|
typedef Entity__DestroyEntityMessage DestroyEntityMessage;
|
|
typedef Entity__TransferEntityMessage TransferEntityMessage;
|
|
typedef Entity__TransferCompleteMessage TransferCompleteMessage;
|
|
typedef Entity__TransferIncompleteMessage TransferIncompleteMessage;
|
|
typedef Entity__BecomeInterestingMessage BecomeInterestingMessage;
|
|
typedef Entity__BecomeUninterestingMessage BecomeUninterestingMessage;
|
|
typedef Entity__UpdateMessage UpdateMessage;
|
|
typedef Entity__SubscribeReplicantMessage SubscribeReplicantMessage;
|
|
typedef Entity__UnsubscribeReplicantMessage UnsubscribeReplicantMessage;
|
|
typedef Entity__TakeDamageMessage TakeDamageMessage;
|
|
typedef Entity__TakeDamageStreamMessage TakeDamageStreamMessage;
|
|
typedef Entity__PlayerLinkMessage PlayerLinkMessage;
|
|
typedef Entity__DynamicMessage DynamicMessage;
|
|
typedef Entity__MakeMapMessage MakeMapMessage;
|
|
|
|
|
|
void
|
|
Receive(Event *event);
|
|
|
|
void
|
|
LocalDispatch(Message *what)
|
|
{Receiver::Receive(Cast_Object(Receiver::Message*,what));}
|
|
|
|
void
|
|
Dispatch(Receiver::Message *what);
|
|
|
|
void
|
|
DispatchToReplicant(
|
|
Message *what,
|
|
HostID host
|
|
);
|
|
|
|
void
|
|
DispatchToReplicants(Message *what);
|
|
|
|
private:
|
|
static const HandlerEntry MessageHandlerEntries[];
|
|
|
|
protected:
|
|
//static MessageHandlerSet MessageHandlers;
|
|
static MessageHandlerSet& GetMessageHandlers();
|
|
|
|
//##########################################################################
|
|
// Attribute Support
|
|
//
|
|
public:
|
|
enum {
|
|
LocalToWorldAttributeID = Simulation::NextAttributeID,
|
|
LocalOriginAttributeID,
|
|
DamageZoneCountAttributeID,
|
|
DamageZonesAttributeID,
|
|
NextAttributeID
|
|
};
|
|
|
|
private:
|
|
static const IndexEntry AttributePointers[];
|
|
|
|
protected:
|
|
//static AttributeIndexSet AttributeIndex
|
|
static AttributeIndexSet& GetAttributeIndex();
|
|
|
|
//
|
|
// public attribute declarations go here
|
|
//
|
|
public:
|
|
LinearMatrix localToWorld;
|
|
Origin localOrigin;
|
|
int damageZoneCount;
|
|
DamageZone **damageZones;
|
|
|
|
//######################################################################
|
|
//################### Render interpolation #########################
|
|
//######################################################################
|
|
//
|
|
// The simulation advances in whole fixed steps (RP412PHYSICSHZ) and the
|
|
// renderer draws whenever it can, so at any frame rate that is not the
|
|
// step rate the drawn position only changes 50 times a second and is
|
|
// held for however many frames fall inside a step. That is visible as
|
|
// stepping, and it gets WORSE the faster the machine: at 240 fps each
|
|
// position is held for nearly five frames.
|
|
//
|
|
// So drawing interpolates. renderPreviousOrigin is this entity's origin
|
|
// at the START of the step it is currently in, snapshotted by
|
|
// Mover::BeginStep, and renderStepFraction is how far through that step
|
|
// the frame being drawn falls. GetRenderToWorld blends the two.
|
|
//
|
|
// RENDER ONLY. localOrigin and localToWorld are untouched, so physics,
|
|
// collision, scoring, the nav map's entity queries and the network
|
|
// update records all keep seeing exact stepped values - which is what
|
|
// keeps the simulation identical on every machine at every frame rate.
|
|
// RP412PHYSTRACE is the proof of that and must not move.
|
|
//
|
|
// The picture therefore trails the simulation by up to one step (20 ms
|
|
// at 50 Hz), which is the standard price and much the lesser evil:
|
|
// extrapolating FORWARD instead has to guess, and overshoots into
|
|
// shimmer every time the guess is corrected.
|
|
//
|
|
// A teleport must not be smoothed - sliding a pod 300 metres across the
|
|
// map over 20 ms would be far worse than the cut it replaces. That
|
|
// falls out for free: VTV::BeginStep applies a scheduled respawn and
|
|
// THEN calls Mover::BeginStep, so the snapshot is taken after the
|
|
// teleport and the blend has nothing to travel.
|
|
//
|
|
Origin renderPreviousOrigin;
|
|
Scalar renderStepFraction;
|
|
|
|
//
|
|
// Whether renderPreviousOrigin describes a step this entity actually
|
|
// took. It has to be asked separately from the fraction, because a
|
|
// fraction of zero is a perfectly ordinary place to be drawing - see
|
|
// GetRenderToWorld for the one-step jump that testing the fraction
|
|
// instead used to produce.
|
|
//
|
|
Logical renderStepTaken;
|
|
|
|
//
|
|
// The transform to DRAW with. Falls back to localToWorld verbatim when
|
|
// interpolation is off, when there is no fixed step to interpolate
|
|
// within, or before the first snapshot exists - so the unfixed-step
|
|
// path behaves exactly as it always did.
|
|
//
|
|
void
|
|
GetRenderToWorld(LinearMatrix *out);
|
|
|
|
//
|
|
// Filled by Simulation::PerformTo around each fixed step, for locally
|
|
// simulated entities and replicants alike.
|
|
//
|
|
void
|
|
SnapshotRenderOrigin();
|
|
void
|
|
SetRenderStepFraction(Scalar fraction);
|
|
|
|
|
|
int
|
|
GetDamageZoneIndex(const CString &damage_zone_name) const;
|
|
|
|
|
|
//
|
|
// virtual access
|
|
//
|
|
public:
|
|
virtual Vector3D
|
|
GetWorldLinearVelocity()
|
|
{return Vector3D(0.0f, 0.0f, 0.0f);}
|
|
virtual Vector3D
|
|
GetWorldLinearAcceleration()
|
|
{return Vector3D(0.0f, 0.0f, 0.0f);}
|
|
|
|
//##########################################################################
|
|
// Model Support
|
|
//
|
|
public:
|
|
UpdateMessage*
|
|
Execute(const Time &till);
|
|
|
|
Subsystem*
|
|
GetSubsystem(int index)
|
|
{
|
|
Check(this); Verify((unsigned)index < subsystemCount);
|
|
return subsystemArray[index];
|
|
}
|
|
Simulation*
|
|
GetSimulation(int index);
|
|
int
|
|
GetSubsystemCount()
|
|
{Check(this); return subsystemCount;}
|
|
Subsystem*
|
|
FindSubsystem(const char* name);
|
|
|
|
typedef void
|
|
(Entity::*Performance)(Scalar time_slice);
|
|
|
|
typedef Entity__UpdateRecord UpdateRecord;
|
|
|
|
void
|
|
SetPerformance(Performance performance)
|
|
{
|
|
Check(this);
|
|
activePerformance = (Simulation::Performance)performance;
|
|
}
|
|
|
|
enum {
|
|
EntitySubsystemID = -1
|
|
};
|
|
|
|
static int
|
|
FindSubsytemID(
|
|
const char *model_name,
|
|
const char *subsystem_name
|
|
);
|
|
public:
|
|
|
|
enum {
|
|
DamageZoneUpdateModelBit = Simulation::NextUpdateModelBit,
|
|
NextUpdateModelBit
|
|
};
|
|
|
|
enum {
|
|
DamageZoneUpdateModelFlag = 1 << DamageZoneUpdateModelBit
|
|
};
|
|
|
|
Logical DamageZoneUpdateModelFlagSet()
|
|
{
|
|
Check(this);
|
|
return ((updateModel & DamageZoneUpdateModelFlag) != 0);
|
|
}
|
|
|
|
protected:
|
|
|
|
void
|
|
WriteUpdateRecord(
|
|
Simulation::UpdateRecord *message,
|
|
int update_model
|
|
);
|
|
void
|
|
UpdateMessageHandler(UpdateMessage *message);
|
|
|
|
void
|
|
ReadUpdateRecord(Simulation::UpdateRecord *message);
|
|
|
|
void
|
|
ReadDamageUpdateRecord(Simulation::UpdateRecord *update_record);
|
|
|
|
void
|
|
WriteDamageUpdateRecord(Simulation::UpdateRecord *update_record);
|
|
|
|
void
|
|
PerformAndWatch(
|
|
const Time &till,
|
|
MemoryStream *update_stream
|
|
);
|
|
|
|
int
|
|
subsystemCount;
|
|
Subsystem
|
|
**subsystemArray;
|
|
Origin
|
|
updateOrigin;
|
|
|
|
void
|
|
TakeDamageStreamMessageHandler(TakeDamageStreamMessage *message);
|
|
void
|
|
TakeDamageMessageHandler(TakeDamageMessage *message);
|
|
void
|
|
PlayerLinkMessageHandler(PlayerLinkMessage *message);
|
|
|
|
//##########################################################################
|
|
// Renderer Support
|
|
//
|
|
public:
|
|
void
|
|
AddStaticVideoComponent(Component *component);
|
|
void
|
|
AddDynamicVideoComponent(Component *component);
|
|
void
|
|
AddAudioComponent(Component *component);
|
|
|
|
typedef Entity__StaticVideoSocketIterator
|
|
StaticVideoSocketIterator;
|
|
typedef Entity__DynamicVideoSocketIterator
|
|
DynamicVideoSocketIterator;
|
|
typedef Entity__AudioSocketIterator
|
|
AudioSocketIterator;
|
|
|
|
virtual Enumeration
|
|
GetAudioRepresentation(Entity *linked_entity);
|
|
|
|
private:
|
|
SChainOf<Component*>
|
|
staticVideoSocket;
|
|
SChainOf<Component*>
|
|
dynamicVideoSocket;
|
|
SChainOf<Component*>
|
|
audioSocket;
|
|
|
|
//##########################################################################
|
|
// Flag Support
|
|
//
|
|
public:
|
|
//
|
|
// MasterInstance - created on owning host, replicated on remote hosts,
|
|
// sends updates to replicants
|
|
// ReplicantInstance - created on remote hosts, receives updates from
|
|
// MasterInstance
|
|
// IndependantInstance - created on owning host, replicated on remote
|
|
// hosts, never receives updates from MasterInstance
|
|
// HermitInstance - created on owning host, is not replicated, does
|
|
// send updates
|
|
//
|
|
enum {
|
|
InstanceBits = Simulation::NextBit,
|
|
ValidBit = InstanceBits+2,
|
|
TransferableBit,
|
|
InterestBit,
|
|
InterestLockedBit,
|
|
DynamicBit,
|
|
TrappedBit,
|
|
StatueBit,
|
|
MapBit,
|
|
PreRunBit,
|
|
CondemnedBit,
|
|
NextBit
|
|
};
|
|
enum Instance
|
|
{
|
|
MasterInstance=0,
|
|
ReplicantInstance=1<<InstanceBits,
|
|
IndependantInstance=2<<InstanceBits,
|
|
HermitInstance=3<<InstanceBits
|
|
};
|
|
enum {
|
|
InstanceMask = HermitInstance,
|
|
ValidFlag = 1<<ValidBit,
|
|
TransferableFlag = 1<<TransferableBit,
|
|
InterestFlag = 1<<InterestBit,
|
|
InterestLockedFlag = 1<<InterestLockedBit,
|
|
DynamicFlag = 1<<DynamicBit,
|
|
TrappedFlag = 1<<TrappedBit,
|
|
StatueFlag = 1<<StatueBit,
|
|
MapFlag = 1<<MapBit,
|
|
PreRunFlag = 1<<PreRunBit,
|
|
CondemnedFlag = 1<<CondemnedBit,
|
|
TypeMask = DynamicFlag|TrappedFlag|MapFlag,
|
|
// DefaultFlags = MasterInstance
|
|
DefaultFlags = DynamicFlag|MasterInstance
|
|
};
|
|
enum Type {
|
|
StaticType = 0,
|
|
DynamicType = DynamicFlag,
|
|
TrappedType = DynamicFlag|TrappedFlag,
|
|
StatueType = DynamicFlag|TrappedFlag|StatueFlag,
|
|
MapType = DynamicFlag|MapFlag,
|
|
TrappedMapType = DynamicFlag|TrappedFlag|MapFlag,
|
|
};
|
|
|
|
//##########################################################################
|
|
// Instance Type support
|
|
//
|
|
public:
|
|
void
|
|
SetInstance(Instance type)
|
|
{Check(this); simulationFlags &= ~InstanceMask; simulationFlags |= type;}
|
|
Instance
|
|
GetInstance()
|
|
{Check(this); return (Instance)(simulationFlags&InstanceMask);}
|
|
|
|
static LWord
|
|
EntityFlagsSetInstance(LWord entity_flags, Instance type)
|
|
{
|
|
entity_flags &= ~InstanceMask;
|
|
entity_flags |= type;
|
|
return entity_flags;
|
|
}
|
|
static Instance
|
|
EntityFlagsGetInstance(LWord entity_flags)
|
|
{return (Instance)(entity_flags&InstanceMask);}
|
|
|
|
//##########################################################################
|
|
// Validity Support
|
|
//
|
|
public:
|
|
void
|
|
SetValidFlag()
|
|
{Check(this); simulationFlags |= ValidFlag;}
|
|
void
|
|
SetInvalidFlag()
|
|
{Check(this); simulationFlags &= ~ValidFlag;}
|
|
Logical
|
|
IsValid()
|
|
{Check(this); return (simulationFlags&ValidFlag) != 0;}
|
|
|
|
void
|
|
SetPreRunFlag()
|
|
{Check(this); simulationFlags |= PreRunFlag;}
|
|
void
|
|
SetNoPreRunFlag()
|
|
{Check(this); simulationFlags &= ~PreRunFlag;}
|
|
Logical
|
|
IsPreRunnable()
|
|
{Check(this); return (simulationFlags&PreRunFlag) != 0;}
|
|
|
|
void
|
|
SetCondemnedFlag()
|
|
{Check(this); simulationFlags |= CondemnedFlag;}
|
|
Logical
|
|
IsCondemned()
|
|
{Check(this); return (simulationFlags&CondemnedFlag) != 0;}
|
|
|
|
//##########################################################################
|
|
// Transference Support
|
|
//
|
|
public:
|
|
void
|
|
SetTransferableFlag()
|
|
{Check(this); simulationFlags |= TransferableFlag;}
|
|
void
|
|
SetNontransferableFlag()
|
|
{Check(this); simulationFlags &= ~TransferableFlag;}
|
|
Logical
|
|
IsTransferable()
|
|
{Check(this); return (simulationFlags&TransferableFlag) != 0;}
|
|
|
|
const EntityID&
|
|
GetEntityID()
|
|
{return entityID;}
|
|
HostID
|
|
GetOwnerID()
|
|
{return ownerID;}
|
|
|
|
EntityID
|
|
entityID;
|
|
HostID
|
|
ownerID;
|
|
|
|
Player
|
|
*playerLink;
|
|
|
|
Player*
|
|
GetPlayerLink()
|
|
{return playerLink;}
|
|
|
|
//##########################################################################
|
|
// Interest Support
|
|
//
|
|
public:
|
|
void
|
|
BecomeInteresting();
|
|
void
|
|
BecomeInterestingMessageHandler(const Receiver::Message*);
|
|
void
|
|
BecomeUninterestingMessageHandler();
|
|
|
|
Logical
|
|
IsInteresting()
|
|
{Check(this); return interestCount != 0;}
|
|
|
|
void
|
|
SetInterestLockedFlag();
|
|
void
|
|
SetInterestUnlockedFlag();
|
|
Logical
|
|
IsInterestLocked()
|
|
{Check(this); return (simulationFlags&InterestLockedFlag) != 0;}
|
|
|
|
void
|
|
SetInterestZoneID(InterestZoneID interest_zone_ID)
|
|
{interestZoneID = interest_zone_ID;}
|
|
InterestZoneID
|
|
GetInterestZoneID()
|
|
{return interestZoneID;}
|
|
|
|
Logical
|
|
IsStatic()
|
|
{Check(this); return (simulationFlags&DynamicFlag) == 0;}
|
|
Logical
|
|
IsDynamic()
|
|
{Check(this); return (simulationFlags&DynamicFlag) != 0;}
|
|
|
|
void
|
|
SetTrappedFlag()
|
|
{Check(this); simulationFlags |= TrappedFlag;}
|
|
Logical
|
|
IsTrapped()
|
|
{Check(this); return (simulationFlags&TrappedFlag) != 0;}
|
|
|
|
Logical
|
|
IsStatue()
|
|
{Check(this); return (simulationFlags&StatueFlag) != 0;}
|
|
|
|
Logical
|
|
IsMap()
|
|
{Check(this); return (simulationFlags&MapFlag) != 0;}
|
|
|
|
static Logical
|
|
EntityFlagsIsMap(LWord entity_flags)
|
|
{return (entity_flags&MapFlag) != 0;}
|
|
|
|
Type
|
|
GetType()
|
|
{Check(this); return (Type)(simulationFlags&TypeMask);}
|
|
|
|
virtual Enumeration
|
|
GetInterestPriority(Entity *linked_entity);
|
|
|
|
InterestZoneID
|
|
interestZoneID;
|
|
int
|
|
interestCount;
|
|
Time
|
|
creationTime;
|
|
|
|
//##########################################################################
|
|
// Camera Support
|
|
//
|
|
protected:
|
|
|
|
Origin
|
|
cameraOffset;
|
|
|
|
public:
|
|
Origin
|
|
GetCameraOffset() const
|
|
{Check(this); return cameraOffset; }
|
|
|
|
//##########################################################################
|
|
// Scoring support
|
|
//
|
|
public:
|
|
//
|
|
// HACK - ECH 7/6/95 - Allow the player vehicle to respond to score
|
|
// messages, allows attribute system to be used for scoring
|
|
//
|
|
virtual void
|
|
RespondToScoreMessage(Message *message) {}
|
|
|
|
//##########################################################################
|
|
// Construction and Destruction
|
|
//
|
|
public:
|
|
typedef Entity* (*MakeHandler)(MakeMessage *);
|
|
|
|
static Entity*
|
|
Make(MakeMessage *creation_message);
|
|
|
|
//
|
|
// Warning... This function requires a properly set up strtok!!!!
|
|
//
|
|
static Logical
|
|
CreateMakeMessage(
|
|
MakeMessage *creation_message,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories *directories
|
|
);
|
|
|
|
static Logical
|
|
CreateMakeMapMessage(
|
|
MakeMapMessage *creation_message,
|
|
ResourceDescription::ResourceID resource_id,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories * //directories
|
|
);
|
|
|
|
static ResourceDescription::ResourceID
|
|
CreateDamageZoneStream(
|
|
ResourceFile *resource_file,
|
|
const char *model_name,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories *directories
|
|
);
|
|
|
|
static ResourceDescription::ResourceID
|
|
CreateExplosionTableStream(
|
|
ResourceFile *resource_file,
|
|
const char *model_name,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories *directories
|
|
);
|
|
|
|
ResourceDescription::ResourceID
|
|
GetResourceID()
|
|
{Check(this); return resourceID;}
|
|
Player*
|
|
GetOwningPlayer()
|
|
{Check(this); return owningPlayer;}
|
|
|
|
void
|
|
SetupNetworkMessage(Entity::Message *message);
|
|
|
|
protected:
|
|
Entity(
|
|
MakeMessage *creation_message,
|
|
SharedData &virtual_data
|
|
);
|
|
|
|
ResourceDescription::ResourceID
|
|
resourceID;
|
|
Player
|
|
*owningPlayer;
|
|
|
|
void
|
|
DestroyEntityMessageHandler(Message *message);
|
|
|
|
public:
|
|
~Entity();
|
|
|
|
void
|
|
CondemnToDeathRow();
|
|
|
|
//##########################################################################
|
|
// Test Support
|
|
//
|
|
public:
|
|
Logical
|
|
TestInstance() const;
|
|
static Logical
|
|
TestClass();
|
|
};
|
|
|
|
inline void
|
|
Entity::SetupNetworkMessage(Entity::Message *message)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
message->entityID = entityID;
|
|
message->interestZoneID = interestZoneID;
|
|
message->ownerID = ownerID;
|
|
}
|
|
|
|
#include "entity3.h"
|
|
#undef ENTITY_CONTINUATION |