Cyd asked for an analysis of the networking stack and what would make the simulation feel better over the internet. The analysis found something more urgent than latency: the transport has been losing data silently since the arcade, and nothing in the game could see it happen. Every send result was discarded - L4NET, the console, all of it. On the 1ms arcade LAN the socket buffer never filled, so it never mattered. Over the internet it matters twice. A peer stalled in its own 10-30 second mission load stops reading, its window closes, and our nonblocking send starts answering would-block, which threw the message away; or worse, answering a PARTIAL count, and since framing on that stream is recovered purely from each message's length prefix, the bytes that never followed sheared it for good. Both are reachable in an ordinary race, because every race has a load in it. So sends go through a bounded per-connection queue now. What the wire will not take is kept, byte-exact, and retried at three flush points - before the render (the present blocks on vsync, and this frame's state should be travelling while it does), at the top of the receive pump, and before a connect sequence. Nothing is ever dropped from the middle: these are reliable ordered messages carrying entity creation, damage and race control, so a queue that overflows its 256K declares the connection dead and lets the disconnect path run rather than quietly desyncing the stream. RP412NETSENDQ=0 restores the old behaviour and still logs what it would have lost, which is the honest way to A/B it. On Steam the same queue finally surfaces k_EResultLimitExceeded, which the old code collapsed into -1 and discarded - that was backpressure, unlogged. The receive side gained the check the release build never had. The length prefix is untrusted input; Verify() compiles away in release, so a corrupt one went to memmove as a negative, or copied 4096 bytes of assembled packet into a 1600-byte stack buffer, or named a size the pad could never complete and wedged the connection forever. It is now validated against the same bounds the sender works to, and a stream that fails them is dropped like any other lost peer. And a fry that never ends: drop zones are map entities dealt round-robin at load, ownership transfer is not implemented, so a leaver's pads stay in the DropZones group. The respawn request dispatched to one goes to a host that is gone - dropped at the send, the 'no host N in the table' path - and the two-second retry re-dispatches to the same dead owner forever. The pad scan now skips zones whose owner has left, and re-validates one assigned earlier before reusing it. The rest is measurement, because the symptoms this work exists to chase are all reported in prose and none of them are in any log. Sixteen logs from the six-player night contain zero player-facing latency lines. A race now ends with a NetLog summary: per remote pod, how many updates arrived and how evenly (median and p95 out of a log2 histogram), the widest gap, how many gaps were long enough to mean a quiet sender versus short enough to mean OUR loop stalled, how often its motion snapped instead of blending, and how far arriving updates moved it. Per peer, whether the clock alignment ever had to step mid-race - which is the input for deciding if it needs slewing, rather than guessing. The mission t0 tick goes in the log too, alongside the console's per-pod RunMission send ticks, because nothing has ever measured how far apart the machines actually start; the clockwork doors inherit that skew directly. RP412NETSTATS adds the transport's own view - per connection: messages, bytes, wire writes, partials, refusals, how much sat queued - and on Steam the first read this codebase has ever taken of GetConnectionRealTime Status. Ping, quality, pending and unacked bytes, and one route description per connection at teardown. The API was vendored and never called; there was no RTT number anywhere in the game. Finally, rpl4opt -spoolstats reads any recording offline. The data was already in every spool ever made and nothing read it that way: the recorder restamps each packet with local arrival time while the update records inside keep the sender's sim-grid stamp, so the difference is clock offset plus one-way delay, and the same running-minimum estimator the game runs live separates them. It prints delay above the per-host minimum, and decomposes each entity's gaps into sender pacing versus delivery jitter - which no live counter can do. It lives in the game exe rather than RPL4TOOL because the tool is deliberately not /Zp1 and would misread every struct in the file. Verified on the two-pod loopback harness: mesh up, egg fed, 60s raced, stopped on command, scores collected, and both summaries reading exactly what a pair of PARKED pods should read - heartbeat cadence, one snap per heartbeat, sub-quarter-metre corrections, no clock steps. The t0 ticks and the netclock offsets agree with each other to the two seconds the pods launched apart. The latency tier is deliberately NOT here. TCP_NODELAY, the Steam NoNagle flag, per-frame coalescing and the pre-sim receive drain are all scoped and all wait on this build's numbers, because the point of shipping measurement first is to find out whether the thing we would fix is the thing that hurts. Nagle is still on. Interest management is still inert. The wire format is untouched, so this build and the last one still race each other. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
755 lines
19 KiB
C++
755 lines
19 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;
|
|
|
|
//######################################################################
|
|
//################ Race-total network statistics ###################
|
|
//######################################################################
|
|
//
|
|
// Whole-race arrival statistics for a REPLICANT entity, accumulated
|
|
// ungated (the arithmetic is a subtract and a length per arriving
|
|
// update) and printed once at mission end under RP412NETLOG - so a
|
|
// playtest log finally carries the symptoms instead of nothing. The
|
|
// windowed CamLog counters near these update sites reset every trace
|
|
// print and cover one latched entity; these cover every replicant for
|
|
// the whole race.
|
|
//
|
|
// gapHistogram is log2 milliseconds: bucket 0 is a sub-millisecond
|
|
// gap, bucket b covers [2^(b-1), 2^b) ms, bucket 15 collects
|
|
// everything from ~16 s up. Median and p95 fall out of a cumulative
|
|
// walk at print time.
|
|
//
|
|
class NetRaceStats
|
|
{
|
|
public:
|
|
unsigned long updateCount; // updates applied to this entity
|
|
Scalar widestGapSeconds; // worst inter-arrival gap (<10 s stream)
|
|
unsigned long longGapCount; // gaps > 0.200 s (sender quiet)
|
|
unsigned long queuedGapCount; // gaps < 0.005 s (our loop stalled)
|
|
unsigned long snapCount; // lerp->snap transitions while flowing
|
|
unsigned long staleCount; // rejected out-of-order records (0
|
|
// until the stale guard ships)
|
|
unsigned long correctionCount; // arriving updates that moved us
|
|
Scalar correctionTotal; // metres, for the mean
|
|
Scalar correctionWorst; // metres
|
|
unsigned long gapHistogram[16];
|
|
Logical wasSnapped; // edge detector for snapCount
|
|
|
|
NetRaceStats():
|
|
updateCount(0),
|
|
widestGapSeconds(0.0f),
|
|
longGapCount(0),
|
|
queuedGapCount(0),
|
|
snapCount(0),
|
|
staleCount(0),
|
|
correctionCount(0),
|
|
correctionTotal(0.0f),
|
|
correctionWorst(0.0f),
|
|
wasSnapped(False)
|
|
{
|
|
for (int i = 0; i < 16; ++i)
|
|
{
|
|
gapHistogram[i] = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
CountGap(Scalar seconds)
|
|
{
|
|
if (seconds > widestGapSeconds)
|
|
{
|
|
widestGapSeconds = seconds;
|
|
}
|
|
unsigned long ms = (unsigned long) (seconds * 1000.0f);
|
|
int bucket = 0;
|
|
while (ms != 0 && bucket < 15)
|
|
{
|
|
ms >>= 1;
|
|
++bucket;
|
|
}
|
|
gapHistogram[bucket]++;
|
|
}
|
|
};
|
|
|
|
NetRaceStats netRaceStats;
|
|
|
|
//
|
|
// 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 |