added everything
This commit is contained in:
1683
engine/sim/actionMap.cc
Executable file
1683
engine/sim/actionMap.cc
Executable file
File diff suppressed because it is too large
Load Diff
147
engine/sim/actionMap.h
Executable file
147
engine/sim/actionMap.h
Executable file
@ -0,0 +1,147 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _ACTIONMAP_H_
|
||||
#define _ACTIONMAP_H_
|
||||
|
||||
#ifndef _PLATFORM_H_
|
||||
#include "platform/platform.h"
|
||||
#endif
|
||||
#ifndef _TVECTOR_H_
|
||||
#include "core/tVector.h"
|
||||
#endif
|
||||
#ifndef _SIMBASE_H_
|
||||
#include "console/simBase.h"
|
||||
#endif
|
||||
|
||||
struct InputEvent;
|
||||
|
||||
struct EventDescriptor
|
||||
{
|
||||
U8 flags; ///< Combination of any modifier flags.
|
||||
U8 eventType; ///< SI_KEY, etc.
|
||||
U16 eventCode; ///< From event.h
|
||||
};
|
||||
|
||||
/// Map raw inputs to a variety of actions. This is used for all keymapping
|
||||
/// in the engine.
|
||||
/// @see ActionMap::Node
|
||||
class ActionMap : public SimObject
|
||||
{
|
||||
typedef SimObject Parent;
|
||||
|
||||
protected:
|
||||
bool onAdd();
|
||||
|
||||
struct Node {
|
||||
U32 modifiers;
|
||||
U32 action;
|
||||
|
||||
enum Flags {
|
||||
Ranged = BIT(0), ///< Ranged input.
|
||||
HasScale = BIT(1), ///< Scaled input.
|
||||
HasDeadZone = BIT(2), ///< Dead zone is present.
|
||||
Inverted = BIT(3), ///< Input is inverted.
|
||||
BindCmd = BIT(4) ///< Bind a console command to this.
|
||||
};
|
||||
|
||||
U32 flags; /// @see Node::Flags
|
||||
F32 deadZoneBegin;
|
||||
F32 deadZoneEnd;
|
||||
F32 scaleFactor;
|
||||
|
||||
StringTableEntry consoleFunction; ///< Console function to call with new values.
|
||||
|
||||
char *makeConsoleCommand; ///< Console command to execute when we make this command.
|
||||
char *breakConsoleCommand; ///< Console command to execute when we break this command.
|
||||
};
|
||||
|
||||
/// Used to represent a devices.
|
||||
struct DeviceMap
|
||||
{
|
||||
U32 deviceType;
|
||||
U32 deviceInst;
|
||||
|
||||
Vector<Node> nodeMap;
|
||||
DeviceMap() {
|
||||
VECTOR_SET_ASSOCIATION(nodeMap);
|
||||
}
|
||||
~DeviceMap();
|
||||
};
|
||||
struct BreakEntry
|
||||
{
|
||||
U32 deviceType;
|
||||
U32 deviceInst;
|
||||
U32 objInst;
|
||||
StringTableEntry consoleFunction;
|
||||
char *breakConsoleCommand;
|
||||
|
||||
// It's possible that the node could be deleted (unlikely, but possible,
|
||||
// so we replicate the node flags here...
|
||||
//
|
||||
U32 flags;
|
||||
F32 deadZoneBegin;
|
||||
F32 deadZoneEnd;
|
||||
F32 scaleFactor;
|
||||
};
|
||||
|
||||
|
||||
Vector<DeviceMap*> mDeviceMaps;
|
||||
static Vector<BreakEntry> smBreakTable;
|
||||
|
||||
// Find: return NULL if not found in current map, Get: create if not
|
||||
// found.
|
||||
const Node* findNode(const U32 inDeviceType, const U32 inDeviceInst,
|
||||
const U32 inModifiers, const U32 inAction);
|
||||
bool findBoundNode( const char* function, U32 &devMapIndex, U32 &nodeIndex );
|
||||
bool nextBoundNode( const char* function, U32 &devMapIndex, U32 &nodeIndex );
|
||||
Node* getNode(const U32 inDeviceType, const U32 inDeviceInst,
|
||||
const U32 inModifiers, const U32 inAction);
|
||||
|
||||
void removeNode(const U32 inDeviceType, const U32 inDeviceInst,
|
||||
const U32 inModifiers, const U32 inAction);
|
||||
|
||||
void enterBreakEvent(const InputEvent* pEvent, const Node* pNode);
|
||||
|
||||
static const char* getModifierString(const U32 modifiers);
|
||||
|
||||
public:
|
||||
ActionMap();
|
||||
~ActionMap();
|
||||
|
||||
void dumpActionMap(const char* fileName, const bool append) const;
|
||||
|
||||
static bool createEventDescriptor(const char* pEventString, EventDescriptor* pDescriptor);
|
||||
|
||||
bool processBind(const U32 argc, const char** argv);
|
||||
bool processBindCmd(const char *device, const char *action, const char *makeCmd, const char *breakCmd);
|
||||
bool processUnbind(const char *device, const char *action);
|
||||
|
||||
/// @name Console Interface Functions
|
||||
/// @{
|
||||
const char* getBinding( const char* command ); ///< Find what the given command is bound to.
|
||||
const char* getCommand( const char* device, const char* action ); ///< Find what command is bound to the given event descriptor .
|
||||
bool isInverted( const char* device, const char* action );
|
||||
F32 getScale( const char* device, const char* action );
|
||||
const char* getDeadZone( const char* device, const char* action );
|
||||
/// @}
|
||||
|
||||
|
||||
static bool getKeyString(const U32 action, char* buffer);
|
||||
static bool getDeviceName(const U32 deviceType, const U32 deviceInstance, char* buffer);
|
||||
static const char* buildActionString( const InputEvent* event );
|
||||
|
||||
bool processAction(const InputEvent*);
|
||||
|
||||
static bool checkBreakTable(const InputEvent*);
|
||||
static bool handleEvent(const InputEvent*);
|
||||
static bool handleEventGlobal(const InputEvent*);
|
||||
|
||||
static bool getDeviceTypeAndInstance(const char *device, U32 &deviceType, U32 &deviceInstance);
|
||||
|
||||
DECLARE_CONOBJECT(ActionMap);
|
||||
};
|
||||
|
||||
#endif // _ACTIONMAP_H_
|
172
engine/sim/connectionStringTable.cc
Executable file
172
engine/sim/connectionStringTable.cc
Executable file
@ -0,0 +1,172 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (c) 2002 GarageGames.Com
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "core/dnet.h"
|
||||
#include "console/simBase.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "core/bitStream.h"
|
||||
#include "console/consoleTypes.h"
|
||||
|
||||
class NetStringEvent : public NetEvent
|
||||
{
|
||||
StringHandle mString;
|
||||
U32 mIndex;
|
||||
public:
|
||||
NetStringEvent(U32 index = 0, StringHandle string = StringHandle())
|
||||
{
|
||||
mIndex = index;
|
||||
mString = string;
|
||||
}
|
||||
virtual void pack(NetConnection* /*ps*/, BitStream *bstream)
|
||||
{
|
||||
bstream->writeInt(mIndex, ConnectionStringTable::EntryBitSize);
|
||||
bstream->writeString(mString.getString());
|
||||
}
|
||||
virtual void write(NetConnection* /*ps*/, BitStream *bstream)
|
||||
{
|
||||
bstream->writeInt(mIndex, ConnectionStringTable::EntryBitSize);
|
||||
bstream->writeString(mString.getString());
|
||||
}
|
||||
virtual void unpack(NetConnection* /*con*/, BitStream *bstream)
|
||||
{
|
||||
char buf[256];
|
||||
mIndex = bstream->readInt(ConnectionStringTable::EntryBitSize);
|
||||
bstream->readString(buf);
|
||||
mString = StringHandle(buf);
|
||||
}
|
||||
virtual void notifyDelivered(NetConnection *ps, bool madeit)
|
||||
{
|
||||
if(madeit)
|
||||
ps->confirmStringReceived(mString, mIndex);
|
||||
}
|
||||
virtual void process(NetConnection *connection)
|
||||
{
|
||||
Con::printf("Mapping string: %s to index: %d", mString.getString(), mIndex);
|
||||
connection->mapString(mIndex, mString);
|
||||
}
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
const char *getDebugName()
|
||||
{
|
||||
static char buffer[512];
|
||||
dSprintf(buffer, sizeof(buffer), "%s - \"", getClassName());
|
||||
expandEscape(buffer + dStrlen(buffer), mString.getString());
|
||||
dStrcat(buffer, "\"");
|
||||
return buffer;
|
||||
}
|
||||
#endif
|
||||
DECLARE_CONOBJECT(NetStringEvent);
|
||||
};
|
||||
|
||||
IMPLEMENT_CO_NETEVENT_V1(NetStringEvent);
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
|
||||
ConnectionStringTable::ConnectionStringTable(NetConnection *parent)
|
||||
{
|
||||
mParent = parent;
|
||||
for(U32 i = 0; i < EntryCount; i++)
|
||||
{
|
||||
mEntryTable[i].nextHash = NULL;
|
||||
mEntryTable[i].nextLink = &mEntryTable[i+1];
|
||||
mEntryTable[i].prevLink = &mEntryTable[i-1];
|
||||
mEntryTable[i].index = i;
|
||||
|
||||
mHashTable[i] = NULL;
|
||||
}
|
||||
mLRUHead.nextLink = &mEntryTable[0];
|
||||
mEntryTable[0].prevLink = &mLRUHead;
|
||||
mLRUTail.prevLink = &mEntryTable[EntryCount-1];
|
||||
mEntryTable[EntryCount-1].nextLink = &mLRUTail;
|
||||
}
|
||||
|
||||
U32 ConnectionStringTable::getNetSendId(StringHandle &string)
|
||||
{
|
||||
// see if the entry is in the hash table right now
|
||||
U32 hashIndex = string.getIndex() % EntryCount;
|
||||
for(Entry *walk = mHashTable[hashIndex]; walk; walk = walk->nextHash)
|
||||
if(walk->string == string)
|
||||
return walk->index;
|
||||
AssertFatal(0, "Net send id is not in the table. Error!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
U32 ConnectionStringTable::checkString(StringHandle &string, bool *isOnOtherSide)
|
||||
{
|
||||
// see if the entry is in the hash table right now
|
||||
U32 hashIndex = string.getIndex() % EntryCount;
|
||||
for(Entry *walk = mHashTable[hashIndex]; walk; walk = walk->nextHash)
|
||||
{
|
||||
if(walk->string == string)
|
||||
{
|
||||
pushBack(walk);
|
||||
if(isOnOtherSide)
|
||||
*isOnOtherSide = walk->receiveConfirmed;
|
||||
return walk->index;
|
||||
}
|
||||
}
|
||||
|
||||
// not in the hash table, means we have to add it
|
||||
Entry *newEntry;
|
||||
|
||||
// pull the new entry from the LRU list.
|
||||
newEntry = mLRUHead.nextLink;
|
||||
pushBack(newEntry);
|
||||
|
||||
// remove the string from the hash table
|
||||
Entry **hashWalk;
|
||||
for (hashWalk = &mHashTable[newEntry->string.getIndex() % EntryCount]; *hashWalk; hashWalk = &((*hashWalk)->nextHash))
|
||||
{
|
||||
if(*hashWalk == newEntry)
|
||||
{
|
||||
*hashWalk = newEntry->nextHash;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
newEntry->string = string;
|
||||
newEntry->receiveConfirmed = false;
|
||||
newEntry->nextHash = mHashTable[hashIndex];
|
||||
mHashTable[hashIndex] = newEntry;
|
||||
|
||||
mParent->postNetEvent(new NetStringEvent(newEntry->index, string));
|
||||
if(isOnOtherSide)
|
||||
*isOnOtherSide = false;
|
||||
return newEntry->index;
|
||||
}
|
||||
|
||||
void ConnectionStringTable::mapString(U32 netId, StringHandle &string)
|
||||
{
|
||||
// the netId is sent by the other side... throw it in our mapping table:
|
||||
mRemoteStringTable[netId] = string;
|
||||
}
|
||||
|
||||
void ConnectionStringTable::readDemoStartBlock(BitStream *stream)
|
||||
{
|
||||
// ok, reading the demo start block
|
||||
for(U32 i = 0; i < EntryCount; i++)
|
||||
{
|
||||
if(stream->readFlag())
|
||||
{
|
||||
char buffer[256];
|
||||
stream->readString(buffer);
|
||||
mRemoteStringTable[i] = StringHandle(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionStringTable::writeDemoStartBlock(ResizeBitStream *stream)
|
||||
{
|
||||
// ok, writing the demo start block
|
||||
for(U32 i = 0; i < EntryCount; i++)
|
||||
{
|
||||
if(stream->writeFlag(mRemoteStringTable[i].isValidString()))
|
||||
{
|
||||
stream->writeString(mRemoteStringTable[i].getString());
|
||||
stream->validate();
|
||||
}
|
||||
}
|
||||
}
|
92
engine/sim/connectionStringTable.h
Executable file
92
engine/sim/connectionStringTable.h
Executable file
@ -0,0 +1,92 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (c) 2002 GarageGames.Com
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _H_CONNECTIONSTRINGTABLE
|
||||
#define _H_CONNECTIONSTRINGTABLE
|
||||
|
||||
/// Maintain a table of strings which are shared across the network.
|
||||
///
|
||||
/// This allows us to reference strings in our network streams more efficiently.
|
||||
class ConnectionStringTable
|
||||
{
|
||||
public:
|
||||
enum Constants {
|
||||
EntryCount = 32,
|
||||
EntryBitSize = 5,
|
||||
InvalidEntryId = 32,
|
||||
};
|
||||
private:
|
||||
struct Entry {
|
||||
StringHandle string; ///< Global string table entry of this string
|
||||
/// will be 0 if this string is unused.
|
||||
|
||||
U32 index; ///< index of this entry
|
||||
Entry *nextHash; ///< the next hash entry for this id
|
||||
Entry *nextLink; ///< the next in the LRU list
|
||||
Entry *prevLink; ///< the prev entry in the LRU list
|
||||
bool receiveConfirmed; ///< The other side now has this string.
|
||||
};
|
||||
|
||||
Entry mEntryTable[EntryCount];
|
||||
Entry *mHashTable[EntryCount];
|
||||
StringHandle mRemoteStringTable[EntryCount];
|
||||
Entry mLRUHead, mLRUTail;
|
||||
|
||||
/// Connection over which we are maintaining this string table.
|
||||
NetConnection *mParent;
|
||||
|
||||
inline void pushBack(Entry *entry) // pushes an entry to the back of the LRU list
|
||||
{
|
||||
entry->prevLink->nextLink = entry->nextLink;
|
||||
entry->nextLink->prevLink = entry->prevLink;
|
||||
entry->nextLink = &mLRUTail;
|
||||
entry->prevLink = mLRUTail.prevLink;
|
||||
entry->nextLink->prevLink = entry;
|
||||
entry->prevLink->nextLink = entry;
|
||||
}
|
||||
|
||||
public:
|
||||
/// Initialize the connection string table.
|
||||
///
|
||||
/// @param parent Connection over which we are maintaining this string table.
|
||||
ConnectionStringTable(NetConnection *parent);
|
||||
|
||||
/// Has the specified string been received on the other side?
|
||||
inline void confirmStringReceived(StringHandle &string, U32 index)
|
||||
{
|
||||
if(mEntryTable[index].string == string)
|
||||
mEntryTable[index].receiveConfirmed = true;
|
||||
}
|
||||
|
||||
U32 checkString(StringHandle &stringTableId, bool *stringOnOtherSide = NULL); ///< Checks if the global string ID is
|
||||
/// currently valid for this connection
|
||||
/// and returns the table ID.
|
||||
/// Sends a string event to the other side
|
||||
/// if it is not active.
|
||||
/// It will fill in stringOnOtherSide.
|
||||
|
||||
U32 getNetSendId(StringHandle &stringTableId); ///< Same return value as checkString
|
||||
/// but will assert if the string is not
|
||||
/// valid.
|
||||
|
||||
void mapString(U32 netId, StringHandle &string); ///< Maps a string that
|
||||
/// was just sent over the net
|
||||
/// to the corresponding net ID.
|
||||
|
||||
inline StringHandle lookupString(U32 netId) ///< looks up the string ID and returns
|
||||
{ /// the global string table ID for that string.
|
||||
return mRemoteStringTable[netId];
|
||||
}
|
||||
|
||||
/// @name Demo functionality
|
||||
/// @{
|
||||
|
||||
void readDemoStartBlock(BitStream *stream);
|
||||
void writeDemoStartBlock(ResizeBitStream *stream);
|
||||
/// @}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
591
engine/sim/decalManager.cc
Executable file
591
engine/sim/decalManager.cc
Executable file
@ -0,0 +1,591 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "sim/decalManager.h"
|
||||
#include "dgl/dgl.h"
|
||||
#include "sceneGraph/sceneGraph.h"
|
||||
#include "sceneGraph/sceneState.h"
|
||||
#include "ts/tsShapeInstance.h"
|
||||
#include "core/bitStream.h"
|
||||
#include "console/consoleTypes.h"
|
||||
|
||||
bool DecalManager::smDecalsOn = true;
|
||||
bool DecalManager::sgThisIsSelfIlluminated = false;
|
||||
bool DecalManager::sgLastWasSelfIlluminated = false;
|
||||
const U32 DecalManager::csmFreePoolBlockSize = 256;
|
||||
U32 DecalManager::smMaxNumDecals = 256;
|
||||
U32 DecalManager::smDecalTimeout = 5000;
|
||||
|
||||
DecalManager* gDecalManager = NULL;
|
||||
IMPLEMENT_CONOBJECT(DecalManager);
|
||||
IMPLEMENT_CO_DATABLOCK_V1(DecalData);
|
||||
|
||||
namespace {
|
||||
|
||||
int QSORT_CALLBACK cmpDecalInstance(const void* p1, const void* p2)
|
||||
{
|
||||
const DecalInstance** pd1 = (const DecalInstance**)p1;
|
||||
const DecalInstance** pd2 = (const DecalInstance**)p2;
|
||||
|
||||
return int(((char *)(*pd1)->decalData) - ((char *)(*pd2)->decalData));
|
||||
}
|
||||
|
||||
} // namespace {}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
DecalData::DecalData()
|
||||
{
|
||||
sizeX = 1;
|
||||
sizeY = 1;
|
||||
textureName = "";
|
||||
|
||||
selfIlluminated = false;
|
||||
lifeSpan = DecalManager::smDecalTimeout;
|
||||
}
|
||||
|
||||
DecalData::~DecalData()
|
||||
{
|
||||
if(gDecalManager)
|
||||
gDecalManager->dataDeleted(this);
|
||||
}
|
||||
|
||||
|
||||
void DecalData::packData(BitStream* stream)
|
||||
{
|
||||
Parent::packData(stream);
|
||||
|
||||
stream->write(sizeX);
|
||||
stream->write(sizeY);
|
||||
stream->writeString(textureName);
|
||||
|
||||
stream->write(selfIlluminated);
|
||||
stream->write(lifeSpan);
|
||||
}
|
||||
|
||||
void DecalData::unpackData(BitStream* stream)
|
||||
{
|
||||
Parent::unpackData(stream);
|
||||
|
||||
stream->read(&sizeX);
|
||||
stream->read(&sizeY);
|
||||
textureName = stream->readSTString();
|
||||
|
||||
stream->read(&selfIlluminated);
|
||||
stream->read(&lifeSpan);
|
||||
}
|
||||
|
||||
bool DecalData::preload(bool server, char errorBuffer[256])
|
||||
{
|
||||
if (Parent::preload(server, errorBuffer) == false)
|
||||
return false;
|
||||
|
||||
if (sizeX < 0.0) {
|
||||
Con::warnf("DecalData::preload: sizeX < 0");
|
||||
sizeX = 0;
|
||||
}
|
||||
if (sizeY < 0.0) {
|
||||
Con::warnf("DecalData::preload: sizeX < 0");
|
||||
sizeY = 0;
|
||||
}
|
||||
if (textureName == NULL || textureName[0] == '\0') {
|
||||
Con::errorf("No texture name for decal!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!server) {
|
||||
textureHandle = TextureHandle(textureName, MeshTexture);
|
||||
if (textureHandle.getGLName() == 0) {
|
||||
Con::errorf("Unable to load texture: %s for decal!", textureName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_CONSOLETYPE(DecalData)
|
||||
IMPLEMENT_SETDATATYPE(DecalData)
|
||||
IMPLEMENT_GETDATATYPE(DecalData)
|
||||
|
||||
void DecalData::initPersistFields()
|
||||
{
|
||||
addField("sizeX", TypeF32, Offset(sizeX, DecalData));
|
||||
addField("sizeY", TypeF32, Offset(sizeY, DecalData));
|
||||
addField("textureName", TypeFilename, Offset(textureName, DecalData));
|
||||
|
||||
addField("SelfIlluminated", TypeBool, Offset(selfIlluminated, DecalData));
|
||||
addField("LifeSpan", TypeS32, Offset(lifeSpan, DecalData));
|
||||
}
|
||||
|
||||
DecalManager::DecalManager()
|
||||
{
|
||||
mTypeMask |= DecalManagerObjectType;
|
||||
|
||||
mObjBox.min.set(-1e7, -1e7, -1e7);
|
||||
mObjBox.max.set( 1e7, 1e7, 1e7);
|
||||
mWorldBox.min.set(-1e7, -1e7, -1e7);
|
||||
mWorldBox.max.set( 1e7, 1e7, 1e7);
|
||||
|
||||
mFreePool = NULL;
|
||||
VECTOR_SET_ASSOCIATION(mDecalQueue);
|
||||
VECTOR_SET_ASSOCIATION(mFreePoolBlocks);
|
||||
}
|
||||
|
||||
|
||||
DecalManager::~DecalManager()
|
||||
{
|
||||
mFreePool = NULL;
|
||||
for (S32 i = 0; i < mFreePoolBlocks.size(); i++)
|
||||
{
|
||||
delete [] mFreePoolBlocks[i];
|
||||
}
|
||||
mDecalQueue.clear();
|
||||
}
|
||||
|
||||
|
||||
DecalInstance* DecalManager::allocateDecalInstance()
|
||||
{
|
||||
if (mFreePool == NULL)
|
||||
{
|
||||
// Allocate a new block of decals
|
||||
mFreePoolBlocks.push_back(new DecalInstance[csmFreePoolBlockSize]);
|
||||
|
||||
// Init them onto the free pool chain
|
||||
DecalInstance* pNewInstances = mFreePoolBlocks.last();
|
||||
for (U32 i = 0; i < csmFreePoolBlockSize - 1; i++)
|
||||
pNewInstances[i].next = &pNewInstances[i + 1];
|
||||
pNewInstances[csmFreePoolBlockSize - 1].next = NULL;
|
||||
mFreePool = pNewInstances;
|
||||
}
|
||||
AssertFatal(mFreePool != NULL, "Error, should always have a free pool available here!");
|
||||
|
||||
DecalInstance* pRet = mFreePool;
|
||||
mFreePool = pRet->next;
|
||||
pRet->next = NULL;
|
||||
return pRet;
|
||||
}
|
||||
|
||||
|
||||
void DecalManager::freeDecalInstance(DecalInstance* trash)
|
||||
{
|
||||
AssertFatal(trash != NULL, "Error, no trash pointer to free!");
|
||||
|
||||
trash->next = mFreePool;
|
||||
mFreePool = trash;
|
||||
}
|
||||
|
||||
|
||||
void DecalManager::dataDeleted(DecalData *data)
|
||||
{
|
||||
for(S32 i = mDecalQueue.size() - 1; i >= 0; i--)
|
||||
{
|
||||
DecalInstance *inst = mDecalQueue[i];
|
||||
if(inst->decalData == data)
|
||||
{
|
||||
freeDecalInstance(inst);
|
||||
mDecalQueue.erase(U32(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DecalManager::consoleInit()
|
||||
{
|
||||
Con::addVariable("$pref::decalsOn", TypeBool, &smDecalsOn);
|
||||
Con::addVariable("$pref::Decal::maxNumDecals", TypeS32, &smMaxNumDecals);
|
||||
Con::addVariable("$pref::Decal::decalTimeout", TypeS32, &smDecalTimeout);
|
||||
}
|
||||
|
||||
void DecalManager::addDecal(const Point3F& pos,
|
||||
Point3F normal,
|
||||
DecalData* decalData)
|
||||
{
|
||||
if (smMaxNumDecals == 0)
|
||||
return;
|
||||
|
||||
// DMM: Rework this, should be based on time
|
||||
if(mDecalQueue.size() >= smMaxNumDecals)
|
||||
{
|
||||
findSpace();
|
||||
}
|
||||
|
||||
Point3F vecX, vecY;
|
||||
DecalInstance* newDecal = allocateDecalInstance();
|
||||
newDecal->decalData = decalData;
|
||||
newDecal->allocTime = Platform::getVirtualMilliseconds();
|
||||
|
||||
if(mFabs(normal.z) > 0.9f)
|
||||
mCross(normal, Point3F(0.0f, 1.0f, 0.0f), &vecX);
|
||||
else
|
||||
mCross(normal, Point3F(0.0f, 0.0f, 1.0f), &vecX);
|
||||
|
||||
mCross(vecX, normal, &vecY);
|
||||
|
||||
normal.normalizeSafe();
|
||||
Point3F position = Point3F(pos.x + (normal.x * 0.008), pos.y + (normal.y * 0.008), pos.z + (normal.z * 0.008));
|
||||
|
||||
vecX.normalizeSafe();
|
||||
vecY.normalizeSafe();
|
||||
|
||||
vecX *= decalData->sizeX;
|
||||
vecY *= decalData->sizeY;
|
||||
|
||||
newDecal->point[0] = position + vecX + vecY;
|
||||
newDecal->point[1] = position + vecX - vecY;
|
||||
newDecal->point[2] = position - vecX - vecY;
|
||||
newDecal->point[3] = position - vecX + vecY;
|
||||
|
||||
mDecalQueue.push_back(newDecal);
|
||||
mQueueDirty = true;
|
||||
}
|
||||
|
||||
void DecalManager::addDecal(const Point3F& pos,
|
||||
const Point3F& rot,
|
||||
Point3F normal,
|
||||
DecalData* decalData)
|
||||
{
|
||||
if (smMaxNumDecals == 0)
|
||||
return;
|
||||
|
||||
addDecal( pos, rot, normal, Point3F( 1, 1, 1 ), decalData );
|
||||
}
|
||||
|
||||
void DecalManager::addDecal(const Point3F& pos,
|
||||
const Point3F& rot,
|
||||
Point3F normal,
|
||||
const Point3F& scale,
|
||||
DecalData* decalData)
|
||||
{
|
||||
if (smMaxNumDecals == 0)
|
||||
return;
|
||||
|
||||
if(mDot(rot, normal) < 0.98)
|
||||
{
|
||||
// DMM: Rework this, should be based on time
|
||||
if(mDecalQueue.size() >= smMaxNumDecals)
|
||||
{
|
||||
findSpace();
|
||||
}
|
||||
|
||||
Point3F vecX, vecY;
|
||||
DecalInstance* newDecal = allocateDecalInstance();
|
||||
newDecal->decalData = decalData;
|
||||
newDecal->allocTime = Platform::getVirtualMilliseconds();
|
||||
|
||||
mCross(rot, normal, &vecX);
|
||||
mCross(normal, vecX, &vecY);
|
||||
|
||||
normal.normalize();
|
||||
Point3F position = Point3F(pos.x + (normal.x * 0.008), pos.y + (normal.y * 0.008), pos.z + (normal.z * 0.008));
|
||||
|
||||
vecX.normalize();
|
||||
vecX.convolve( scale );
|
||||
vecY.normalize();
|
||||
vecY.convolve( scale );
|
||||
|
||||
vecX *= decalData->sizeX;
|
||||
vecY *= decalData->sizeY;
|
||||
|
||||
newDecal->point[0] = position + vecX + vecY;
|
||||
newDecal->point[1] = position + vecX - vecY;
|
||||
newDecal->point[2] = position - vecX - vecY;
|
||||
newDecal->point[3] = position - vecX + vecY;
|
||||
|
||||
mDecalQueue.push_back(newDecal);
|
||||
mQueueDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool DecalManager::prepRenderImage(SceneState* state, const U32 stateKey,
|
||||
const U32 /*startZone*/, const bool /*modifyBaseState*/)
|
||||
{
|
||||
if (!smDecalsOn) return false;
|
||||
|
||||
if (isLastState(state, stateKey))
|
||||
return false;
|
||||
setLastState(state, stateKey);
|
||||
|
||||
if (mDecalQueue.size() == 0)
|
||||
return false;
|
||||
|
||||
// This should be sufficient for most objects that don't manage zones, and
|
||||
// don't need to return a specialized RenderImage...
|
||||
SceneRenderImage* image = new SceneRenderImage;
|
||||
image->obj = this;
|
||||
image->isTranslucent = true;
|
||||
image->sortType = SceneRenderImage::BeginSort;
|
||||
state->insertRenderImage(image);
|
||||
|
||||
U32 currMs = Platform::getVirtualMilliseconds();
|
||||
for (S32 i = mDecalQueue.size() - 1; i >= 0; i--)
|
||||
{
|
||||
U32 age = currMs - mDecalQueue[i]->allocTime;
|
||||
U32 timeout = mDecalQueue[i]->decalData->lifeSpan;
|
||||
if (age > timeout)
|
||||
{
|
||||
freeDecalInstance(mDecalQueue[i]);
|
||||
mDecalQueue.erase(i);
|
||||
}
|
||||
else if (age > ((3 * timeout) / 4))
|
||||
{
|
||||
mDecalQueue[i]->fade = 1.0f - (F32(age - ((3 * timeout) / 4)) / F32(timeout / 4));
|
||||
}
|
||||
else
|
||||
{
|
||||
mDecalQueue[i]->fade = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (mQueueDirty == true)
|
||||
{
|
||||
// Sort the decals based on the data pointers...
|
||||
dQsort(mDecalQueue.address(),
|
||||
mDecalQueue.size(),
|
||||
sizeof(DecalInstance*),
|
||||
cmpDecalInstance);
|
||||
mQueueDirty = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DecalManager::renderObject(SceneState* state, SceneRenderImage*)
|
||||
{
|
||||
if (!smDecalsOn) return;
|
||||
|
||||
AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry");
|
||||
|
||||
RectI viewport;
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glPushMatrix();
|
||||
dglGetViewport(&viewport);
|
||||
|
||||
state->setupBaseProjection();
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glPushMatrix();
|
||||
|
||||
renderDecal();
|
||||
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glPopMatrix();
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glPopMatrix();
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
dglSetViewport(viewport);
|
||||
|
||||
AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit");
|
||||
}
|
||||
|
||||
struct DecalVert
|
||||
{
|
||||
Point3F vert;
|
||||
Point2F texCoord;
|
||||
ColorI color;
|
||||
};
|
||||
|
||||
void DecalManager::renderDecal()
|
||||
{
|
||||
static Vector<DecalVert> renderVerts(__FILE__, __LINE__);
|
||||
static Vector<U16> indices(__FILE__, __LINE__);
|
||||
renderVerts.clear();
|
||||
indices.clear();
|
||||
U32 decalCount = 0;
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glEnable(GL_BLEND);
|
||||
glEnable(GL_ALPHA_TEST);
|
||||
glDepthMask(GL_FALSE);
|
||||
glAlphaFunc(GL_GREATER, 0.1f);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
||||
|
||||
sgThisIsSelfIlluminated = false;
|
||||
sgLastWasSelfIlluminated = false;
|
||||
glEnable(GL_POLYGON_OFFSET_FILL);
|
||||
glPolygonOffset(-1,-1);
|
||||
glDepthMask(GL_FALSE);
|
||||
|
||||
DecalData* pLastData = NULL;
|
||||
for (S32 x = 0; x < mDecalQueue.size(); x++)
|
||||
{
|
||||
if (mDecalQueue[x]->decalData != pLastData)
|
||||
{
|
||||
glVertexPointer(3, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].vert));
|
||||
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(DecalVert), &(renderVerts[0].color));
|
||||
glTexCoordPointer(2, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].texCoord));
|
||||
|
||||
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, indices.address());
|
||||
|
||||
renderVerts.clear();
|
||||
indices.clear();
|
||||
decalCount = 0;
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, mDecalQueue[x]->decalData->textureHandle.getGLName());
|
||||
pLastData = mDecalQueue[x]->decalData;
|
||||
}
|
||||
|
||||
sgThisIsSelfIlluminated = mDecalQueue[x]->decalData->selfIlluminated;
|
||||
if(sgThisIsSelfIlluminated != sgLastWasSelfIlluminated)
|
||||
{
|
||||
glVertexPointer(3, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].vert));
|
||||
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(DecalVert), &(renderVerts[0].color));
|
||||
glTexCoordPointer(2, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].texCoord));
|
||||
|
||||
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, indices.address());
|
||||
|
||||
renderVerts.clear();
|
||||
indices.clear();
|
||||
decalCount = 0;
|
||||
|
||||
if(sgThisIsSelfIlluminated)
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
||||
else
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
sgLastWasSelfIlluminated = sgThisIsSelfIlluminated;
|
||||
}
|
||||
|
||||
renderVerts.increment(4);
|
||||
indices.increment(6);
|
||||
DecalVert *verts = &(renderVerts[decalCount * 4]);
|
||||
U16 *ind = &(indices[decalCount * 6]);
|
||||
|
||||
const U8 fade = mDecalQueue[x]->fade * 255;
|
||||
|
||||
verts[0].vert = mDecalQueue[x]->point[3];
|
||||
verts[0].texCoord.set(0, 0);
|
||||
verts[0].color.set(255, 255, 255, fade);
|
||||
ind[0] = decalCount * 4;
|
||||
ind[5] = decalCount * 4;
|
||||
|
||||
verts[1].vert = mDecalQueue[x]->point[2];
|
||||
verts[1].texCoord.set(0, 1);
|
||||
verts[1].color.set(255, 255, 255, fade);
|
||||
ind[1] = decalCount * 4 + 1;
|
||||
|
||||
verts[2].vert = mDecalQueue[x]->point[1];
|
||||
verts[2].texCoord.set(1, 1);
|
||||
verts[2].color.set(255, 255, 255, fade);
|
||||
ind[2] = decalCount * 4 + 2;
|
||||
ind[3] = decalCount * 4 + 2;
|
||||
|
||||
verts[3].vert = mDecalQueue[x]->point[0];
|
||||
verts[3].texCoord.set(1, 0);
|
||||
verts[3].color.set(255, 255, 255, fade);
|
||||
ind[4] = decalCount * 4 + 3;
|
||||
|
||||
decalCount++;
|
||||
}
|
||||
|
||||
if(decalCount)
|
||||
{
|
||||
glVertexPointer(3, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].vert));
|
||||
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(DecalVert), &(renderVerts[0].color));
|
||||
glTexCoordPointer(2, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].texCoord));
|
||||
|
||||
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, indices.address());
|
||||
}
|
||||
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
glDisable(GL_POLYGON_OFFSET_FILL);
|
||||
glDepthMask(GL_TRUE);
|
||||
|
||||
glDepthMask(GL_TRUE);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
}
|
||||
|
||||
void DecalManager::findSpace()
|
||||
{
|
||||
S32 besttime = S32_MAX;
|
||||
U32 bestindex = 0;
|
||||
DecalInstance *bestdecal = NULL;
|
||||
|
||||
U32 time = Platform::getVirtualMilliseconds();
|
||||
|
||||
for(U32 i=0; i<mDecalQueue.size(); i++)
|
||||
{
|
||||
DecalInstance *inst = mDecalQueue[i];
|
||||
U32 age = time - inst->allocTime;
|
||||
U32 timeleft = inst->decalData->lifeSpan - age;
|
||||
if(besttime > timeleft)
|
||||
{
|
||||
besttime = timeleft;
|
||||
bestindex = i;
|
||||
bestdecal = inst;
|
||||
}
|
||||
}
|
||||
|
||||
AssertFatal((bestdecal), "No good decals?");
|
||||
|
||||
mDecalQueue.erase_fast(bestindex);
|
||||
freeDecalInstance(bestdecal);
|
||||
}
|
||||
|
||||
void DecalManager::addDecal(const Point3F& pos, const Point3F& rot, Point3F normal,
|
||||
const Point3F& scale, DecalData *decaldata, U32 ownerid)
|
||||
{
|
||||
if(smMaxNumDecals == 0)
|
||||
return;
|
||||
|
||||
if(mDot(rot, normal) < 0.98)
|
||||
{
|
||||
if(mDecalQueue.size() >= smMaxNumDecals)
|
||||
findSpace();
|
||||
|
||||
Point3F vecX, vecY;
|
||||
DecalInstance* newDecal = allocateDecalInstance();
|
||||
newDecal->decalData = decaldata;
|
||||
newDecal->allocTime = Platform::getVirtualMilliseconds();
|
||||
newDecal->ownerId = ownerid;
|
||||
|
||||
mCross(rot, normal, &vecX);
|
||||
mCross(normal, vecX, &vecY);
|
||||
|
||||
normal.normalize();
|
||||
Point3F position = Point3F(pos.x + (normal.x * 0.008), pos.y + (normal.y * 0.008), pos.z + (normal.z * 0.008));
|
||||
|
||||
vecX.normalize();
|
||||
vecX.convolve( scale );
|
||||
vecY.normalize();
|
||||
vecY.convolve( scale );
|
||||
|
||||
vecX *= decaldata->sizeX;
|
||||
vecY *= decaldata->sizeY;
|
||||
|
||||
newDecal->point[0] = position + vecX + vecY;
|
||||
newDecal->point[1] = position + vecX - vecY;
|
||||
newDecal->point[2] = position - vecX - vecY;
|
||||
newDecal->point[3] = position - vecX + vecY;
|
||||
|
||||
mDecalQueue.push_back(newDecal);
|
||||
mQueueDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DecalManager::ageDecal(U32 ownerid)
|
||||
{
|
||||
for(U32 i=0; i<mDecalQueue.size(); i++)
|
||||
{
|
||||
DecalInstance *inst = mDecalQueue[i];
|
||||
if(inst->ownerId == ownerid)
|
||||
{
|
||||
freeDecalInstance(inst);
|
||||
mDecalQueue.erase(U32(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
130
engine/sim/decalManager.h
Executable file
130
engine/sim/decalManager.h
Executable file
@ -0,0 +1,130 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _DECALMANAGER_H_
|
||||
#define _DECALMANAGER_H_
|
||||
|
||||
#ifndef _SCENEOBJECT_H_
|
||||
#include "sim/sceneObject.h"
|
||||
#endif
|
||||
#ifndef _GTEXMANAGER_H_
|
||||
#include "dgl/gTexManager.h"
|
||||
#endif
|
||||
|
||||
#include "game/gameBase.h"
|
||||
|
||||
/// DataBlock implementation for decals.
|
||||
class DecalData : public GameBaseData
|
||||
{
|
||||
typedef GameBaseData Parent;
|
||||
|
||||
//-------------------------------------- Console set variables
|
||||
public:
|
||||
F32 sizeX;
|
||||
F32 sizeY;
|
||||
StringTableEntry textureName;
|
||||
|
||||
bool selfIlluminated;
|
||||
U32 lifeSpan;
|
||||
|
||||
//-------------------------------------- load set variables
|
||||
public:
|
||||
TextureHandle textureHandle;
|
||||
|
||||
public:
|
||||
DecalData();
|
||||
~DecalData();
|
||||
|
||||
void packData(BitStream*);
|
||||
void unpackData(BitStream*);
|
||||
bool preload(bool server, char errorBuffer[256]);
|
||||
|
||||
DECLARE_CONOBJECT(DecalData);
|
||||
static void initPersistFields();
|
||||
};
|
||||
DECLARE_CONSOLETYPE(DecalData)
|
||||
|
||||
/// Store an instance of a decal.
|
||||
struct DecalInstance
|
||||
{
|
||||
DecalData* decalData;
|
||||
Point3F point[4];
|
||||
|
||||
U32 ownerId;
|
||||
|
||||
U32 allocTime;
|
||||
F32 fade;
|
||||
DecalInstance* next;
|
||||
};
|
||||
|
||||
/// Manage decals in the world.
|
||||
class DecalManager : public SceneObject
|
||||
{
|
||||
typedef SceneObject Parent;
|
||||
|
||||
Vector<DecalInstance*> mDecalQueue;
|
||||
bool mQueueDirty;
|
||||
|
||||
public:
|
||||
void addDecal(const Point3F& pos,
|
||||
const Point3F& rot,
|
||||
Point3F normal,
|
||||
const Point3F& scale,
|
||||
DecalData*, U32);
|
||||
void ageDecal(U32);
|
||||
void findSpace();
|
||||
|
||||
static U32 smMaxNumDecals;
|
||||
static U32 smDecalTimeout;
|
||||
|
||||
static bool sgThisIsSelfIlluminated;
|
||||
static bool sgLastWasSelfIlluminated;
|
||||
|
||||
static const U32 csmFreePoolBlockSize;
|
||||
Vector<DecalInstance*> mFreePoolBlocks;
|
||||
DecalInstance* mFreePool;
|
||||
|
||||
protected:
|
||||
bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState);
|
||||
void renderObject(SceneState *state, SceneRenderImage *image);
|
||||
|
||||
DecalInstance* allocateDecalInstance();
|
||||
void freeDecalInstance(DecalInstance*);
|
||||
|
||||
public:
|
||||
DecalManager();
|
||||
~DecalManager();
|
||||
|
||||
static void consoleInit();
|
||||
|
||||
/// @name Decal Addition
|
||||
///
|
||||
/// These functions allow you to add new decals to the world.
|
||||
/// @{
|
||||
void addDecal(const Point3F& pos,
|
||||
const Point3F& rot,
|
||||
Point3F normal,
|
||||
const Point3F& scale,
|
||||
DecalData*);
|
||||
void addDecal(const Point3F& pos,
|
||||
const Point3F& rot,
|
||||
Point3F normal,
|
||||
DecalData*);
|
||||
void addDecal(const Point3F& pos,
|
||||
Point3F normal,
|
||||
DecalData*);
|
||||
/// @}
|
||||
|
||||
void dataDeleted(DecalData *data);
|
||||
|
||||
void renderDecal();
|
||||
DECLARE_CONOBJECT(DecalManager);
|
||||
|
||||
static bool smDecalsOn;
|
||||
};
|
||||
|
||||
extern DecalManager* gDecalManager;
|
||||
|
||||
#endif // _H_DecalManager
|
1193
engine/sim/netConnection.cc
Executable file
1193
engine/sim/netConnection.cc
Executable file
File diff suppressed because it is too large
Load Diff
1105
engine/sim/netConnection.h
Executable file
1105
engine/sim/netConnection.h
Executable file
File diff suppressed because it is too large
Load Diff
244
engine/sim/netDownload.cc
Executable file
244
engine/sim/netDownload.cc
Executable file
@ -0,0 +1,244 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (c) GarageGames.Com
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "core/dnet.h"
|
||||
#include "console/simBase.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "core/bitStream.h"
|
||||
#include "sim/netObject.h"
|
||||
#include "core/resManager.h"
|
||||
|
||||
class FileDownloadRequestEvent : public NetEvent
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
MaxFileNames = 31,
|
||||
};
|
||||
|
||||
U32 nameCount;
|
||||
char mFileNames[MaxFileNames][256];
|
||||
|
||||
FileDownloadRequestEvent(Vector<char *> *nameList = NULL)
|
||||
{
|
||||
nameCount = 0;
|
||||
if(nameList)
|
||||
{
|
||||
nameCount = nameList->size();
|
||||
|
||||
if(nameCount > MaxFileNames)
|
||||
nameCount = MaxFileNames;
|
||||
|
||||
for(U32 i = 0; i < nameCount; i++)
|
||||
{
|
||||
dStrcpy(mFileNames[i], (*nameList)[i]);
|
||||
Con::printf("Sending request for file %s", mFileNames[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void pack(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
bstream->writeRangedU32(nameCount, 0, MaxFileNames);
|
||||
for(U32 i = 0; i < nameCount; i++)
|
||||
bstream->writeString(mFileNames[i]);
|
||||
}
|
||||
|
||||
virtual void write(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
bstream->writeRangedU32(nameCount, 0, MaxFileNames);
|
||||
for(U32 i = 0; i < nameCount; i++)
|
||||
bstream->writeString(mFileNames[i]);
|
||||
}
|
||||
|
||||
virtual void unpack(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
nameCount = bstream->readRangedU32(0, MaxFileNames);
|
||||
for(U32 i = 0; i < nameCount; i++)
|
||||
bstream->readString(mFileNames[i]);
|
||||
}
|
||||
|
||||
virtual void process(NetConnection *connection)
|
||||
{
|
||||
U32 i;
|
||||
for(i = 0; i < nameCount; i++)
|
||||
if(connection->startSendingFile(mFileNames[i]))
|
||||
break;
|
||||
if(i == nameCount)
|
||||
connection->startSendingFile(NULL); // none of the files were sent
|
||||
}
|
||||
|
||||
DECLARE_CONOBJECT(FileDownloadRequestEvent);
|
||||
|
||||
};
|
||||
|
||||
IMPLEMENT_CO_NETEVENT_V1(FileDownloadRequestEvent);
|
||||
|
||||
class FileChunkEvent : public NetEvent
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
ChunkSize = 63,
|
||||
};
|
||||
|
||||
U8 chunkData[ChunkSize];
|
||||
U32 chunkLen;
|
||||
|
||||
FileChunkEvent(U8 *data = NULL, U32 len = 0)
|
||||
{
|
||||
if(data)
|
||||
dMemcpy(chunkData, data, len);
|
||||
chunkLen = len;
|
||||
}
|
||||
|
||||
virtual void pack(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
bstream->writeRangedU32(chunkLen, 0, ChunkSize);
|
||||
bstream->write(chunkLen, chunkData);
|
||||
}
|
||||
|
||||
virtual void write(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
bstream->writeRangedU32(chunkLen, 0, ChunkSize);
|
||||
bstream->write(chunkLen, chunkData);
|
||||
}
|
||||
|
||||
virtual void unpack(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
chunkLen = bstream->readRangedU32(0, ChunkSize);
|
||||
bstream->read(chunkLen, chunkData);
|
||||
}
|
||||
|
||||
virtual void process(NetConnection *connection)
|
||||
{
|
||||
connection->chunkReceived(chunkData, chunkLen);
|
||||
}
|
||||
|
||||
virtual void notifyDelivered(NetConnection *nc, bool madeIt)
|
||||
{
|
||||
if(!nc->isRemoved())
|
||||
nc->sendFileChunk();
|
||||
}
|
||||
|
||||
DECLARE_CONOBJECT(FileChunkEvent);
|
||||
};
|
||||
|
||||
IMPLEMENT_CO_NETEVENT_V1(FileChunkEvent);
|
||||
|
||||
void NetConnection::sendFileChunk()
|
||||
{
|
||||
U8 buffer[FileChunkEvent::ChunkSize];
|
||||
U32 len = FileChunkEvent::ChunkSize;
|
||||
if(len + mCurrentFileBufferOffset > mCurrentFileBufferSize)
|
||||
len = mCurrentFileBufferSize - mCurrentFileBufferOffset;
|
||||
|
||||
if(!len)
|
||||
{
|
||||
ResourceManager->closeStream(mCurrentDownloadingFile);
|
||||
mCurrentDownloadingFile = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
mCurrentFileBufferOffset += len;
|
||||
mCurrentDownloadingFile->read(len, buffer);
|
||||
postNetEvent(new FileChunkEvent(buffer, len));
|
||||
}
|
||||
|
||||
bool NetConnection::startSendingFile(const char *fileName)
|
||||
{
|
||||
if(!fileName || Con::getBoolVariable("$NetConnection::neverUploadFiles"))
|
||||
{
|
||||
sendConnectionMessage(SendNextDownloadRequest);
|
||||
return false;
|
||||
}
|
||||
|
||||
mCurrentDownloadingFile = ResourceManager->openStream(fileName);
|
||||
|
||||
if(!mCurrentDownloadingFile)
|
||||
{
|
||||
// the server didn't have the file, so send a 0 byte chunk:
|
||||
Con::printf("No such file '%s'.", fileName);
|
||||
postNetEvent(new FileChunkEvent(NULL, 0));
|
||||
return false;
|
||||
}
|
||||
|
||||
Con::printf("Sending file '%s'.", fileName);
|
||||
mCurrentFileBufferSize = mCurrentDownloadingFile->getStreamSize();
|
||||
mCurrentFileBufferOffset = 0;
|
||||
|
||||
// always have 32 file chunks (64 bytes each) in transit
|
||||
sendConnectionMessage(FileDownloadSizeMessage, mCurrentFileBufferSize);
|
||||
for(U32 i = 0; i < 32; i++)
|
||||
sendFileChunk();
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetConnection::sendNextFileDownloadRequest()
|
||||
{
|
||||
// see if we've already downloaded this file...
|
||||
while(mMissingFileList.size() && (ResourceManager->find(mMissingFileList[0]) || Con::getBoolVariable("$NetConnection::neverDownloadFiles")))
|
||||
{
|
||||
dFree(mMissingFileList[0]);
|
||||
mMissingFileList.pop_front();
|
||||
}
|
||||
|
||||
if(mMissingFileList.size())
|
||||
{
|
||||
postNetEvent(new FileDownloadRequestEvent(&mMissingFileList));
|
||||
}
|
||||
else
|
||||
{
|
||||
fileDownloadSegmentComplete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void NetConnection::chunkReceived(U8 *chunkData, U32 chunkLen)
|
||||
{
|
||||
if(chunkLen == 0)
|
||||
{
|
||||
// the server didn't have the file... apparently it's one we don't need...
|
||||
dFree(mCurrentFileBuffer);
|
||||
mCurrentFileBuffer = NULL;
|
||||
dFree(mMissingFileList[0]);
|
||||
mMissingFileList.pop_front();
|
||||
return;
|
||||
}
|
||||
if(chunkLen + mCurrentFileBufferOffset > mCurrentFileBufferSize)
|
||||
{
|
||||
setLastError("Invalid file chunk from server.");
|
||||
return;
|
||||
}
|
||||
dMemcpy(((U8 *) mCurrentFileBuffer) + mCurrentFileBufferOffset, chunkData, chunkLen);
|
||||
mCurrentFileBufferOffset += chunkLen;
|
||||
if(mCurrentFileBufferOffset == mCurrentFileBufferSize)
|
||||
{
|
||||
// this file's done...
|
||||
// save it to disk:
|
||||
FileStream stream;
|
||||
|
||||
Con::printf("Saving file %s.", mMissingFileList[0]);
|
||||
if(!ResourceManager->openFileForWrite(stream, mMissingFileList[0]))
|
||||
{
|
||||
setLastError("Couldn't open file downloaded by server.");
|
||||
return;
|
||||
}
|
||||
dFree(mMissingFileList[0]);
|
||||
mMissingFileList.pop_front();
|
||||
stream.write(mCurrentFileBufferSize, mCurrentFileBuffer);
|
||||
stream.close();
|
||||
mNumDownloadedFiles++;
|
||||
dFree(mCurrentFileBuffer);
|
||||
mCurrentFileBuffer = NULL;
|
||||
sendNextFileDownloadRequest();
|
||||
}
|
||||
else
|
||||
{
|
||||
Con::executef(4, "onFileChunkReceived", mMissingFileList[0], Con::getIntArg(mCurrentFileBufferOffset), Con::getIntArg(mCurrentFileBufferSize));
|
||||
}
|
||||
}
|
||||
|
421
engine/sim/netEvent.cc
Executable file
421
engine/sim/netEvent.cc
Executable file
@ -0,0 +1,421 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "core/dnet.h"
|
||||
#include "console/simBase.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "core/bitStream.h"
|
||||
|
||||
#define DebugChecksum 0xF00DBAAD
|
||||
|
||||
FreeListChunker<NetEventNote> NetConnection::mEventNoteChunker;
|
||||
|
||||
NetEvent::~NetEvent()
|
||||
{
|
||||
AssertWarn(mRefCount == 0, "NetEvent::~NetEvent - encountered non-zero ref count!");
|
||||
}
|
||||
|
||||
void NetEvent::notifyDelivered(NetConnection *, bool)
|
||||
{
|
||||
}
|
||||
|
||||
void NetEvent::notifySent(NetConnection *)
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
const char *NetEvent::getDebugName()
|
||||
{
|
||||
return getClassName();
|
||||
}
|
||||
#endif
|
||||
|
||||
void NetConnection::eventOnRemove()
|
||||
{
|
||||
while(mNotifyEventList)
|
||||
{
|
||||
NetEventNote *temp = mNotifyEventList;
|
||||
mNotifyEventList = temp->mNextEvent;
|
||||
|
||||
temp->mEvent->notifyDelivered(this, true);
|
||||
temp->mEvent->decRef();
|
||||
mEventNoteChunker.free(temp);
|
||||
}
|
||||
|
||||
while(mUnorderedSendEventQueueHead)
|
||||
{
|
||||
NetEventNote *temp = mUnorderedSendEventQueueHead;
|
||||
mUnorderedSendEventQueueHead = temp->mNextEvent;
|
||||
|
||||
temp->mEvent->notifyDelivered(this, true);
|
||||
temp->mEvent->decRef();
|
||||
mEventNoteChunker.free(temp);
|
||||
}
|
||||
|
||||
while(mSendEventQueueHead)
|
||||
{
|
||||
NetEventNote *temp = mSendEventQueueHead;
|
||||
mSendEventQueueHead = temp->mNextEvent;
|
||||
|
||||
temp->mEvent->notifyDelivered(this, true);
|
||||
temp->mEvent->decRef();
|
||||
mEventNoteChunker.free(temp);
|
||||
}
|
||||
}
|
||||
|
||||
void NetConnection::eventPacketDropped(PacketNotify *notify)
|
||||
{
|
||||
NetEventNote *walk = notify->eventList;
|
||||
NetEventNote **insertList = &mSendEventQueueHead;
|
||||
NetEventNote *temp;
|
||||
|
||||
while(walk)
|
||||
{
|
||||
switch(walk->mEvent->mGuaranteeType)
|
||||
{
|
||||
// It was a guaranteed ordered packet, reinsert it back into
|
||||
// mSendEventQueueHead in the right place (based on seq numbers)
|
||||
case NetEvent::GuaranteedOrdered:
|
||||
|
||||
//Con::printf("EVT %d: DROP - %d", getId(), walk->mSeqCount);
|
||||
|
||||
while(*insertList && (*insertList)->mSeqCount < walk->mSeqCount)
|
||||
insertList = &((*insertList)->mNextEvent);
|
||||
|
||||
temp = walk->mNextEvent;
|
||||
walk->mNextEvent = *insertList;
|
||||
if(!walk->mNextEvent)
|
||||
mSendEventQueueTail = walk;
|
||||
*insertList = walk;
|
||||
insertList = &(walk->mNextEvent);
|
||||
walk = temp;
|
||||
break;
|
||||
|
||||
// It was a guaranteed packet, put it at the top of
|
||||
// mUnorderedSendEventQueueHead.
|
||||
case NetEvent::Guaranteed:
|
||||
temp = walk->mNextEvent;
|
||||
walk->mNextEvent = mUnorderedSendEventQueueHead;
|
||||
mUnorderedSendEventQueueHead = walk;
|
||||
if(!walk->mNextEvent)
|
||||
mUnorderedSendEventQueueTail = walk;
|
||||
walk = temp;
|
||||
break;
|
||||
|
||||
// Or else it was an unguaranteed packet, notify that
|
||||
// it was _not_ delivered and blast it.
|
||||
case NetEvent::Unguaranteed:
|
||||
walk->mEvent->notifyDelivered(this, false);
|
||||
walk->mEvent->decRef();
|
||||
temp = walk->mNextEvent;
|
||||
mEventNoteChunker.free(walk);
|
||||
walk = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetConnection::eventPacketReceived(PacketNotify *notify)
|
||||
{
|
||||
NetEventNote *walk = notify->eventList;
|
||||
NetEventNote **noteList = &mNotifyEventList;
|
||||
|
||||
while(walk)
|
||||
{
|
||||
NetEventNote *next = walk->mNextEvent;
|
||||
if(walk->mEvent->mGuaranteeType != NetEvent::GuaranteedOrdered)
|
||||
{
|
||||
walk->mEvent->notifyDelivered(this, true);
|
||||
walk->mEvent->decRef();
|
||||
mEventNoteChunker.free(walk);
|
||||
walk = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
while(*noteList && (*noteList)->mSeqCount < walk->mSeqCount)
|
||||
noteList = &((*noteList)->mNextEvent);
|
||||
|
||||
walk->mNextEvent = *noteList;
|
||||
*noteList = walk;
|
||||
noteList = &walk->mNextEvent;
|
||||
walk = next;
|
||||
}
|
||||
}
|
||||
while(mNotifyEventList && mNotifyEventList->mSeqCount == mLastAckedEventSeq + 1)
|
||||
{
|
||||
mLastAckedEventSeq++;
|
||||
NetEventNote *next = mNotifyEventList->mNextEvent;
|
||||
//Con::printf("EVT %d: ACK - %d", getId(), mNotifyEventList->mSeqCount);
|
||||
mNotifyEventList->mEvent->notifyDelivered(this, true);
|
||||
mNotifyEventList->mEvent->decRef();
|
||||
mEventNoteChunker.free(mNotifyEventList);
|
||||
mNotifyEventList = next;
|
||||
}
|
||||
}
|
||||
|
||||
void NetConnection::eventWritePacket(BitStream *bstream, PacketNotify *notify)
|
||||
{
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
bstream->writeInt(DebugChecksum, 32);
|
||||
#endif
|
||||
|
||||
NetEventNote *packQueueHead = NULL, *packQueueTail = NULL;
|
||||
|
||||
while(mUnorderedSendEventQueueHead)
|
||||
{
|
||||
if(bstream->isFull())
|
||||
break;
|
||||
// dequeue the first event
|
||||
NetEventNote *ev = mUnorderedSendEventQueueHead;
|
||||
mUnorderedSendEventQueueHead = ev->mNextEvent;
|
||||
U32 start = bstream->getCurPos();
|
||||
|
||||
bstream->writeFlag(true);
|
||||
S32 classId = ev->mEvent->getClassId(getNetClassGroup());
|
||||
bstream->writeClassId(classId, NetClassTypeEvent, getNetClassGroup());
|
||||
|
||||
ev->mEvent->pack(this, bstream);
|
||||
DEBUG_LOG(("PKLOG %d EVENT %d: %s", getId(), bstream->getCurPos() - start, ev->mEvent->getDebugName()) );
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
bstream->writeInt(classId ^ DebugChecksum, 32);
|
||||
#endif
|
||||
// add this event onto the packet queue
|
||||
ev->mNextEvent = NULL;
|
||||
if(!packQueueHead)
|
||||
packQueueHead = ev;
|
||||
else
|
||||
packQueueTail->mNextEvent = ev;
|
||||
packQueueTail = ev;
|
||||
}
|
||||
|
||||
bstream->writeFlag(false);
|
||||
S32 prevSeq = -2;
|
||||
|
||||
while(mSendEventQueueHead)
|
||||
{
|
||||
if(bstream->isFull())
|
||||
break;
|
||||
|
||||
// if the event window is full, stop processing
|
||||
if(mSendEventQueueHead->mSeqCount > mLastAckedEventSeq + 126)
|
||||
break;
|
||||
|
||||
// dequeue the first event
|
||||
NetEventNote *ev = mSendEventQueueHead;
|
||||
mSendEventQueueHead = ev->mNextEvent;
|
||||
|
||||
//Con::printf("EVT %d: SEND - %d", getId(), ev->mSeqCount);
|
||||
|
||||
bstream->writeFlag(true);
|
||||
|
||||
ev->mNextEvent = NULL;
|
||||
if(!packQueueHead)
|
||||
packQueueHead = ev;
|
||||
else
|
||||
packQueueTail->mNextEvent = ev;
|
||||
packQueueTail = ev;
|
||||
if(!bstream->writeFlag(ev->mSeqCount == prevSeq + 1))
|
||||
bstream->writeInt(ev->mSeqCount, 7);
|
||||
|
||||
prevSeq = ev->mSeqCount;
|
||||
|
||||
U32 start = bstream->getCurPos();
|
||||
S32 classId = ev->mEvent->getClassId(getNetClassGroup());
|
||||
bstream->writeClassId(classId, NetClassTypeEvent, getNetClassGroup());
|
||||
ev->mEvent->pack(this, bstream);
|
||||
DEBUG_LOG(("PKLOG %d EVENT %d: %s", getId(), bstream->getCurPos() - start, ev->mEvent->getDebugName()) );
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
bstream->writeInt(classId ^ DebugChecksum, 32);
|
||||
#endif
|
||||
}
|
||||
for(NetEventNote *ev = packQueueHead; ev; ev = ev->mNextEvent)
|
||||
ev->mEvent->notifySent(this);
|
||||
|
||||
notify->eventList = packQueueHead;
|
||||
bstream->writeFlag(0);
|
||||
}
|
||||
|
||||
void NetConnection::eventReadPacket(BitStream *bstream)
|
||||
{
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
U32 sum = bstream->readInt(32);
|
||||
AssertISV(sum == DebugChecksum, "Invalid checksum.");
|
||||
#endif
|
||||
|
||||
S32 prevSeq = -2;
|
||||
NetEventNote **waitInsert = &mWaitSeqEvents;
|
||||
bool unguaranteedPhase = true;
|
||||
|
||||
while(true)
|
||||
{
|
||||
bool bit = bstream->readFlag();
|
||||
if(unguaranteedPhase && !bit)
|
||||
{
|
||||
unguaranteedPhase = false;
|
||||
bit = bstream->readFlag();
|
||||
}
|
||||
if(!unguaranteedPhase && !bit)
|
||||
break;
|
||||
|
||||
S32 seq = -1;
|
||||
|
||||
if(!unguaranteedPhase) // get the sequence
|
||||
{
|
||||
if(bstream->readFlag())
|
||||
seq = (prevSeq + 1) & 0x7f;
|
||||
else
|
||||
seq = bstream->readInt(7);
|
||||
prevSeq = seq;
|
||||
}
|
||||
S32 classId = bstream->readClassId(NetClassTypeEvent, getNetClassGroup());
|
||||
if(classId == -1)
|
||||
{
|
||||
setLastError("Invalid packet.");
|
||||
return;
|
||||
}
|
||||
NetEvent *evt = (NetEvent *) ConsoleObject::create(getNetClassGroup(), NetClassTypeEvent, classId);
|
||||
if(!evt)
|
||||
{
|
||||
setLastError("Invalid packet.");
|
||||
return;
|
||||
}
|
||||
|
||||
evt->incRef();
|
||||
|
||||
AbstractClassRep *rep = evt->getClassRep();
|
||||
if((rep->mNetEventDir == NetEventDirServerToClient && !isConnectionToServer())
|
||||
|| (rep->mNetEventDir == NetEventDirClientToServer && isConnectionToServer()) )
|
||||
{
|
||||
evt->decRef();
|
||||
setLastError("Invalid Packet.");
|
||||
return;
|
||||
}
|
||||
|
||||
evt->mSourceId = getId();
|
||||
evt->unpack(this, bstream);
|
||||
if(mErrorBuffer[0])
|
||||
{
|
||||
evt->decRef();
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
U32 checksum = bstream->readInt(32);
|
||||
AssertISV( (checksum ^ DebugChecksum) == (U32)classId,
|
||||
avar("unpack did not match pack for event of class %s.",
|
||||
evt->getClassName()) );
|
||||
#endif
|
||||
|
||||
if(unguaranteedPhase)
|
||||
{
|
||||
evt->process(this);
|
||||
evt->decRef();
|
||||
if(mErrorBuffer[0])
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
seq |= (mNextRecvEventSeq & ~0x7F);
|
||||
if(seq < mNextRecvEventSeq)
|
||||
seq += 128;
|
||||
|
||||
NetEventNote *note = mEventNoteChunker.alloc();
|
||||
note->mEvent = evt;
|
||||
note->mEvent->incRef();
|
||||
|
||||
note->mSeqCount = seq;
|
||||
//Con::printf("EVT %d: RECV - %d", getId(), evt->mSeqCount);
|
||||
while(*waitInsert && (*waitInsert)->mSeqCount < seq)
|
||||
waitInsert = &((*waitInsert)->mNextEvent);
|
||||
|
||||
note->mNextEvent = *waitInsert;
|
||||
*waitInsert = note;
|
||||
waitInsert = &(note->mNextEvent);
|
||||
}
|
||||
while(mWaitSeqEvents && mWaitSeqEvents->mSeqCount == mNextRecvEventSeq)
|
||||
{
|
||||
mNextRecvEventSeq++;
|
||||
NetEventNote *temp = mWaitSeqEvents;
|
||||
mWaitSeqEvents = temp->mNextEvent;
|
||||
|
||||
//Con::printf("EVT %d: PROCESS - %d", getId(), temp->mSeqCount);
|
||||
temp->mEvent->process(this);
|
||||
temp->mEvent->decRef();
|
||||
mEventNoteChunker.free(temp);
|
||||
if(mErrorBuffer[0])
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetConnection::postNetEvent(NetEvent *theEvent)
|
||||
{
|
||||
if(!mSendingEvents)
|
||||
{
|
||||
theEvent->decRef();
|
||||
return false;
|
||||
}
|
||||
NetEventNote *event = mEventNoteChunker.alloc();
|
||||
event->mEvent = theEvent;
|
||||
theEvent->incRef();
|
||||
|
||||
event->mNextEvent = NULL;
|
||||
if(theEvent->mGuaranteeType == NetEvent::GuaranteedOrdered)
|
||||
{
|
||||
event->mSeqCount = mNextSendEventSeq++;
|
||||
if(!mSendEventQueueHead)
|
||||
mSendEventQueueHead = event;
|
||||
else
|
||||
mSendEventQueueTail->mNextEvent = event;
|
||||
mSendEventQueueTail = event;
|
||||
}
|
||||
else
|
||||
{
|
||||
event->mSeqCount = InvalidSendEventSeq;
|
||||
if(!mUnorderedSendEventQueueHead)
|
||||
mUnorderedSendEventQueueHead = event;
|
||||
else
|
||||
mUnorderedSendEventQueueTail->mNextEvent = event;
|
||||
mUnorderedSendEventQueueTail = event;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void NetConnection::eventWriteStartBlock(ResizeBitStream *stream)
|
||||
{
|
||||
stream->write(mNextRecvEventSeq);
|
||||
for(NetEventNote *walk = mWaitSeqEvents; walk; walk = walk->mNextEvent)
|
||||
{
|
||||
stream->writeFlag(true);
|
||||
S32 classId = walk->mEvent->getClassId(getNetClassGroup());
|
||||
stream->writeClassId(classId, NetClassTypeEvent, getNetClassGroup());
|
||||
walk->mEvent->write(this, stream);
|
||||
stream->validate();
|
||||
}
|
||||
stream->writeFlag(false);
|
||||
}
|
||||
|
||||
void NetConnection::eventReadStartBlock(BitStream *stream)
|
||||
{
|
||||
stream->read(&mNextRecvEventSeq);
|
||||
|
||||
NetEventNote *lastEvent = NULL;
|
||||
while(stream->readFlag())
|
||||
{
|
||||
S32 classTag = stream->readClassId(NetClassTypeEvent, getNetClassGroup());
|
||||
NetEvent *evt = (NetEvent *) ConsoleObject::create(getNetClassGroup(), NetClassTypeEvent, classTag);
|
||||
evt->unpack(this, stream);
|
||||
NetEventNote *add = mEventNoteChunker.alloc();
|
||||
add->mEvent = evt;
|
||||
evt->incRef();
|
||||
add->mNextEvent = NULL;
|
||||
|
||||
if(!lastEvent)
|
||||
mWaitSeqEvents = add;
|
||||
else
|
||||
lastEvent->mNextEvent = add;
|
||||
lastEvent = add;
|
||||
}
|
||||
}
|
1103
engine/sim/netGhost.cc
Executable file
1103
engine/sim/netGhost.cc
Executable file
File diff suppressed because it is too large
Load Diff
622
engine/sim/netInterface.cc
Executable file
622
engine/sim/netInterface.cc
Executable file
@ -0,0 +1,622 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (c) 2002 GarageGames.Com
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "platform/event.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "sim/netInterface.h"
|
||||
#include "core/bitStream.h"
|
||||
#include "math/mRandom.h"
|
||||
#include "platform/gameInterface.h"
|
||||
|
||||
NetInterface *GNet = NULL;
|
||||
|
||||
NetInterface::NetInterface()
|
||||
{
|
||||
AssertFatal(GNet == NULL, "ERROR: Multiple net interfaces declared.");
|
||||
GNet = this;
|
||||
|
||||
mLastTimeoutCheckTime = 0;
|
||||
mAllowConnections = true;
|
||||
|
||||
}
|
||||
|
||||
void NetInterface::initRandomData()
|
||||
{
|
||||
mRandomDataInitialized = true;
|
||||
U32 seed = Platform::getRealMilliseconds();
|
||||
|
||||
if(Game->isJournalReading())
|
||||
Game->journalRead(&seed);
|
||||
else if(Game->isJournalWriting())
|
||||
Game->journalWrite(seed);
|
||||
|
||||
MRandomR250 myRandom(seed);
|
||||
for(U32 i = 0; i < 12; i++)
|
||||
mRandomHashData[i] = myRandom.randI();
|
||||
}
|
||||
|
||||
void NetInterface::addPendingConnection(NetConnection *connection)
|
||||
{
|
||||
Con::printf("Adding a pending connection");
|
||||
mPendingConnections.push_back(connection);
|
||||
}
|
||||
|
||||
void NetInterface::removePendingConnection(NetConnection *connection)
|
||||
{
|
||||
for(U32 i = 0; i < mPendingConnections.size(); i++)
|
||||
if(mPendingConnections[i] == connection)
|
||||
mPendingConnections.erase(i);
|
||||
}
|
||||
|
||||
NetConnection *NetInterface::findPendingConnection(const NetAddress *address, U32 connectSequence)
|
||||
{
|
||||
for(U32 i = 0; i < mPendingConnections.size(); i++)
|
||||
if(Net::compareAddresses(address, mPendingConnections[i]->getNetAddress()) &&
|
||||
connectSequence == mPendingConnections[i]->getSequence())
|
||||
return mPendingConnections[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void NetInterface::processPacketReceiveEvent(PacketReceiveEvent *prEvent)
|
||||
{
|
||||
|
||||
U32 dataSize = prEvent->size - PacketReceiveEventHeaderSize;
|
||||
BitStream pStream(prEvent->data, dataSize);
|
||||
|
||||
// Determine what to do with this packet:
|
||||
|
||||
if(prEvent->data[0] & 0x01) // it's a protocol packet...
|
||||
{
|
||||
// if the LSB of the first byte is set, it's a game data packet
|
||||
// so pass it to the appropriate connection.
|
||||
|
||||
// lookup the connection in the addressTable
|
||||
NetConnection *conn = NetConnection::lookup(&prEvent->sourceAddress);
|
||||
if(conn)
|
||||
conn->processRawPacket(&pStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, it's either a game info packet or a
|
||||
// connection handshake packet.
|
||||
|
||||
U8 packetType;
|
||||
pStream.read(&packetType);
|
||||
NetAddress *addr = &prEvent->sourceAddress;
|
||||
|
||||
if(packetType <= GameHeartbeat)
|
||||
handleInfoPacket(addr, packetType, &pStream);
|
||||
else
|
||||
{
|
||||
// check if there's a connection already:
|
||||
NetConnection *conn;
|
||||
switch(packetType)
|
||||
{
|
||||
case ConnectChallengeRequest:
|
||||
handleConnectChallengeRequest(addr, &pStream);
|
||||
break;
|
||||
case ConnectRequest:
|
||||
handleConnectRequest(addr, &pStream);
|
||||
break;
|
||||
case ConnectChallengeResponse:
|
||||
handleConnectChallengeResponse(addr, &pStream);
|
||||
break;
|
||||
case ConnectAccept:
|
||||
handleConnectAccept(addr, &pStream);
|
||||
break;
|
||||
case Disconnect:
|
||||
handleDisconnect(addr, &pStream);
|
||||
break;
|
||||
case ConnectReject:
|
||||
handleConnectReject(addr, &pStream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
// Connection handshaking basic overview:
|
||||
// The torque engine does a two phase connect handshake to
|
||||
// prevent a spoofed source address Denial-of-Service (DOS) attack
|
||||
//
|
||||
// Basically, the initiator of a connection (client) sends a
|
||||
// Connect Challenge Request packet to the server to initiate the connection
|
||||
// The server then hashes the source address of the client request
|
||||
// with some random magic server data to come up with a 16-byte key that
|
||||
// the client can then use to gain entry to the server.
|
||||
// This way there are no partially active connection records on the
|
||||
// server at all.
|
||||
//
|
||||
// The client then sends a Connect Request packet to the server,
|
||||
// including any game specific data necessary to start a connection (a
|
||||
// server password, for instance), along with the key the server sent
|
||||
// on the Connect Challenge Response packet.
|
||||
//
|
||||
// The server, on receipt of the Connect Request, compares the
|
||||
// entry key with a computed key, makes sure it can create the requested
|
||||
// NetConnection subclass, and then passes all processing on to the connection
|
||||
// instance.
|
||||
//
|
||||
// If the subclass reads and accepts he connect request successfully, the
|
||||
// server sends a Connect Accept packet - otherwise the connection
|
||||
// is rejected with the sendConnectReject function
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
void NetInterface::sendConnectChallengeRequest(NetConnection *conn)
|
||||
{
|
||||
|
||||
Con::printf("Sending Connect challenge Request");
|
||||
BitStream *out = BitStream::getPacketStream();
|
||||
out->write(U8(ConnectChallengeRequest));
|
||||
out->write(conn->getSequence());
|
||||
conn->mConnectSendCount++;
|
||||
conn->mConnectLastSendTime = Platform::getVirtualMilliseconds();
|
||||
BitStream::sendPacketStream(conn->getNetAddress());
|
||||
}
|
||||
|
||||
void NetInterface::handleConnectChallengeRequest(const NetAddress *addr, BitStream *stream)
|
||||
{
|
||||
char buf[256];
|
||||
Net::addressToString(addr, buf);
|
||||
Con::printf("Got Connect challenge Request from %s", buf);
|
||||
if(!mAllowConnections)
|
||||
return;
|
||||
|
||||
U32 connectSequence;
|
||||
stream->read(&connectSequence);
|
||||
|
||||
if(!mRandomDataInitialized)
|
||||
initRandomData();
|
||||
|
||||
U32 addressDigest[4];
|
||||
computeNetMD5(addr, connectSequence, addressDigest);
|
||||
|
||||
BitStream *out = BitStream::getPacketStream();
|
||||
out->write(U8(ConnectChallengeResponse));
|
||||
out->write(connectSequence);
|
||||
out->write(addressDigest[0]);
|
||||
out->write(addressDigest[1]);
|
||||
out->write(addressDigest[2]);
|
||||
out->write(addressDigest[3]);
|
||||
|
||||
BitStream::sendPacketStream(addr);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void NetInterface::handleConnectChallengeResponse(const NetAddress *address, BitStream *stream)
|
||||
{
|
||||
Con::printf("Got Connect challenge Response");
|
||||
U32 connectSequence;
|
||||
stream->read(&connectSequence);
|
||||
|
||||
NetConnection *conn = findPendingConnection(address, connectSequence);
|
||||
if(!conn || conn->getConnectionState() != NetConnection::AwaitingChallengeResponse)
|
||||
return;
|
||||
|
||||
U32 addressDigest[4];
|
||||
stream->read(&addressDigest[0]);
|
||||
stream->read(&addressDigest[1]);
|
||||
stream->read(&addressDigest[2]);
|
||||
stream->read(&addressDigest[3]);
|
||||
conn->setAddressDigest(addressDigest);
|
||||
|
||||
conn->setConnectionState(NetConnection::AwaitingConnectResponse);
|
||||
conn->mConnectSendCount = 0;
|
||||
Con::printf("Sending Connect Request");
|
||||
sendConnectRequest(conn);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void NetInterface::sendConnectRequest(NetConnection *conn)
|
||||
{
|
||||
BitStream *out = BitStream::getPacketStream();
|
||||
out->write(U8(ConnectRequest));
|
||||
out->write(conn->getSequence());
|
||||
|
||||
U32 addressDigest[4];
|
||||
conn->getAddressDigest(addressDigest);
|
||||
out->write(addressDigest[0]);
|
||||
out->write(addressDigest[1]);
|
||||
out->write(addressDigest[2]);
|
||||
out->write(addressDigest[3]);
|
||||
|
||||
out->writeString(conn->getClassName());
|
||||
conn->writeConnectRequest(out);
|
||||
conn->mConnectSendCount++;
|
||||
conn->mConnectLastSendTime = Platform::getVirtualMilliseconds();
|
||||
|
||||
BitStream::sendPacketStream(conn->getNetAddress());
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void NetInterface::handleConnectRequest(const NetAddress *address, BitStream *stream)
|
||||
{
|
||||
if(!mAllowConnections)
|
||||
return;
|
||||
Con::printf("Got Connect Request");
|
||||
U32 connectSequence;
|
||||
stream->read(&connectSequence);
|
||||
|
||||
// see if the connection is in the main connection table:
|
||||
|
||||
NetConnection *connect = NetConnection::lookup(address);
|
||||
if(connect && connect->getSequence() == connectSequence)
|
||||
{
|
||||
sendConnectAccept(connect);
|
||||
return;
|
||||
}
|
||||
U32 addressDigest[4];
|
||||
U32 computedAddressDigest[4];
|
||||
|
||||
stream->read(&addressDigest[0]);
|
||||
stream->read(&addressDigest[1]);
|
||||
stream->read(&addressDigest[2]);
|
||||
stream->read(&addressDigest[3]);
|
||||
|
||||
computeNetMD5(address, connectSequence, computedAddressDigest);
|
||||
if(addressDigest[0] != computedAddressDigest[0] ||
|
||||
addressDigest[1] != computedAddressDigest[1] ||
|
||||
addressDigest[2] != computedAddressDigest[2] ||
|
||||
addressDigest[3] != computedAddressDigest[3])
|
||||
return; // bogus connection attempt
|
||||
|
||||
if(connect)
|
||||
{
|
||||
if(connect->getSequence() > connectSequence)
|
||||
return; // the existing connection should be kept - the incoming request is stale.
|
||||
else
|
||||
connect->deleteObject(); // disconnect this one, and allow the new one to be created.
|
||||
}
|
||||
|
||||
char connectionClass[255];
|
||||
stream->readString(connectionClass);
|
||||
|
||||
ConsoleObject *co = ConsoleObject::create(connectionClass);
|
||||
NetConnection *conn = dynamic_cast<NetConnection *>(co);
|
||||
if(!conn || !conn->canRemoteCreate())
|
||||
{
|
||||
delete co;
|
||||
return;
|
||||
}
|
||||
conn->registerObject();
|
||||
conn->setNetAddress(address);
|
||||
conn->setNetworkConnection(true);
|
||||
conn->setSequence(connectSequence);
|
||||
|
||||
const char *errorString = NULL;
|
||||
if(!conn->readConnectRequest(stream, &errorString))
|
||||
{
|
||||
sendConnectReject(conn, errorString);
|
||||
conn->deleteObject();
|
||||
return;
|
||||
}
|
||||
conn->setNetworkConnection(true);
|
||||
conn->onConnectionEstablished(false);
|
||||
conn->setEstablished();
|
||||
conn->setConnectSequence(connectSequence);
|
||||
sendConnectAccept(conn);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void NetInterface::sendConnectAccept(NetConnection *conn)
|
||||
{
|
||||
BitStream *out = BitStream::getPacketStream();
|
||||
out->write(U8(ConnectAccept));
|
||||
out->write(conn->getSequence());
|
||||
conn->writeConnectAccept(out);
|
||||
BitStream::sendPacketStream(conn->getNetAddress());
|
||||
}
|
||||
|
||||
void NetInterface::handleConnectAccept(const NetAddress *address, BitStream *stream)
|
||||
{
|
||||
U32 connectSequence;
|
||||
stream->read(&connectSequence);
|
||||
NetConnection *conn = findPendingConnection(address, connectSequence);
|
||||
if(!conn || conn->getConnectionState() != NetConnection::AwaitingConnectResponse)
|
||||
return;
|
||||
const char *errorString = NULL;
|
||||
if(!conn->readConnectAccept(stream, &errorString))
|
||||
{
|
||||
conn->handleStartupError(errorString);
|
||||
removePendingConnection(conn);
|
||||
conn->deleteObject();
|
||||
return;
|
||||
}
|
||||
|
||||
removePendingConnection(conn); // remove from the pending connection list
|
||||
conn->setNetworkConnection(true);
|
||||
conn->onConnectionEstablished(true); // notify the connection that it has been established
|
||||
conn->setEstablished(); // installs the connection in the connection table, and causes pings/timeouts to happen
|
||||
conn->setConnectSequence(connectSequence);
|
||||
}
|
||||
|
||||
void NetInterface::sendConnectReject(NetConnection *conn, const char *reason)
|
||||
{
|
||||
if(!reason)
|
||||
return; // if the stream is NULL, we reject silently
|
||||
|
||||
BitStream *out = BitStream::getPacketStream();
|
||||
out->write(U8(ConnectReject));
|
||||
out->write(conn->getSequence());
|
||||
out->writeString(reason);
|
||||
BitStream::sendPacketStream(conn->getNetAddress());
|
||||
}
|
||||
|
||||
void NetInterface::handleConnectReject(const NetAddress *address, BitStream *stream)
|
||||
{
|
||||
U32 connectSequence;
|
||||
stream->read(&connectSequence);
|
||||
NetConnection *conn = findPendingConnection(address, connectSequence);
|
||||
if(!conn || (conn->getConnectionState() != NetConnection::AwaitingChallengeResponse &&
|
||||
conn->getConnectionState() != NetConnection::AwaitingConnectResponse))
|
||||
return;
|
||||
removePendingConnection(conn);
|
||||
char reason[256];
|
||||
stream->readString(reason);
|
||||
conn->onConnectionRejected(reason);
|
||||
conn->deleteObject();
|
||||
}
|
||||
|
||||
void NetInterface::handleDisconnect(const NetAddress *address, BitStream *stream)
|
||||
{
|
||||
NetConnection *conn = NetConnection::lookup(address);
|
||||
if(!conn)
|
||||
return;
|
||||
|
||||
U32 connectSequence;
|
||||
char reason[256];
|
||||
|
||||
stream->read(&connectSequence);
|
||||
stream->readString(reason);
|
||||
|
||||
if(conn->getSequence() != connectSequence)
|
||||
return;
|
||||
|
||||
conn->onDisconnect(reason);
|
||||
conn->deleteObject();
|
||||
}
|
||||
|
||||
void NetInterface::handleInfoPacket(const NetAddress *address, U8 packetType, BitStream *stream)
|
||||
{
|
||||
}
|
||||
|
||||
void NetInterface::processClient()
|
||||
{
|
||||
NetObject::collapseDirtyList(); // collapse all the mask bits...
|
||||
for(NetConnection *walk = NetConnection::getConnectionList();
|
||||
walk; walk = walk->getNext())
|
||||
{
|
||||
if(walk->isConnectionToServer() && (walk->isLocalConnection() || walk->isNetworkConnection()))
|
||||
walk->checkPacketSend(false);
|
||||
}
|
||||
}
|
||||
|
||||
void NetInterface::processServer()
|
||||
{
|
||||
NetObject::collapseDirtyList(); // collapse all the mask bits...
|
||||
for(NetConnection *walk = NetConnection::getConnectionList();
|
||||
walk; walk = walk->getNext())
|
||||
{
|
||||
if(!walk->isConnectionToServer() && (walk->isLocalConnection() || walk->isNetworkConnection()))
|
||||
walk->checkPacketSend(false);
|
||||
}
|
||||
}
|
||||
|
||||
void NetInterface::startConnection(NetConnection *conn)
|
||||
{
|
||||
addPendingConnection(conn);
|
||||
conn->mConnectionSendCount = 0;
|
||||
conn->setConnectSequence(Platform::getVirtualMilliseconds());
|
||||
conn->setConnectionState(NetConnection::AwaitingChallengeResponse);
|
||||
|
||||
// This is a the client side of the connection, so set the connection to
|
||||
// server flag. We need to set this early so that if the connection times
|
||||
// out, its onRemove() will handle the cleanup properly.
|
||||
conn->setIsConnectionToServer();
|
||||
|
||||
// Everything set, so send off the request.
|
||||
sendConnectChallengeRequest(conn);
|
||||
}
|
||||
|
||||
void NetInterface::sendDisconnectPacket(NetConnection *conn, const char *reason)
|
||||
{
|
||||
Con::printf("Issuing Disconnect packet.");
|
||||
|
||||
// send a disconnect packet...
|
||||
U32 connectSequence = conn->getSequence();
|
||||
|
||||
BitStream *out = BitStream::getPacketStream();
|
||||
out->write(U8(Disconnect));
|
||||
out->write(connectSequence);
|
||||
out->writeString(reason);
|
||||
|
||||
BitStream::sendPacketStream(conn->getNetAddress());
|
||||
}
|
||||
|
||||
void NetInterface::checkTimeouts()
|
||||
{
|
||||
U32 time = Platform::getVirtualMilliseconds();
|
||||
if(time > mLastTimeoutCheckTime + TimeoutCheckInterval)
|
||||
{
|
||||
for(U32 i = 0; i < mPendingConnections.size();)
|
||||
{
|
||||
NetConnection *pending = mPendingConnections[i];
|
||||
|
||||
if(pending->getConnectionState() == NetConnection::AwaitingChallengeResponse &&
|
||||
time > pending->mConnectLastSendTime + ChallengeRetryTime)
|
||||
{
|
||||
if(pending->mConnectSendCount > ChallengeRetryCount)
|
||||
{
|
||||
pending->onConnectTimedOut();
|
||||
removePendingConnection(pending);
|
||||
pending->deleteObject();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
sendConnectChallengeRequest(pending);
|
||||
}
|
||||
else if(pending->getConnectionState() == NetConnection::AwaitingConnectResponse &&
|
||||
time > pending->mConnectLastSendTime + ConnectRetryTime)
|
||||
{
|
||||
if(pending->mConnectSendCount > ConnectRetryCount)
|
||||
{
|
||||
pending->onConnectTimedOut();
|
||||
removePendingConnection(pending);
|
||||
pending->deleteObject();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
sendConnectRequest(pending);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
mLastTimeoutCheckTime = time;
|
||||
NetConnection *walk = NetConnection::getConnectionList();
|
||||
|
||||
while(walk)
|
||||
{
|
||||
NetConnection *next = walk->getNext();
|
||||
if(walk->checkTimeout(time))
|
||||
{
|
||||
// this baddie timed out
|
||||
walk->onTimedOut();
|
||||
walk->deleteObject();
|
||||
}
|
||||
walk = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define F1(x, y, z) (z ^ (x & (y ^ z)))
|
||||
#define F2(x, y, z) F1(z, x, y)
|
||||
#define F3(x, y, z) (x ^ y ^ z)
|
||||
#define F4(x, y, z) (y ^ (x | ~z))
|
||||
|
||||
inline U32 rotlFixed(U32 x, unsigned int y)
|
||||
{
|
||||
return (x >> y) | (x << (32 - y));
|
||||
}
|
||||
|
||||
#define MD5STEP(f, w, x, y, z, data, s) w = rotlFixed(w + f(x, y, z) + data, s) + x
|
||||
|
||||
void NetInterface::computeNetMD5(const NetAddress *address, U32 connectSequence, U32 digest[4])
|
||||
{
|
||||
digest[0] = 0x67452301L;
|
||||
digest[1] = 0xefcdab89L;
|
||||
digest[2] = 0x98badcfeL;
|
||||
digest[3] = 0x10325476L;
|
||||
|
||||
|
||||
U32 a, b, c, d;
|
||||
|
||||
a=digest[0];
|
||||
b=digest[1];
|
||||
c=digest[2];
|
||||
d=digest[3];
|
||||
|
||||
U32 in[16];
|
||||
in[0] = address->type;
|
||||
in[1] = (U32(address->netNum[0]) << 24) |
|
||||
(U32(address->netNum[1]) << 16) |
|
||||
(U32(address->netNum[2]) << 8) |
|
||||
(U32(address->netNum[3]));
|
||||
in[2] = address->port;
|
||||
in[3] = connectSequence;
|
||||
for(U32 i = 0; i < 12; i++)
|
||||
in[i + 4] = mRandomHashData[i];
|
||||
|
||||
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
|
||||
|
||||
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
|
||||
|
||||
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
|
||||
|
||||
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
|
||||
|
||||
digest[0]+=a;
|
||||
digest[1]+=b;
|
||||
digest[2]+=c;
|
||||
digest[3]+=d;
|
||||
}
|
||||
|
||||
ConsoleFunctionGroupBegin(NetInterface, "Global control functions for the netInterfaces.");
|
||||
|
||||
ConsoleFunction(allowConnections,void,2,2,"allowConnections(bool);")
|
||||
{
|
||||
argc;
|
||||
GNet->setAllowsConnections(dAtob(argv[1]));
|
||||
}
|
||||
|
||||
ConsoleFunctionGroupEnd(NetInterface);
|
||||
|
120
engine/sim/netInterface.h
Executable file
120
engine/sim/netInterface.h
Executable file
@ -0,0 +1,120 @@
|
||||
#ifndef _H_NETINTERFACE
|
||||
#define _H_NETINTERFACE
|
||||
|
||||
/// NetInterface class. Manages all valid and pending notify protocol connections.
|
||||
///
|
||||
/// @see NetConnection, GameConnection, NetObject, NetEvent
|
||||
class NetInterface
|
||||
{
|
||||
public:
|
||||
/// PacketType is encoded as the first byte of each packet. If the LSB of
|
||||
/// the first byte is set (i.e. if the type number is odd), then the packet
|
||||
/// is a data protocol packet, otherwise it's an OOB packet, suitable for
|
||||
/// use in strange protocols, like game querying or connection initialization.
|
||||
enum PacketTypes
|
||||
{
|
||||
MasterServerGameTypesRequest = 2,
|
||||
MasterServerGameTypesResponse = 4,
|
||||
MasterServerListRequest = 6,
|
||||
MasterServerListResponse = 8,
|
||||
GameMasterInfoRequest = 10,
|
||||
GameMasterInfoResponse = 12,
|
||||
GamePingRequest = 14,
|
||||
GamePingResponse = 16,
|
||||
GameInfoRequest = 18,
|
||||
GameInfoResponse = 20,
|
||||
GameHeartbeat = 22,
|
||||
|
||||
ConnectChallengeRequest = 26,
|
||||
ConnectChallengeReject = 28,
|
||||
ConnectChallengeResponse = 30,
|
||||
ConnectRequest = 32,
|
||||
ConnectReject = 34,
|
||||
ConnectAccept = 36,
|
||||
Disconnect = 38,
|
||||
};
|
||||
protected:
|
||||
|
||||
Vector<NetConnection *> mPendingConnections; ///< List of connections that are in the startup phase.
|
||||
U32 mLastTimeoutCheckTime; ///< Last time all the active connections were checked for timeouts.
|
||||
U32 mRandomHashData[12]; ///< Data that gets hashed with connect challenge requests to prevent connection spoofing.
|
||||
bool mRandomDataInitialized; ///< Have we initialized our random number generator?
|
||||
bool mAllowConnections; ///< Is this NetInterface allowing connections at this time?
|
||||
|
||||
enum NetInterfaceConstants
|
||||
{
|
||||
MaxPendingConnects = 20, ///< Maximum number of pending connections. If new connection requests come in before
|
||||
ChallengeRetryCount = 4, ///< Number of times to send connect challenge requests before giving up.
|
||||
ChallengeRetryTime = 2500, ///< Timeout interval in milliseconds before retrying connect challenge.
|
||||
|
||||
ConnectRetryCount = 4, ///< Number of times to send connect requests before giving up.
|
||||
ConnectRetryTime = 2500, ///< Timeout interval in milliseconds before retrying connect request.
|
||||
TimeoutCheckInterval = 1500, ///< Interval in milliseconds between checking for connection timeouts.
|
||||
};
|
||||
|
||||
/// Initialize random data.
|
||||
void initRandomData();
|
||||
|
||||
/// @name Connection management
|
||||
/// Most of these are pretty self-explanatory.
|
||||
/// @{
|
||||
|
||||
void addPendingConnection(NetConnection *conn);
|
||||
NetConnection *findPendingConnection(const NetAddress *address, U32 packetSequence);
|
||||
void removePendingConnection(NetConnection *conn);
|
||||
|
||||
void sendConnectChallengeRequest(NetConnection *conn);
|
||||
void handleConnectChallengeRequest(const NetAddress *addr, BitStream *stream);
|
||||
|
||||
void handleConnectChallengeResponse(const NetAddress *address, BitStream *stream);
|
||||
|
||||
void sendConnectRequest(NetConnection *conn);
|
||||
void handleConnectRequest(const NetAddress *address, BitStream *stream);
|
||||
|
||||
void sendConnectAccept(NetConnection *conn);
|
||||
void handleConnectAccept(const NetAddress *address, BitStream *stream);
|
||||
|
||||
void sendConnectReject(NetConnection *conn, const char *reason);
|
||||
void handleConnectReject(const NetAddress *address, BitStream *stream);
|
||||
|
||||
void handleDisconnect(const NetAddress *address, BitStream *stream);
|
||||
|
||||
/// @}
|
||||
|
||||
/// Calculate an MD5 sum representing a connection, and store it into addressDigest.
|
||||
void computeNetMD5(const NetAddress *address, U32 connectSequence, U32 addressDigest[4]);
|
||||
|
||||
public:
|
||||
NetInterface();
|
||||
|
||||
/// Returns whether or not this NetInterface allows connections from remote hosts.
|
||||
bool doesAllowConnections() { return mAllowConnections; }
|
||||
|
||||
/// Sets whether or not this NetInterface allows connections from remote hosts.
|
||||
void setAllowsConnections(bool conn) { mAllowConnections = conn; }
|
||||
|
||||
/// Dispatch function for processing all network packets through this NetInterface.
|
||||
virtual void processPacketReceiveEvent(PacketReceiveEvent *event);
|
||||
|
||||
/// Handles all packets that don't fall into the category of connection handshake or game data.
|
||||
virtual void handleInfoPacket(const NetAddress *address, U8 packetType, BitStream *stream);
|
||||
|
||||
/// Checks all connections marked as client to server for packet sends.
|
||||
void processClient();
|
||||
|
||||
/// Checks all connections marked as server to client for packet sends.
|
||||
void processServer();
|
||||
|
||||
/// Begins the connection handshaking process for a connection.
|
||||
void startConnection(NetConnection *conn);
|
||||
|
||||
/// Checks for timeouts on all valid and pending connections.
|
||||
void checkTimeouts();
|
||||
|
||||
/// Send a disconnect packet on a connection, along with a reason.
|
||||
void sendDisconnectPacket(NetConnection *conn, const char *reason);
|
||||
};
|
||||
|
||||
/// The global net interface instance.
|
||||
extern NetInterface *GNet;
|
||||
#endif
|
265
engine/sim/netObject.cc
Executable file
265
engine/sim/netObject.cc
Executable file
@ -0,0 +1,265 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "console/simBase.h"
|
||||
#include "core/dnet.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "sim/netObject.h"
|
||||
#include "console/consoleTypes.h"
|
||||
|
||||
IMPLEMENT_CONOBJECT(NetObject);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
NetObject *NetObject::mDirtyList = NULL;
|
||||
|
||||
NetObject::NetObject()
|
||||
{
|
||||
// netFlags will clear itself to 0
|
||||
mNetIndex = U32(-1);
|
||||
mFirstObjectRef = NULL;
|
||||
mPrevDirtyList = NULL;
|
||||
mNextDirtyList = NULL;
|
||||
mDirtyMaskBits = 0;
|
||||
}
|
||||
|
||||
NetObject::~NetObject()
|
||||
{
|
||||
if(mDirtyMaskBits)
|
||||
{
|
||||
if(mPrevDirtyList)
|
||||
mPrevDirtyList->mNextDirtyList = mNextDirtyList;
|
||||
else
|
||||
mDirtyList = mNextDirtyList;
|
||||
if(mNextDirtyList)
|
||||
mNextDirtyList->mPrevDirtyList = mPrevDirtyList;
|
||||
}
|
||||
}
|
||||
|
||||
void NetObject::setMaskBits(U32 orMask)
|
||||
{
|
||||
AssertFatal(orMask != 0, "Invalid net mask bits set.");
|
||||
AssertFatal(mDirtyMaskBits == 0 || (mPrevDirtyList != NULL || mNextDirtyList != NULL || mDirtyList == this), "Invalid dirty list state.");
|
||||
if(!mDirtyMaskBits)
|
||||
{
|
||||
AssertFatal(mNextDirtyList == NULL && mPrevDirtyList == NULL, "Object with zero mask already in list.");
|
||||
if(mDirtyList)
|
||||
{
|
||||
mNextDirtyList = mDirtyList;
|
||||
mDirtyList->mPrevDirtyList = this;
|
||||
}
|
||||
mDirtyList = this;
|
||||
}
|
||||
mDirtyMaskBits |= orMask;
|
||||
AssertFatal(mDirtyMaskBits == 0 || (mPrevDirtyList != NULL || mNextDirtyList != NULL || mDirtyList == this), "Invalid dirty list state.");
|
||||
}
|
||||
|
||||
void NetObject::clearMaskBits(U32 orMask)
|
||||
{
|
||||
if(isDeleted())
|
||||
return;
|
||||
if(mDirtyMaskBits)
|
||||
{
|
||||
mDirtyMaskBits &= ~orMask;
|
||||
if(!mDirtyMaskBits)
|
||||
{
|
||||
if(mPrevDirtyList)
|
||||
mPrevDirtyList->mNextDirtyList = mNextDirtyList;
|
||||
else
|
||||
mDirtyList = mNextDirtyList;
|
||||
if(mNextDirtyList)
|
||||
mNextDirtyList->mPrevDirtyList = mPrevDirtyList;
|
||||
mNextDirtyList = mPrevDirtyList = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
for(GhostInfo *walk = mFirstObjectRef; walk; walk = walk->nextObjectRef)
|
||||
{
|
||||
if(walk->updateMask && walk->updateMask == orMask)
|
||||
{
|
||||
walk->updateMask = 0;
|
||||
walk->connection->ghostPushToZero(walk);
|
||||
}
|
||||
else
|
||||
walk->updateMask &= ~orMask;
|
||||
}
|
||||
}
|
||||
|
||||
void NetObject::collapseDirtyList()
|
||||
{
|
||||
Vector<NetObject *> tempV;
|
||||
for(NetObject *t = mDirtyList; t; t = t->mNextDirtyList)
|
||||
tempV.push_back(t);
|
||||
|
||||
for(NetObject *obj = mDirtyList; obj; )
|
||||
{
|
||||
NetObject *next = obj->mNextDirtyList;
|
||||
U32 orMask = obj->mDirtyMaskBits;
|
||||
|
||||
obj->mNextDirtyList = NULL;
|
||||
obj->mPrevDirtyList = NULL;
|
||||
obj->mDirtyMaskBits = 0;
|
||||
|
||||
if(!obj->isDeleted() && orMask)
|
||||
{
|
||||
for(GhostInfo *walk = obj->mFirstObjectRef; walk; walk = walk->nextObjectRef)
|
||||
{
|
||||
if(!walk->updateMask)
|
||||
{
|
||||
walk->updateMask = orMask;
|
||||
walk->connection->ghostPushNonZero(walk);
|
||||
}
|
||||
else
|
||||
walk->updateMask |= orMask;
|
||||
}
|
||||
}
|
||||
obj = next;
|
||||
}
|
||||
mDirtyList = NULL;
|
||||
for(U32 i = 0; i < tempV.size(); i++)
|
||||
{
|
||||
AssertFatal(tempV[i]->mNextDirtyList == NULL && tempV[i]->mPrevDirtyList == NULL && tempV[i]->mDirtyMaskBits == 0, "Error in collapse");
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConsoleMethod(NetObject,scopeToClient,void,3,3,"(NetConnection %client)"
|
||||
"Cause the NetObject to be forced as scoped on the specified NetConnection.")
|
||||
{
|
||||
argc;
|
||||
NetConnection *conn;
|
||||
if(!Sim::findObject(argv[2], conn))
|
||||
{
|
||||
Con::errorf(ConsoleLogEntry::General, "NetObject::scopeToClient: Couldn't find connection %s", argv[2]);
|
||||
return;
|
||||
}
|
||||
conn->objectLocalScopeAlways(object);
|
||||
}
|
||||
|
||||
ConsoleMethod(NetObject,clearScopeToClient,void,3,3,"clearScopeToClient(%client)"
|
||||
"Undo the effects of a scopeToClient() call.")
|
||||
{
|
||||
argc;
|
||||
NetConnection *conn;
|
||||
if(!Sim::findObject(argv[2], conn))
|
||||
{
|
||||
Con::errorf(ConsoleLogEntry::General, "NetObject::clearScopeToClient: Couldn't find connection %s", argv[2]);
|
||||
return;
|
||||
}
|
||||
conn->objectLocalClearAlways(object);
|
||||
}
|
||||
|
||||
ConsoleMethod(NetObject,setScopeAlways,void,2,2,"Always scope this object on all connections.")
|
||||
{
|
||||
argc; argv;
|
||||
object->setScopeAlways();
|
||||
}
|
||||
|
||||
void NetObject::setScopeAlways()
|
||||
{
|
||||
if(mNetFlags.test(Ghostable) && !mNetFlags.test(IsGhost))
|
||||
{
|
||||
mNetFlags.set(ScopeAlways);
|
||||
|
||||
// if it's a ghost always object, add it to the ghost always set
|
||||
// for ClientReps created later.
|
||||
|
||||
Sim::getGhostAlwaysSet()->addObject(this);
|
||||
|
||||
// add it to all Connections that already exist.
|
||||
|
||||
SimGroup *clientGroup = Sim::getClientGroup();
|
||||
SimGroup::iterator i;
|
||||
for(i = clientGroup->begin(); i != clientGroup->end(); i++)
|
||||
{
|
||||
NetConnection *con = (NetConnection *) (*i);
|
||||
if(con->isGhosting())
|
||||
con->objectInScope(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetObject::clearScopeAlways()
|
||||
{
|
||||
if(!mNetFlags.test(IsGhost))
|
||||
{
|
||||
mNetFlags.clear(ScopeAlways);
|
||||
Sim::getGhostAlwaysSet()->removeObject(this);
|
||||
|
||||
// Un ghost this object from all the connections
|
||||
while(mFirstObjectRef)
|
||||
mFirstObjectRef->connection->detachObject(mFirstObjectRef);
|
||||
}
|
||||
}
|
||||
|
||||
bool NetObject::onAdd()
|
||||
{
|
||||
if(mNetFlags.test(ScopeAlways))
|
||||
setScopeAlways();
|
||||
|
||||
return Parent::onAdd();
|
||||
}
|
||||
|
||||
void NetObject::onRemove()
|
||||
{
|
||||
while(mFirstObjectRef)
|
||||
mFirstObjectRef->connection->detachObject(mFirstObjectRef);
|
||||
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
F32 NetObject::getUpdatePriority(CameraScopeQuery*, U32, S32 updateSkips)
|
||||
{
|
||||
return F32(updateSkips) * 0.1;
|
||||
}
|
||||
|
||||
U32 NetObject::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void NetObject::unpackUpdate(NetConnection*, BitStream*)
|
||||
{
|
||||
}
|
||||
|
||||
void NetObject::onCameraScopeQuery(NetConnection *cr, CameraScopeQuery* /*camInfo*/)
|
||||
{
|
||||
// default behavior -
|
||||
// ghost everything that is ghostable
|
||||
|
||||
for (SimSetIterator obj(Sim::getRootGroup()); *obj; ++obj)
|
||||
{
|
||||
NetObject* nobj = dynamic_cast<NetObject*>(*obj);
|
||||
if (nobj)
|
||||
{
|
||||
AssertFatal(!nobj->mNetFlags.test(NetObject::Ghostable) || !nobj->mNetFlags.test(NetObject::IsGhost),
|
||||
"NetObject::onCameraScopeQuery: object marked both ghostable and as ghost");
|
||||
|
||||
// Some objects don't ever want to be ghosted
|
||||
if (!nobj->mNetFlags.test(NetObject::Ghostable))
|
||||
continue;
|
||||
if (!nobj->mNetFlags.test(NetObject::ScopeAlways))
|
||||
{
|
||||
// it's in scope...
|
||||
cr->objectInScope(nobj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void NetObject::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
}
|
||||
|
||||
ConsoleMethod( NetObject, getGhostID, S32, 2, 2, "")
|
||||
{
|
||||
return object->getNetIndex();
|
||||
}
|
403
engine/sim/netObject.h
Executable file
403
engine/sim/netObject.h
Executable file
@ -0,0 +1,403 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _NETOBJECT_H_
|
||||
#define _NETOBJECT_H_
|
||||
|
||||
#ifndef _SIMBASE_H_
|
||||
#include "console/simBase.h"
|
||||
#endif
|
||||
#ifndef _MMATH_H_
|
||||
#include "math/mMath.h"
|
||||
#endif
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
class NetConnection;
|
||||
class NetObject;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
struct CameraScopeQuery
|
||||
{
|
||||
NetObject *camera; ///< Pointer to the viewing object.
|
||||
Point3F pos; ///< Position in world space
|
||||
Point3F orientation; ///< Viewing vector in world space
|
||||
F32 fov; ///< Viewing angle/2
|
||||
F32 sinFov; ///< sin(fov/2);
|
||||
F32 cosFov; ///< cos(fov/2);
|
||||
F32 visibleDistance; ///< Visible distance.
|
||||
};
|
||||
|
||||
struct GhostInfo;
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Superclass for ghostable networked objects.
|
||||
///
|
||||
/// @section NetObject_intro Introduction To NetObject And Ghosting
|
||||
///
|
||||
/// One of the most powerful aspects of Torque's networking code is its support
|
||||
/// for ghosting and prioritized, most-recent-state network updates. The way
|
||||
/// this works is a bit complex, but it is immensely efficient. Let's run
|
||||
/// through the steps that the server goes through for each client in this part
|
||||
/// of Torque's networking:
|
||||
/// - First, the server determines what objects are in-scope for the client.
|
||||
/// This is done by calling onCameraScopeQuery() on the object which is
|
||||
/// considered the "scope" object. This is usually the player object, but
|
||||
/// it can be something else. (For instance, the current vehicle, or a
|
||||
/// object we're remote controlling.)
|
||||
/// - Second, it ghosts them to the client; this is implemented in netGhost.cc.
|
||||
/// - Finally, it sends updates as needed, by checking the dirty list and packing
|
||||
/// updates.
|
||||
///
|
||||
/// There several significant advantages to using this networking system:
|
||||
/// - Efficient network usage, since we only send data that has changed. In addition,
|
||||
/// since we only care about most-recent data, if a packet is dropped, we don't waste
|
||||
/// effort trying to deliver stale data.
|
||||
/// - Cheating protection; since we don't deliver information about game objects which
|
||||
/// aren't in scope, we dramatically reduce the ability of clients to hack the game and
|
||||
/// gain a meaningful advantage. (For instance, they can't find out about things behind
|
||||
/// them, since objects behind them don't fall in scope.) In addition, since ghost IDs are
|
||||
/// assigned per-client, it's difficult for any sort of co-ordination between cheaters to
|
||||
/// occur.
|
||||
///
|
||||
/// NetConnection contains the Ghost Manager implementation, which deals with transferring data to
|
||||
/// the appropriate clients and keeping state in synch.
|
||||
///
|
||||
/// @section NetObject_Implementation An Example Implementation
|
||||
///
|
||||
/// The basis of the ghost implementation in Torque is NetObject. It tracks the dirty flags for the
|
||||
/// various states that the object trackers, and does some other book-keeping to allow more efficient
|
||||
/// operation of the networking layer.
|
||||
///
|
||||
/// Using a NetObject is very simple; let's go through a simple example implementation:
|
||||
///
|
||||
/// @code
|
||||
/// class SimpleNetObject : public NetObject
|
||||
/// {
|
||||
/// public:
|
||||
/// typedef NetObject Parent;
|
||||
/// DECLARE_CONOBJECT(SimpleNetObject);
|
||||
/// @endcode
|
||||
///
|
||||
/// Above is the standard boilerplate code for a Torque class. You can find out more about this in SimObject.
|
||||
///
|
||||
/// @code
|
||||
/// char message1[256];
|
||||
/// char message2[256];
|
||||
/// enum States {
|
||||
/// Message1Mask = BIT(0),
|
||||
/// Message2Mask = BIT(1),
|
||||
/// };
|
||||
/// @endcode
|
||||
///
|
||||
/// For our example, we're having two "states" that we keep track of, message1 and message2. In a real
|
||||
/// object, we might map our states to health and position, or some other set of fields. You have 32
|
||||
/// bits to work with, so it's possible to be very specific when defining states. In general, you
|
||||
/// should try to use as few states as possible (you never know when you'll need to expand your object's
|
||||
/// functionality!), and in fact, most of your fields will end up changing all at once, so it's not worth
|
||||
/// it to be too fine-grained. (As an example, position and velocity on Player are controlled by the same
|
||||
/// bit, as one rarely changes without the other changing, too.)
|
||||
///
|
||||
/// @code
|
||||
/// SimpleNetObject()
|
||||
/// {
|
||||
/// // in order for an object to be considered by the network system,
|
||||
/// // the Ghostable net flag must be set.
|
||||
/// // the ScopeAlways flag indicates that the object is always scoped
|
||||
/// // on all active connections.
|
||||
/// mNetFlags.set(ScopeAlways | Ghostable);
|
||||
/// dStrcpy(message1, "Hello World 1!");
|
||||
/// dStrcpy(message2, "Hello World 2!");
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// Here is the constructor. Here, you see that we initialize our net flags to show that
|
||||
/// we should always be scoped, and that we're to be taken into consideration for ghosting. We
|
||||
/// also provide some initial values for the message fields.
|
||||
///
|
||||
/// @code
|
||||
/// U32 packUpdate(NetConnection *, U32 mask, BitStream *stream)
|
||||
/// {
|
||||
/// // check which states need to be updated, and update them
|
||||
/// if(stream->writeFlag(mask & Message1Mask))
|
||||
/// stream->writeString(message1);
|
||||
/// if(stream->writeFlag(mask & Message2Mask))
|
||||
/// stream->writeString(message2);
|
||||
///
|
||||
/// // the return value from packUpdate can set which states still
|
||||
/// // need to be updated for this object.
|
||||
/// return 0;
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// Here's half of the meat of the networking code, the packUpdate() function. (The other half, unpackUpdate(),
|
||||
/// we'll get to in a second.) The comments in the code pretty much explain everything, however, notice that the
|
||||
/// code follows a pattern of if(writeFlag(mask & StateMask)) { ... write data ... }. The packUpdate()/unpackUpdate()
|
||||
/// functions are responsible for reading and writing the dirty bits to the bitstream by themselves.
|
||||
///
|
||||
/// @code
|
||||
/// void unpackUpdate(NetConnection *, BitStream *stream)
|
||||
/// {
|
||||
/// // the unpackUpdate function must be symmetrical to packUpdate
|
||||
/// if(stream->readFlag())
|
||||
/// {
|
||||
/// stream->readString(message1);
|
||||
/// Con::printf("Got message1: %s", message1);
|
||||
/// }
|
||||
/// if(stream->readFlag())
|
||||
/// {
|
||||
/// stream->readString(message2);
|
||||
/// Con::printf("Got message2: %s", message2);
|
||||
/// }
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// The other half of the networking code in any NetObject, unpackUpdate(). In our simple example, all that
|
||||
/// the code does is print the new messages to the console; however, in a more advanced object, you might
|
||||
/// trigger animations, update complex object properties, or even spawn new objects, based on what packet
|
||||
/// data you unpack.
|
||||
///
|
||||
/// @code
|
||||
/// void setMessage1(const char *msg)
|
||||
/// {
|
||||
/// setMaskBits(Message1Mask);
|
||||
/// dStrcpy(message1, msg);
|
||||
/// }
|
||||
/// void setMessage2(const char *msg)
|
||||
/// {
|
||||
/// setMaskBits(Message2Mask);
|
||||
/// dStrcpy(message2, msg);
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// Here are the accessors for the two properties. It is good to encapsulate your state
|
||||
/// variables, so that you don't have to remember to make a call to setMaskBits every time you change
|
||||
/// anything; the accessors can do it for you. In a more complex object, you might need to set
|
||||
/// multiple mask bits when you change something; this can be done using the | operator, for instance,
|
||||
/// setMaskBits( Message1Mask | Message2Mask ); if you changed both messages.
|
||||
///
|
||||
/// @code
|
||||
/// IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject);
|
||||
///
|
||||
/// ConsoleMethod(SimpleNetObject, setMessage1, void, 3, 3, "(string msg) Set message 1.")
|
||||
/// {
|
||||
/// object->setMessage1(argv[2]);
|
||||
/// }
|
||||
///
|
||||
/// ConsoleMethod(SimpleNetObject, setMessage2, void, 3, 3, "(string msg) Set message 2.")
|
||||
/// {
|
||||
/// object->setMessage2(argv[2]);
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// Finally, we use the NetObject implementation macro, IMPLEMENT_CO_NETOBJECT_V1(), to implement our
|
||||
/// NetObject. It is important that we use this, as it makes Torque perform certain initialization tasks
|
||||
/// that allow us to send the object over the network. IMPLEMENT_CONOBJECT() doesn't perform these tasks, see
|
||||
/// the documentation on AbstractClassRep for more details.
|
||||
///
|
||||
/// @nosubgrouping
|
||||
class NetObject: public SimObject
|
||||
{
|
||||
// The Ghost Manager needs read/write access
|
||||
friend class NetConnection;
|
||||
friend struct GhostInfo;
|
||||
friend class ProcessList;
|
||||
|
||||
// Not the best way to do this, but the event needs access to mNetFlags
|
||||
friend class GhostAlwaysObjectEvent;
|
||||
|
||||
private:
|
||||
typedef SimObject Parent;
|
||||
|
||||
/// Mask indicating which states are dirty and need to be retransmitted on this
|
||||
/// object.
|
||||
U32 mDirtyMaskBits;
|
||||
|
||||
/// @name Dirty List
|
||||
///
|
||||
/// Whenever a NetObject becomes "dirty", we add it to the dirty list.
|
||||
/// We also remove ourselves on the destructor.
|
||||
///
|
||||
/// This is done so that when we want to send updates (in NetConnection),
|
||||
/// it's very fast to find the objects that need to be updated.
|
||||
/// @{
|
||||
|
||||
/// Static pointer to the head of the dirty NetObject list.
|
||||
static NetObject *mDirtyList;
|
||||
|
||||
/// Next item in the dirty list...
|
||||
NetObject *mPrevDirtyList;
|
||||
|
||||
/// Previous item in the dirty list...
|
||||
NetObject *mNextDirtyList;
|
||||
|
||||
/// @}
|
||||
protected:
|
||||
|
||||
/// Pointer to the server object; used only when we are doing "short-circuited" networking.
|
||||
///
|
||||
/// When we are running with client and server on the same system (which can happen be either
|
||||
/// when we are doing a single player game, or if we're hosting a multiplayer game and having
|
||||
/// someone playing on the same instance), we can do some short circuited code to enhance
|
||||
/// performance.
|
||||
///
|
||||
/// This variable is used to make it simpler; if we are running in short-circuited mode, it's set
|
||||
/// to the object on the server that this NetObject is ghosting.
|
||||
///
|
||||
/// @note "Premature optimization is the root of all evil" - Donald Knuth. The current codebase
|
||||
/// uses this feature in three small places, mostly for non-speed-related purposes.
|
||||
SimObjectPtr<NetObject> mServerObject;
|
||||
|
||||
enum NetFlags
|
||||
{
|
||||
IsGhost = BIT(1), ///< This is a ghost.
|
||||
ScopeAlways = BIT(6), ///< Object always ghosts to clients.
|
||||
ScopeLocal = BIT(7), ///< Ghost only to local client.
|
||||
Ghostable = BIT(8), ///< Set if this object CAN ghost.
|
||||
|
||||
MaxNetFlagBit = 15
|
||||
};
|
||||
|
||||
BitSet32 mNetFlags; ///< Flag values from NetFlags
|
||||
U32 mNetIndex; ///< The index of this ghost in the GhostManager on the server.
|
||||
|
||||
GhostInfo *mFirstObjectRef; ///< Head of a linked list storing GhostInfos referencing this NetObject.
|
||||
|
||||
public:
|
||||
NetObject();
|
||||
~NetObject();
|
||||
|
||||
/// @name Miscellaneous
|
||||
/// @{
|
||||
DECLARE_CONOBJECT(NetObject);
|
||||
static void initPersistFields();
|
||||
bool onAdd();
|
||||
void onRemove();
|
||||
/// @}
|
||||
|
||||
static void collapseDirtyList();
|
||||
|
||||
/// Used to mark a bit as dirty; ie, that its corresponding set of fields need to be transmitted next update.
|
||||
///
|
||||
/// @param orMask Bit(s) to set
|
||||
void setMaskBits(U32 orMask);
|
||||
|
||||
/// Clear the specified bits from the dirty mask.
|
||||
///
|
||||
/// @param orMask Bits to clear
|
||||
void clearMaskBits(U32 orMask);
|
||||
|
||||
/// Scope the object to all connections.
|
||||
///
|
||||
/// The object is marked as ScopeAlways and is immediately ghosted to
|
||||
/// all active connections. This function has no effect if the object
|
||||
/// is not marked as Ghostable.
|
||||
void setScopeAlways();
|
||||
|
||||
/// Stop scoping the object to all connections.
|
||||
///
|
||||
/// The object's ScopeAlways flag is cleared and the object is removed from
|
||||
/// all current active connections.
|
||||
void clearScopeAlways();
|
||||
|
||||
/// This returns a value which is used to prioritize which objects need to be updated.
|
||||
///
|
||||
/// In NetObject, our returned priority is 0.1 * updateSkips, so that less recently
|
||||
/// updated objects are more likely to be updated.
|
||||
///
|
||||
/// In subclasses, this can be adjusted. For instance, ShapeBase provides priority
|
||||
/// based on proximity to the camera.
|
||||
///
|
||||
/// @param focusObject Information from a previous call to onCameraScopeQuery.
|
||||
/// @param updateMask Current update mask.
|
||||
/// @param updateSkips Number of ticks we haven't been updated for.
|
||||
/// @returns A floating point value indicating priority. These are typically < 5.0.
|
||||
virtual F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips);
|
||||
|
||||
/// Instructs this object to pack its state for transfer over the network.
|
||||
///
|
||||
/// @param conn Net connection being used
|
||||
/// @param mask Mask indicating fields to transmit.
|
||||
/// @param stream Bitstream to pack data to
|
||||
///
|
||||
/// @returns Any bits which were not dealt with. The value is stored by the networking
|
||||
/// system. Don't set bits you weren't passed.
|
||||
virtual U32 packUpdate(NetConnection * conn, U32 mask, BitStream *stream);
|
||||
|
||||
/// Instructs this object to read state data previously packed with packUpdate.
|
||||
///
|
||||
/// @param conn Net connection being used
|
||||
/// @param stream stream to read from
|
||||
virtual void unpackUpdate(NetConnection * conn, BitStream *stream);
|
||||
|
||||
/// Queries the object about information used to determine scope.
|
||||
///
|
||||
/// Something that is 'in scope' is somehow interesting to the client.
|
||||
///
|
||||
/// If we are a NetConnection's scope object, it calls this method to determine
|
||||
/// how things should be scoped; basically, we tell it our field of view with camInfo,
|
||||
/// and have the opportunity to manually mark items as "in scope" as we see fit.
|
||||
///
|
||||
/// By default, we just mark all ghostable objects as in scope.
|
||||
///
|
||||
/// @param cr Net connection requesting scope information.
|
||||
/// @param camInfo Information about what this object can see.
|
||||
virtual void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery *camInfo);
|
||||
|
||||
/// Get the ghost index of this object.
|
||||
U32 getNetIndex() { return mNetIndex; }
|
||||
|
||||
bool isServerObject() const; ///< Is this a server object?
|
||||
bool isClientObject() const; ///< Is this a client object?
|
||||
|
||||
bool isGhost() const; ///< Is this is a ghost?
|
||||
bool isScopeLocal() const; ///< Should this object only be visible to the client which created it?
|
||||
bool isScopeable() const; ///< Is this object subject to scoping?
|
||||
bool isGhostable() const; ///< Is this object ghostable?
|
||||
bool isGhostAlways() const; ///< Should this object always be ghosted?
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
inline bool NetObject::isGhost() const
|
||||
{
|
||||
return mNetFlags.test(IsGhost);
|
||||
}
|
||||
|
||||
inline bool NetObject::isClientObject() const
|
||||
{
|
||||
return mNetFlags.test(IsGhost);
|
||||
}
|
||||
|
||||
inline bool NetObject::isServerObject() const
|
||||
{
|
||||
return !mNetFlags.test(IsGhost);
|
||||
}
|
||||
|
||||
inline bool NetObject::isScopeLocal() const
|
||||
{
|
||||
return mNetFlags.test(ScopeLocal);
|
||||
}
|
||||
|
||||
inline bool NetObject::isScopeable() const
|
||||
{
|
||||
return mNetFlags.test(Ghostable) && !mNetFlags.test(ScopeAlways);
|
||||
}
|
||||
|
||||
inline bool NetObject::isGhostable() const
|
||||
{
|
||||
return mNetFlags.test(Ghostable);
|
||||
}
|
||||
|
||||
inline bool NetObject::isGhostAlways() const
|
||||
{
|
||||
AssertFatal(mNetFlags.test(Ghostable) || mNetFlags.test(ScopeAlways) == false,
|
||||
"That's strange, a ScopeAlways non-ghostable object? Something wrong here");
|
||||
return mNetFlags.test(Ghostable) && mNetFlags.test(ScopeAlways);
|
||||
}
|
||||
|
||||
#endif
|
260
engine/sim/netStringTable.cc
Executable file
260
engine/sim/netStringTable.cc
Executable file
@ -0,0 +1,260 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (c) 2002 GarageGames.Com
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "core/dnet.h"
|
||||
#include "console/simBase.h"
|
||||
#include "sim/netStringTable.h"
|
||||
#include "core/stringTable.h"
|
||||
|
||||
NetStringTable *gNetStringTable = NULL;
|
||||
|
||||
NetStringTable::NetStringTable()
|
||||
{
|
||||
firstFree = 1;
|
||||
firstValid = 1;
|
||||
|
||||
table = (Entry *) dMalloc(sizeof(Entry) * InitialSize);
|
||||
size = InitialSize;
|
||||
for(U32 i = 0; i < InitialSize; i++)
|
||||
{
|
||||
table[i].next = i + 1;
|
||||
table[i].refCount = 0;
|
||||
table[i].scriptRefCount = 0;
|
||||
}
|
||||
table[InitialSize-1].next = InvalidEntry;
|
||||
for(U32 j = 0; j < HashTableSize; j++)
|
||||
hashTable[j] = 0;
|
||||
allocator = new DataChunker(DataChunkerSize);
|
||||
}
|
||||
|
||||
NetStringTable::~NetStringTable()
|
||||
{
|
||||
delete allocator;
|
||||
}
|
||||
|
||||
void NetStringTable::incStringRef(U32 id)
|
||||
{
|
||||
AssertFatal(table[id].refCount != 0 || table[id].scriptRefCount != 0 , "Cannot inc ref count from zero.");
|
||||
table[id].refCount++;
|
||||
}
|
||||
|
||||
void NetStringTable::incStringRefScript(U32 id)
|
||||
{
|
||||
AssertFatal(table[id].refCount != 0 || table[id].scriptRefCount != 0 , "Cannot inc ref count from zero.");
|
||||
table[id].scriptRefCount++;
|
||||
}
|
||||
|
||||
U32 NetStringTable::addString(const char *string)
|
||||
{
|
||||
U32 hash = _StringTable::hashString(string);
|
||||
U32 bucket = hash % HashTableSize;
|
||||
for(U32 walk = hashTable[bucket];walk; walk = table[walk].next)
|
||||
{
|
||||
if(!dStrcmp(table[walk].string, string))
|
||||
{
|
||||
table[walk].refCount++;
|
||||
return walk;
|
||||
}
|
||||
}
|
||||
U32 e = firstFree;
|
||||
firstFree = table[e].next;
|
||||
if(firstFree == InvalidEntry)
|
||||
{
|
||||
// in this case, we should expand the table for next time...
|
||||
U32 newSize = size * 2;
|
||||
table = (Entry *) dRealloc(table, newSize * sizeof(Entry));
|
||||
for(U32 i = size; i < newSize; i++)
|
||||
{
|
||||
table[i].next = i + 1;
|
||||
table[i].refCount = 0;
|
||||
table[i].scriptRefCount = 0;
|
||||
}
|
||||
firstFree = size;
|
||||
table[newSize - 1].next = InvalidEntry;
|
||||
size = newSize;
|
||||
}
|
||||
table[e].refCount++;
|
||||
table[e].string = (char *) allocator->alloc(dStrlen(string) + 1);
|
||||
dStrcpy(table[e].string, string);
|
||||
table[e].next = hashTable[bucket];
|
||||
hashTable[bucket] = e;
|
||||
table[e].link = firstValid;
|
||||
table[firstValid].prevLink = e;
|
||||
firstValid = e;
|
||||
table[e].prevLink = 0;
|
||||
return e;
|
||||
}
|
||||
|
||||
U32 GameAddTaggedString(const char *string)
|
||||
{
|
||||
return gNetStringTable->addString(string);
|
||||
}
|
||||
|
||||
const char *NetStringTable::lookupString(U32 id)
|
||||
{
|
||||
if(table[id].refCount == 0 && table[id].scriptRefCount == 0)
|
||||
return NULL;
|
||||
return table[id].string;
|
||||
}
|
||||
|
||||
void NetStringTable::removeString(U32 id, bool script)
|
||||
{
|
||||
if(!script)
|
||||
{
|
||||
AssertFatal(table[id].refCount != 0, "Error, ref count is already 0!!");
|
||||
if(--table[id].refCount)
|
||||
return;
|
||||
if(table[id].scriptRefCount)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If both ref counts are already 0, this id is not valid. Ignore
|
||||
// the remove
|
||||
if (table[id].scriptRefCount == 0 && table[id].refCount == 0)
|
||||
return;
|
||||
|
||||
if(table[id].scriptRefCount == 0 && table[id].refCount)
|
||||
{
|
||||
Con::errorf("removeTaggedString failed! Ref count is already 0 for string: %s", table[id].string);
|
||||
return;
|
||||
}
|
||||
if(--table[id].scriptRefCount)
|
||||
return;
|
||||
if(table[id].refCount)
|
||||
return;
|
||||
}
|
||||
// unlink first:
|
||||
U32 prev = table[id].prevLink;
|
||||
U32 next = table[id].link;
|
||||
if(next)
|
||||
table[next].prevLink = prev;
|
||||
if(prev)
|
||||
table[prev].link = next;
|
||||
else
|
||||
firstValid = next;
|
||||
// remove it from the hash table
|
||||
U32 hash = _StringTable::hashString(table[id].string);
|
||||
U32 bucket = hash % HashTableSize;
|
||||
for(U32 *walk = &hashTable[bucket];*walk; walk = &table[*walk].next)
|
||||
{
|
||||
if(*walk == id)
|
||||
{
|
||||
*walk = table[id].next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
table[id].next = firstFree;
|
||||
firstFree = id;
|
||||
}
|
||||
|
||||
void NetStringTable::repack()
|
||||
{
|
||||
DataChunker *newAllocator = new DataChunker(DataChunkerSize);
|
||||
for(U32 walk = firstValid; walk; walk = table[walk].link)
|
||||
{
|
||||
const char *prevStr = table[walk].string;
|
||||
|
||||
|
||||
table[walk].string = (char *) newAllocator->alloc(dStrlen(prevStr) + 1);
|
||||
dStrcpy(table[walk].string, prevStr);
|
||||
}
|
||||
delete allocator;
|
||||
allocator = newAllocator;
|
||||
}
|
||||
|
||||
void NetStringTable::create()
|
||||
{
|
||||
AssertFatal(gNetStringTable == NULL, "Error, calling NetStringTable::create twice.");
|
||||
gNetStringTable = new NetStringTable();
|
||||
}
|
||||
|
||||
void NetStringTable::destroy()
|
||||
{
|
||||
AssertFatal(gNetStringTable != NULL, "Error, not calling NetStringTable::create.");
|
||||
delete gNetStringTable;
|
||||
gNetStringTable = NULL;
|
||||
}
|
||||
|
||||
void NetStringTable::expandString(StringHandle &inString, char *buf, U32 bufSize, U32 argc, const char **argv)
|
||||
{
|
||||
buf[0] = StringTagPrefixByte;
|
||||
dSprintf(buf + 1, bufSize - 1, "%d ", inString.getIndex());
|
||||
|
||||
const char *string = inString.getString();
|
||||
if (string != NULL) {
|
||||
U32 index = dStrlen(buf);
|
||||
while(index < bufSize)
|
||||
{
|
||||
char c = *string++;
|
||||
if(c == '%')
|
||||
{
|
||||
c = *string++;
|
||||
if(c >= '1' && c <= '9')
|
||||
{
|
||||
U32 strIndex = c - '1';
|
||||
if(strIndex >= argc)
|
||||
continue;
|
||||
// start copying out of arg index
|
||||
const char *copy = argv[strIndex];
|
||||
// skip past any tags:
|
||||
if(*copy == StringTagPrefixByte)
|
||||
{
|
||||
while(*copy && *copy != ' ')
|
||||
copy++;
|
||||
if(*copy)
|
||||
copy++;
|
||||
}
|
||||
|
||||
while(*copy && index < bufSize)
|
||||
buf[index++] = *copy++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
buf[index++] = c;
|
||||
if(!c)
|
||||
break;
|
||||
}
|
||||
buf[bufSize - 1] = 0;
|
||||
} else {
|
||||
dStrcat(buf, "<NULL>");
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(TORQUE_DEBUG)
|
||||
void NetStringTable::dumpToConsole()
|
||||
{
|
||||
U32 count = 0;
|
||||
S32 maxIndex = -1;
|
||||
for ( U32 i = 0; i < size; i++ )
|
||||
{
|
||||
if ( table[i].refCount > 0 || table[i].scriptRefCount > 0)
|
||||
{
|
||||
Con::printf( "%d: \"%c%s%c\" REF: %d", i, 0x10, table[i].string, 0x11, table[i].refCount );
|
||||
if ( maxIndex == -1 || table[i].refCount > table[maxIndex].refCount )
|
||||
maxIndex = i;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
Con::printf( ">> STRINGS: %d MAX REF COUNT: %d \"%c%s%c\" <<",
|
||||
count,
|
||||
( maxIndex == -1 ) ? 0 : table[maxIndex].refCount,
|
||||
0x10,
|
||||
( maxIndex == -1 ) ? "" : table[maxIndex].string,
|
||||
0x11 );
|
||||
}
|
||||
|
||||
ConsoleFunctionGroupBegin(NetStringTable, "Debug functions for the NetStringTable.");
|
||||
|
||||
ConsoleFunction( dumpNetStringTable, void, 1, 1, "dumpNetStringTable()" )
|
||||
{
|
||||
argc; argv;
|
||||
gNetStringTable->dumpToConsole();
|
||||
}
|
||||
|
||||
ConsoleFunctionGroupEnd(NetStringTable);
|
||||
|
||||
#endif // DEBUG
|
139
engine/sim/netStringTable.h
Executable file
139
engine/sim/netStringTable.h
Executable file
@ -0,0 +1,139 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (c) 2002 GarageGames.Com
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _NETSTRINGTABLE_H_
|
||||
#define _NETSTRINGTABLE_H_
|
||||
|
||||
#ifndef _DATACHUNKER_H_
|
||||
#include "core/dataChunker.h"
|
||||
#endif
|
||||
#ifndef _CONSOLE_H_
|
||||
#include "console/console.h"
|
||||
#endif
|
||||
|
||||
class NetConnection;
|
||||
|
||||
class StringHandle;
|
||||
extern U32 GameAddTaggedString(const char *string);
|
||||
|
||||
class NetStringTable
|
||||
{
|
||||
friend class StringHandle;
|
||||
friend U32 GameAddTaggedString(const char *string);
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
friend class RemoteCommandEvent;
|
||||
#endif
|
||||
|
||||
enum Constants {
|
||||
InitialSize = 16,
|
||||
InvalidEntry = 0xFFFFFFFF,
|
||||
HashTableSize = 2128,
|
||||
DataChunkerSize = 65536
|
||||
};
|
||||
struct Entry
|
||||
{
|
||||
char *string;
|
||||
U32 refCount;
|
||||
U32 scriptRefCount;
|
||||
U32 next;
|
||||
U32 link;
|
||||
U32 prevLink;
|
||||
U32 seq;
|
||||
};
|
||||
U32 size;
|
||||
U32 firstFree;
|
||||
U32 firstValid;
|
||||
U32 sequenceCount;
|
||||
|
||||
Entry *table;
|
||||
U32 hashTable[HashTableSize];
|
||||
DataChunker *allocator;
|
||||
|
||||
NetStringTable();
|
||||
~NetStringTable();
|
||||
|
||||
U32 addString(const char *string);
|
||||
|
||||
// XA: Moved this ones to public to avoid using the friend_ConsoleMethod hack.
|
||||
public:
|
||||
const char *lookupString(U32 id);
|
||||
void removeString(U32 id, bool script = false);
|
||||
void incStringRefScript(U32 id);
|
||||
|
||||
private:
|
||||
void incStringRef(U32 id);
|
||||
|
||||
void repack();
|
||||
public:
|
||||
static void create();
|
||||
static void destroy();
|
||||
|
||||
static void expandString(StringHandle &string, char *buf, U32 bufSize, U32 argc, const char **argv);
|
||||
|
||||
#if defined(TORQUE_DEBUG)
|
||||
void dumpToConsole();
|
||||
#endif // DEBUG
|
||||
};
|
||||
|
||||
extern NetStringTable *gNetStringTable;
|
||||
|
||||
class StringHandle
|
||||
{
|
||||
U32 index;
|
||||
public:
|
||||
StringHandle() { index = 0; }
|
||||
StringHandle(const StringHandle &string) {
|
||||
index = string.index;
|
||||
if(index)
|
||||
gNetStringTable->incStringRef(index);
|
||||
}
|
||||
StringHandle(const char *string) {
|
||||
index = gNetStringTable->addString(string);
|
||||
}
|
||||
StringHandle(U32 initIndex)
|
||||
{
|
||||
index = initIndex;
|
||||
if(index)
|
||||
gNetStringTable->incStringRef(index);
|
||||
}
|
||||
~StringHandle()
|
||||
{
|
||||
if(index)
|
||||
gNetStringTable->removeString(index);
|
||||
}
|
||||
|
||||
void setFromIndex(U32 newIndex)
|
||||
{
|
||||
if(index)
|
||||
gNetStringTable->removeString(index);
|
||||
index = newIndex;
|
||||
}
|
||||
|
||||
bool operator==(const StringHandle &s) const { return index == s.index; }
|
||||
bool operator!=(const StringHandle &s) const { return index != s.index; }
|
||||
|
||||
StringHandle &operator=(const StringHandle &s)
|
||||
{
|
||||
if(index)
|
||||
gNetStringTable->removeString(index);
|
||||
index = s.index;
|
||||
if(index)
|
||||
gNetStringTable->incStringRef(index);
|
||||
return *this;
|
||||
}
|
||||
const char *getString()
|
||||
{
|
||||
if(index)
|
||||
return gNetStringTable->lookupString(index);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
bool isNull() { return index == 0; }
|
||||
bool isValidString() { return index != 0; }
|
||||
U32 getIndex() { return index; }
|
||||
};
|
||||
|
||||
#endif
|
391
engine/sim/pathManager.cc
Executable file
391
engine/sim/pathManager.cc
Executable file
@ -0,0 +1,391 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
//
|
||||
// Copyright (c) 2001 GarageGames.Com
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "sim/pathManager.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "core/bitStream.h"
|
||||
#include "sim/simPath.h"
|
||||
#include "interior/interiorInstance.h"
|
||||
#include "math/mathIO.h"
|
||||
#include "dgl/dgl.h"
|
||||
#include "sceneGraph/sceneState.h"
|
||||
#include "sceneGraph/sceneGraph.h"
|
||||
|
||||
extern bool gEditingMission;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
U32 countNumBits(U32 n)
|
||||
{
|
||||
U32 count = 0;
|
||||
while (n != 0) {
|
||||
n >>= 1;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count ? count : 1;
|
||||
}
|
||||
|
||||
} // namespace {}
|
||||
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//-------------------------------------- PathManagerEvent
|
||||
//
|
||||
class PathManagerEvent : public NetEvent
|
||||
{
|
||||
public:
|
||||
U32 modifiedPath;
|
||||
bool clearPaths;
|
||||
PathManager::PathEntry path;
|
||||
|
||||
public:
|
||||
PathManagerEvent() { }
|
||||
|
||||
void pack(NetConnection*, BitStream*);
|
||||
void write(NetConnection*, BitStream*);
|
||||
void unpack(NetConnection*, BitStream*);
|
||||
void process(NetConnection*);
|
||||
|
||||
DECLARE_CONOBJECT(PathManagerEvent);
|
||||
};
|
||||
|
||||
void PathManagerEvent::pack(NetConnection*, BitStream* stream)
|
||||
{
|
||||
// Write out the modified path...
|
||||
stream->write(modifiedPath);
|
||||
stream->writeFlag(clearPaths);
|
||||
stream->write(path.totalTime);
|
||||
stream->write(path.positions.size());
|
||||
|
||||
|
||||
// This is here for safety. You can remove it if you want to try your luck at bigger sizes. -- BJG
|
||||
AssertWarn(path.positions.size() < 1500/40, "Warning! Path size is pretty big - may cause packet overrun!");
|
||||
|
||||
// Each one of these is about 8 floats and 2 ints
|
||||
// so we'll say it's about 40 bytes in size, which is where the 40 in the above calc comes from.
|
||||
for (U32 j = 0; j < path.positions.size(); j++)
|
||||
{
|
||||
mathWrite(*stream, path.positions[j]);
|
||||
mathWrite(*stream, path.rotations[j]);
|
||||
stream->write(path.msToNext[j]);
|
||||
stream->write(path.smoothingType[j]);
|
||||
}
|
||||
}
|
||||
|
||||
void PathManagerEvent::write(NetConnection*nc, BitStream *stream)
|
||||
{
|
||||
pack(nc, stream);
|
||||
}
|
||||
|
||||
void PathManagerEvent::unpack(NetConnection*, BitStream* stream)
|
||||
{
|
||||
// Read in the modified path...
|
||||
|
||||
stream->read(&modifiedPath);
|
||||
clearPaths = stream->readFlag();
|
||||
stream->read(&path.totalTime);
|
||||
|
||||
U32 numPoints;
|
||||
stream->read(&numPoints);
|
||||
path.positions.setSize(numPoints);
|
||||
path.rotations.setSize(numPoints);
|
||||
path.msToNext.setSize(numPoints);
|
||||
path.smoothingType.setSize(numPoints);
|
||||
for (U32 j = 0; j < path.positions.size(); j++)
|
||||
{
|
||||
mathRead(*stream, &path.positions[j]);
|
||||
mathRead(*stream, &path.rotations[j]);
|
||||
stream->read(&path.msToNext[j]);
|
||||
stream->read(&path.smoothingType[j]);
|
||||
}
|
||||
}
|
||||
|
||||
void PathManagerEvent::process(NetConnection*)
|
||||
{
|
||||
if (clearPaths)
|
||||
{
|
||||
// Clear out all the client's paths...
|
||||
gClientPathManager->clearPaths();
|
||||
}
|
||||
AssertFatal(modifiedPath <= gClientPathManager->mPaths.size(), "Error out of bounds path!");
|
||||
if (modifiedPath == gClientPathManager->mPaths.size()) {
|
||||
PathManager::PathEntry *pe = new PathManager::PathEntry;
|
||||
*pe = path;
|
||||
gClientPathManager->mPaths.push_back(pe);
|
||||
}
|
||||
else
|
||||
*(gClientPathManager->mPaths[modifiedPath]) = path;
|
||||
}
|
||||
|
||||
IMPLEMENT_CO_NETEVENT_V1(PathManagerEvent);
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//-------------------------------------- PathManager Implementation
|
||||
//
|
||||
PathManager* gClientPathManager = NULL;
|
||||
PathManager* gServerPathManager = NULL;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
PathManager::PathManager(const bool isServer)
|
||||
{
|
||||
VECTOR_SET_ASSOCIATION(mPaths);
|
||||
|
||||
mIsServer = isServer;
|
||||
}
|
||||
|
||||
PathManager::~PathManager()
|
||||
{
|
||||
clearPaths();
|
||||
}
|
||||
|
||||
void PathManager::clearPaths()
|
||||
{
|
||||
for (U32 i = 0; i < mPaths.size(); i++)
|
||||
delete mPaths[i];
|
||||
mPaths.setSize(0);
|
||||
}
|
||||
|
||||
ConsoleFunction(clearServerPaths, void, 1, 1, "")
|
||||
{
|
||||
gServerPathManager->clearPaths();
|
||||
}
|
||||
|
||||
void PathManager::init()
|
||||
{
|
||||
AssertFatal(gClientPathManager == NULL && gServerPathManager == NULL, "Error, already initialized the path manager!");
|
||||
|
||||
gClientPathManager = new PathManager(false);
|
||||
gServerPathManager = new PathManager(true);
|
||||
}
|
||||
|
||||
void PathManager::destroy()
|
||||
{
|
||||
AssertFatal(gClientPathManager != NULL && gServerPathManager != NULL, "Error, path manager not initialized!");
|
||||
|
||||
delete gClientPathManager;
|
||||
gClientPathManager = NULL;
|
||||
delete gServerPathManager;
|
||||
gServerPathManager = NULL;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
U32 PathManager::allocatePathId()
|
||||
{
|
||||
mPaths.increment();
|
||||
mPaths.last() = new PathEntry;
|
||||
|
||||
return (mPaths.size() - 1);
|
||||
}
|
||||
|
||||
|
||||
void PathManager::updatePath(const U32 id,
|
||||
const Vector<Point3F>& positions,
|
||||
const Vector<QuatF>& rotations,
|
||||
const Vector<U32>& times,
|
||||
const Vector<U32>& smoothingTypes)
|
||||
{
|
||||
AssertFatal(mIsServer == true, "PathManager::updatePath: Error, must be called on the server side");
|
||||
AssertFatal(id < mPaths.size(), "PathManager::updatePath: error, id out of range");
|
||||
AssertFatal(positions.size() == times.size() && positions.size() == smoothingTypes.size(), "Error, times and positions must match!");
|
||||
|
||||
PathEntry& rEntry = *mPaths[id];
|
||||
|
||||
rEntry.positions = positions;
|
||||
rEntry.rotations = rotations;
|
||||
rEntry.msToNext = times;
|
||||
rEntry.smoothingType = smoothingTypes;
|
||||
|
||||
rEntry.totalTime = 0;
|
||||
for (S32 i = 0; i < S32(rEntry.msToNext.size()); i++)
|
||||
rEntry.totalTime += rEntry.msToNext[i];
|
||||
|
||||
transmitPath(id);
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
void PathManager::transmitPaths(NetConnection* nc)
|
||||
{
|
||||
AssertFatal(mIsServer, "Error, cannot call transmitPaths on client path manager!");
|
||||
|
||||
// Send over paths
|
||||
for(S32 i = 0; i < mPaths.size(); i++)
|
||||
{
|
||||
PathManagerEvent* event = new PathManagerEvent;
|
||||
event->clearPaths = (i == 0);
|
||||
event->modifiedPath = i;
|
||||
event->path = *(mPaths[i]);
|
||||
nc->postNetEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void PathManager::transmitPath(const U32 id)
|
||||
{
|
||||
AssertFatal(mIsServer, "Error, cannot call transmitNewPath on client path manager!");
|
||||
|
||||
// Post to all active clients that have already received their paths...
|
||||
//
|
||||
SimGroup* pClientGroup = Sim::getClientGroup();
|
||||
for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++) {
|
||||
NetConnection* nc = dynamic_cast<NetConnection*>(*itr);
|
||||
if (nc && nc->missionPathsSent())
|
||||
{
|
||||
// Transmit the updated path...
|
||||
PathManagerEvent* event = new PathManagerEvent;
|
||||
event->modifiedPath = id;
|
||||
event->clearPaths = false;
|
||||
event->path = *(mPaths[id]);
|
||||
nc->postNetEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PathManager::getPathPosition(const U32 id,
|
||||
const F64 msPosition,
|
||||
Point3F& rPosition,
|
||||
QuatF &rotation)
|
||||
{
|
||||
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
|
||||
|
||||
// Ok, query holds our path information...
|
||||
F64 ms = msPosition;
|
||||
if (ms > mPaths[id]->totalTime)
|
||||
ms = mPaths[id]->totalTime;
|
||||
|
||||
S32 startNode = 0;
|
||||
while (ms > mPaths[id]->msToNext[startNode]) {
|
||||
ms -= mPaths[id]->msToNext[startNode];
|
||||
startNode++;
|
||||
}
|
||||
S32 endNode = (startNode + 1) % mPaths[id]->positions.size();
|
||||
|
||||
Point3F& rStart = mPaths[id]->positions[startNode];
|
||||
Point3F& rEnd = mPaths[id]->positions[endNode];
|
||||
|
||||
F64 interp = ms / F32(mPaths[id]->msToNext[startNode]);
|
||||
if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeLinear)
|
||||
{
|
||||
rPosition = (rStart * (1.0 - interp)) + (rEnd * interp);
|
||||
}
|
||||
else if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeAccelerate)
|
||||
{
|
||||
interp = mSin(interp * M_PI - (M_PI / 2)) * 0.5 + 0.5;
|
||||
rPosition = (rStart * (1.0 - interp)) + (rEnd * interp);
|
||||
}
|
||||
else if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeSpline)
|
||||
{
|
||||
S32 preStart = startNode - 1;
|
||||
S32 postEnd = endNode + 1;
|
||||
if(postEnd >= mPaths[id]->positions.size())
|
||||
postEnd = 0;
|
||||
if(preStart < 0)
|
||||
preStart = mPaths[id]->positions.size() - 1;
|
||||
Point3F p0 = mPaths[id]->positions[preStart];
|
||||
Point3F p1 = rStart;
|
||||
Point3F p2 = rEnd;
|
||||
Point3F p3 = mPaths[id]->positions[postEnd];
|
||||
rPosition.x = mCatmullrom(interp, p0.x, p1.x, p2.x, p3.x);
|
||||
rPosition.y = mCatmullrom(interp, p0.y, p1.y, p2.y, p3.y);
|
||||
rPosition.z = mCatmullrom(interp, p0.z, p1.z, p2.z, p3.z);
|
||||
}
|
||||
rotation.interpolate( mPaths[id]->rotations[startNode], mPaths[id]->rotations[endNode], interp );
|
||||
}
|
||||
|
||||
U32 PathManager::getPathTotalTime(const U32 id) const
|
||||
{
|
||||
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
|
||||
|
||||
return mPaths[id]->totalTime;
|
||||
}
|
||||
|
||||
U32 PathManager::getPathNumWaypoints(const U32 id) const
|
||||
{
|
||||
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
|
||||
|
||||
return mPaths[id]->positions.size();
|
||||
}
|
||||
|
||||
U32 PathManager::getWaypointTime(const U32 id, const U32 wayPoint) const
|
||||
{
|
||||
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
|
||||
AssertFatal(wayPoint < getPathNumWaypoints(id), "Invalid waypoint!");
|
||||
|
||||
U32 time = 0;
|
||||
for (U32 i = 0; i < wayPoint; i++)
|
||||
time += mPaths[id]->msToNext[i];
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
U32 PathManager::getPathTimeBits(const U32 id)
|
||||
{
|
||||
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
|
||||
|
||||
return countNumBits(mPaths[id]->totalTime);
|
||||
}
|
||||
|
||||
U32 PathManager::getPathWaypointBits(const U32 id)
|
||||
{
|
||||
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
|
||||
|
||||
return countNumBits(mPaths[id]->positions.size());
|
||||
}
|
||||
|
||||
|
||||
bool PathManager::dumpState(BitStream* stream) const
|
||||
{
|
||||
stream->write(mPaths.size());
|
||||
|
||||
for (U32 i = 0; i < mPaths.size(); i++) {
|
||||
const PathEntry& rEntry = *mPaths[i];
|
||||
stream->write(rEntry.totalTime);
|
||||
|
||||
stream->write(rEntry.positions.size());
|
||||
for (U32 j = 0; j < rEntry.positions.size(); j++) {
|
||||
mathWrite(*stream, rEntry.positions[j]);
|
||||
stream->write(rEntry.msToNext[j]);
|
||||
}
|
||||
}
|
||||
|
||||
return stream->getStatus() == Stream::Ok;
|
||||
}
|
||||
|
||||
bool PathManager::readState(BitStream* stream)
|
||||
{
|
||||
U32 i;
|
||||
for (i = 0; i < mPaths.size(); i++)
|
||||
delete mPaths[i];
|
||||
|
||||
U32 numPaths;
|
||||
stream->read(&numPaths);
|
||||
mPaths.setSize(numPaths);
|
||||
|
||||
for (i = 0; i < mPaths.size(); i++) {
|
||||
mPaths[i] = new PathEntry;
|
||||
PathEntry& rEntry = *mPaths[i];
|
||||
|
||||
stream->read(&rEntry.totalTime);
|
||||
|
||||
U32 numPositions;
|
||||
stream->read(&numPositions);
|
||||
rEntry.positions.setSize(numPositions);
|
||||
rEntry.msToNext.setSize(numPositions);
|
||||
for (U32 j = 0; j < rEntry.positions.size(); j++) {
|
||||
mathRead(*stream, &rEntry.positions[j]);
|
||||
stream->read(&rEntry.msToNext[j]);
|
||||
}
|
||||
}
|
||||
|
||||
return stream->getStatus() == Stream::Ok;
|
||||
}
|
||||
|
||||
|
||||
|
115
engine/sim/pathManager.h
Executable file
115
engine/sim/pathManager.h
Executable file
@ -0,0 +1,115 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
//
|
||||
// Copyright (c) 2001 GarageGames.Com
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _PATHMANAGER_H_
|
||||
#define _PATHMANAGER_H_
|
||||
|
||||
#ifndef _PLATFORM_H_
|
||||
#include "platform/platform.h"
|
||||
#endif
|
||||
#ifndef _TVECTOR_H_
|
||||
#include "core/tVector.h"
|
||||
#endif
|
||||
#ifndef _MPOINT_H_
|
||||
#include "math/mPoint.h"
|
||||
#endif
|
||||
#ifndef _MQUAT_H_
|
||||
#include "math/mQuat.h"
|
||||
#endif
|
||||
#ifndef _SCENEOBJECT_H_
|
||||
#include "sim/sceneObject.h"
|
||||
#endif
|
||||
|
||||
class NetConnection;
|
||||
class BitStream;
|
||||
|
||||
class PathManager
|
||||
{
|
||||
friend class PathManagerEvent;
|
||||
|
||||
private:
|
||||
struct PathEntry {
|
||||
U32 totalTime;
|
||||
|
||||
Vector<Point3F> positions;
|
||||
Vector<QuatF> rotations;
|
||||
Vector<U32> smoothingType;
|
||||
Vector<U32> msToNext;
|
||||
|
||||
PathEntry() {
|
||||
VECTOR_SET_ASSOCIATION(positions);
|
||||
VECTOR_SET_ASSOCIATION(rotations);
|
||||
VECTOR_SET_ASSOCIATION(smoothingType);
|
||||
VECTOR_SET_ASSOCIATION(msToNext);
|
||||
}
|
||||
};
|
||||
|
||||
Vector<PathEntry*> mPaths;
|
||||
|
||||
public:
|
||||
enum PathType {
|
||||
BackAndForth,
|
||||
Looping
|
||||
};
|
||||
|
||||
public:
|
||||
PathManager(const bool isServer);
|
||||
~PathManager();
|
||||
|
||||
static void init();
|
||||
static void destroy();
|
||||
void clearPaths();
|
||||
|
||||
//-------------------------------------- Path querying
|
||||
public:
|
||||
bool isValidPath(const U32 id) const;
|
||||
void getPathPosition(const U32 id, const F64 msPosition, Point3F& rPosition, QuatF &rotation);
|
||||
U32 getPathTotalTime(const U32 id) const;
|
||||
U32 getPathNumWaypoints(const U32 id) const;
|
||||
U32 getWaypointTime(const U32 id, const U32 wayPoint) const;
|
||||
|
||||
U32 getPathTimeBits(const U32 id);
|
||||
U32 getPathWaypointBits(const U32 id);
|
||||
|
||||
//-------------------------------------- Path Registration/Transmission/Management
|
||||
public:
|
||||
// Called after mission load to clear out the paths on the client, and to transmit
|
||||
// the information for the current mission's paths.
|
||||
void transmitPaths(NetConnection*);
|
||||
void transmitPath(U32);
|
||||
|
||||
U32 allocatePathId();
|
||||
void updatePath(const U32 id, const Vector<Point3F>&, const Vector<QuatF>&, const Vector<U32> &, const Vector<U32>&);
|
||||
|
||||
//-------------------------------------- State dumping/reading
|
||||
public:
|
||||
bool dumpState(BitStream*) const;
|
||||
bool readState(BitStream*);
|
||||
|
||||
private:
|
||||
bool mIsServer;
|
||||
bool mPathsSent;
|
||||
};
|
||||
|
||||
struct PathNode {
|
||||
Point3F position;
|
||||
QuatF rotation;
|
||||
U32 smoothingType;
|
||||
U32 msToNext;
|
||||
};
|
||||
|
||||
extern PathManager* gClientPathManager;
|
||||
extern PathManager* gServerPathManager;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
inline bool PathManager::isValidPath(const U32 id) const
|
||||
{
|
||||
return (id < mPaths.size()) && mPaths[id]->positions.size() > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // _H_PATHMANAGER
|
2264
engine/sim/sceneObject.cc
Executable file
2264
engine/sim/sceneObject.cc
Executable file
File diff suppressed because it is too large
Load Diff
890
engine/sim/sceneObject.h
Executable file
890
engine/sim/sceneObject.h
Executable file
@ -0,0 +1,890 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _SCENEOBJECT_H_
|
||||
#define _SCENEOBJECT_H_
|
||||
|
||||
#ifndef _PLATFORM_H_
|
||||
#include "platform/platform.h"
|
||||
#endif
|
||||
#ifndef _NETOBJECT_H_
|
||||
#include "sim/netObject.h"
|
||||
#endif
|
||||
#ifndef _COLLISION_H_
|
||||
#include "collision/collision.h"
|
||||
#endif
|
||||
#ifndef _POLYHEDRON_H_
|
||||
#include "collision/polyhedron.h"
|
||||
#endif
|
||||
#ifndef _ABSTRACTPOLYLIST_H_
|
||||
#include "collision/abstractPolyList.h"
|
||||
#endif
|
||||
#ifndef _OBJECTTYPES_H_
|
||||
#include "game/objectTypes.h"
|
||||
#endif
|
||||
#ifndef _COLOR_H_
|
||||
#include "core/color.h"
|
||||
#endif
|
||||
|
||||
#include "lightingSystem/sgLightManager.h"
|
||||
|
||||
//-------------------------------------- Forward declarations...
|
||||
class SceneObject;
|
||||
class SceneGraph;
|
||||
class SceneState;
|
||||
class SceneRenderImage;
|
||||
class Box3F;
|
||||
class Point3F;
|
||||
class LightManager;
|
||||
class Convex;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// Extension of the collision structore to allow use with raycasting.
|
||||
///
|
||||
/// @see Collision
|
||||
struct RayInfo: public Collision
|
||||
{
|
||||
// The collision struct has object, point, normal & material.
|
||||
F32 t;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/// Reference to a scene object.
|
||||
///
|
||||
/// @note There are two indiscretions here. First is the name, which refers rather
|
||||
/// blatantly to the container bin system. A hygiene issue. Next is the
|
||||
/// user defined U32, which is added solely for the zoning system. This should
|
||||
/// properly be split up into two structures, for the disparate purposes, especially
|
||||
/// since it's not nice to force the container bin to use 20 bytes structures when
|
||||
/// it could get away with a 16 byte version.
|
||||
class SceneObjectRef
|
||||
{
|
||||
public:
|
||||
SceneObject* object;
|
||||
SceneObjectRef* nextInBin;
|
||||
SceneObjectRef* prevInBin;
|
||||
SceneObjectRef* nextInObj;
|
||||
|
||||
U32 zone;
|
||||
};
|
||||
|
||||
/// A scope frustum describes a pyramid to clip new portals against. It is
|
||||
/// rooted at the root position of the scoping query, which is not stored
|
||||
/// here.
|
||||
class ScopeFrustum
|
||||
{
|
||||
public:
|
||||
enum Constants {
|
||||
TopLeft = 0,
|
||||
TopRight = 1,
|
||||
BottomLeft = 2,
|
||||
BottomRight = 3
|
||||
};
|
||||
|
||||
Point3F frustumPoints[4];
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
class Container
|
||||
{
|
||||
public:
|
||||
struct Link
|
||||
{
|
||||
Link* next;
|
||||
Link* prev;
|
||||
Link();
|
||||
void unlink();
|
||||
void linkAfter(Link* ptr);
|
||||
};
|
||||
|
||||
struct CallbackInfo
|
||||
{
|
||||
AbstractPolyList* polyList;
|
||||
Box3F boundingBox;
|
||||
SphereF boundingSphere;
|
||||
void *key;
|
||||
};
|
||||
|
||||
static const U32 csmNumBins;
|
||||
static const F32 csmBinSize;
|
||||
static const F32 csmTotalBinSize;
|
||||
static const U32 csmRefPoolBlockSize;
|
||||
static U32 smCurrSeqKey;
|
||||
|
||||
private:
|
||||
Link mStart,mEnd;
|
||||
|
||||
SceneObjectRef* mFreeRefPool;
|
||||
Vector<SceneObjectRef*> mRefPoolBlocks;
|
||||
|
||||
SceneObjectRef* mBinArray;
|
||||
SceneObjectRef mOverflowBin;
|
||||
|
||||
public:
|
||||
Container();
|
||||
~Container();
|
||||
|
||||
/// @name Basic database operations
|
||||
/// @{
|
||||
|
||||
///
|
||||
typedef void (*FindCallback)(SceneObject*,void *key);
|
||||
void findObjects(U32 mask, FindCallback, void *key = NULL);
|
||||
void findObjects(const Box3F& box, U32 mask, FindCallback, void *key = NULL);
|
||||
void polyhedronFindObjects(const Polyhedron& polyhedron, U32 mask,
|
||||
FindCallback, void *key = NULL);
|
||||
/// @}
|
||||
|
||||
/// @name Line intersection
|
||||
/// @{
|
||||
|
||||
///
|
||||
bool castRay(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info);
|
||||
bool collideBox(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info);
|
||||
/// @}
|
||||
|
||||
/// @name Poly list
|
||||
/// @{
|
||||
|
||||
///
|
||||
bool buildPolyList(const Box3F& box, U32 mask, AbstractPolyList*,FindCallback=0,void *key = NULL);
|
||||
bool buildCollisionList(const Box3F& box, const Point3F& start, const Point3F& end, const VectorF& velocity,
|
||||
U32 mask,CollisionList* collisionList,FindCallback = 0,void *key = NULL,const Box3F *queryExpansion = 0);
|
||||
bool buildCollisionList(const Polyhedron& polyhedron,
|
||||
const Point3F& start, const Point3F& end,
|
||||
const VectorF& velocity,
|
||||
U32 mask, CollisionList* collisionList,
|
||||
FindCallback callback = 0, void *key = NULL);
|
||||
/// @}
|
||||
|
||||
///
|
||||
bool addObject(SceneObject*);
|
||||
bool removeObject(SceneObject*);
|
||||
|
||||
void addRefPoolBlock();
|
||||
SceneObjectRef* allocateObjectRef();
|
||||
void freeObjectRef(SceneObjectRef*);
|
||||
void insertIntoBins(SceneObject*);
|
||||
void removeFromBins(SceneObject*);
|
||||
|
||||
/// Checkbins makes sure that we're not just sticking the object right back
|
||||
/// where it came from. The overloaded insertInto is so we don't calculate
|
||||
/// the ranges twice.
|
||||
void checkBins(SceneObject*);
|
||||
void insertIntoBins(SceneObject*, U32, U32, U32, U32);
|
||||
|
||||
|
||||
private:
|
||||
Vector<SimObjectPtr<SceneObject>*> mSearchList;///< Object searches to support console querying of the database. ONLY WORKS ON SERVER
|
||||
S32 mCurrSearchPos;
|
||||
Point3F mSearchReferencePoint;
|
||||
void cleanupSearchVectors();
|
||||
|
||||
public:
|
||||
void initRadiusSearch(const Point3F& searchPoint,
|
||||
const F32 searchRadius,
|
||||
const U32 searchMask);
|
||||
U32 containerSearchNext();
|
||||
F32 containerSearchCurrDist();
|
||||
F32 containerSearchCurrRadiusDist();
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// For simple queries. Simply creates a vector of the objects
|
||||
class SimpleQueryList
|
||||
{
|
||||
public:
|
||||
Vector<SceneObject*> mList;
|
||||
|
||||
public:
|
||||
SimpleQueryList()
|
||||
{
|
||||
VECTOR_SET_ASSOCIATION(mList);
|
||||
}
|
||||
|
||||
void insertObject(SceneObject* obj) { mList.push_back(obj); }
|
||||
static void insertionCallback(SceneObject* obj, void *key);
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
/// A 3D object.
|
||||
///
|
||||
/// @section SceneObject_intro Introduction
|
||||
///
|
||||
/// SceneObject exists as a foundation for 3D objects in Torque. It provides the
|
||||
/// basic functionality for:
|
||||
/// - A scene graph (in the Zones and Portals sections), allowing efficient
|
||||
/// and robust rendering of the game scene.
|
||||
/// - Various helper functions, including functions to get bounding information
|
||||
/// and momentum/velocity.
|
||||
/// - Collision detection, as well as ray casting.
|
||||
/// - Lighting. SceneObjects can register lights both at lightmap generation time,
|
||||
/// and dynamic lights at runtime (for special effects, such as from flame or
|
||||
/// a projectile, or from an explosion).
|
||||
/// - Manipulating scene objects, for instance varying scale.
|
||||
///
|
||||
/// @section SceneObject_example An Example
|
||||
///
|
||||
/// Melv May has written a most marvelous example object deriving from SceneObject.
|
||||
/// Unfortunately this page is too small to contain it.
|
||||
///
|
||||
/// @see http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=3217
|
||||
/// for a copy of Melv's example.
|
||||
class SceneObject : public NetObject, public Container::Link
|
||||
{
|
||||
typedef NetObject Parent;
|
||||
friend class Container;
|
||||
friend class SceneGraph;
|
||||
friend class SceneState;
|
||||
|
||||
//-------------------------------------- Public constants
|
||||
public:
|
||||
enum
|
||||
{
|
||||
MaxObjectZones = 128
|
||||
};
|
||||
|
||||
enum TraversalState
|
||||
{
|
||||
Pending = 0,
|
||||
Working = 1,
|
||||
Done = 2
|
||||
};
|
||||
|
||||
enum SceneObjectMasks
|
||||
{
|
||||
ScaleMask = BIT(0),
|
||||
NextFreeMask = BIT(1)
|
||||
};
|
||||
|
||||
//-------------------------------------- Public interfaces
|
||||
// C'tors and D'tors
|
||||
private:
|
||||
SceneObject(const SceneObject&); ///< @deprecated disallowed
|
||||
|
||||
public:
|
||||
SceneObject();
|
||||
virtual ~SceneObject();
|
||||
|
||||
// forward declared for TSStatic and StaticShape
|
||||
// not used or transmited in any way by other classes
|
||||
// don't worry this is light-weight and network aware
|
||||
//
|
||||
// we need to override the options in all but the
|
||||
// objects that expose these to script - this is
|
||||
// static and NOT transmitted across the network...
|
||||
bool overrideOptions;
|
||||
bool receiveLMLighting;
|
||||
bool receiveSunLight;
|
||||
bool useAdaptiveSelfIllumination;
|
||||
bool useCustomAmbientLighting;
|
||||
bool customAmbientForSelfIllumination;
|
||||
ColorF customAmbientLighting;
|
||||
StringTableEntry lightGroupName;
|
||||
Vector<S32> lightIds;
|
||||
bool useLightingOcclusion;
|
||||
U32 moveSnapshotId;
|
||||
|
||||
void findLightGroup(NetConnection *con);
|
||||
void findLights(const char *name, NetConnection *con);
|
||||
|
||||
/// Returns a value representing this object which can be passed to script functions.
|
||||
const char* scriptThis();
|
||||
|
||||
public:
|
||||
/// @name Collision and transform related interface
|
||||
///
|
||||
/// The Render Transform is the interpolated transform with respect to the
|
||||
/// frame rate. The Render Transform will differ from the object transform
|
||||
/// because the simulation is updated in fixed intervals, which controls the
|
||||
/// object transform. The framerate is, most likely, higher than this rate,
|
||||
/// so that is why the render transform is interpolated and will differ slightly
|
||||
/// from the object transform.
|
||||
///
|
||||
/// @{
|
||||
|
||||
/// Disables collisions for this object including raycasts
|
||||
virtual void disableCollision();
|
||||
|
||||
/// Enables collisions for this object
|
||||
virtual void enableCollision();
|
||||
|
||||
/// Returns true if collisions are enabled
|
||||
bool isCollisionEnabled() const { return mCollisionCount == 0; }
|
||||
|
||||
/// Returns true if this object allows itself to be displaced
|
||||
/// @see displaceObject
|
||||
virtual bool isDisplacable() const;
|
||||
|
||||
/// Returns the momentum of this object
|
||||
virtual Point3F getMomentum() const;
|
||||
|
||||
/// Sets the momentum of this object
|
||||
/// @param momentum Momentum
|
||||
virtual void setMomentum(const Point3F &momentum);
|
||||
|
||||
/// Returns the mass of this object
|
||||
virtual F32 getMass() const;
|
||||
|
||||
/// Displaces this object by a vector
|
||||
/// @param displaceVector Displacement vector
|
||||
virtual bool displaceObject(const Point3F& displaceVector);
|
||||
|
||||
/// Returns the transform which can be used to convert object space
|
||||
/// to world space
|
||||
const MatrixF& getTransform() const { return mObjToWorld; }
|
||||
|
||||
/// Returns the transform which can be used to convert world space
|
||||
/// into object space
|
||||
const MatrixF& getWorldTransform() const { return mWorldToObj; }
|
||||
|
||||
/// Returns the scale of the object
|
||||
const VectorF& getScale() const { return mObjScale; }
|
||||
|
||||
/// Returns the bounding box for this object in local coordinates
|
||||
const Box3F& getObjBox() const { return mObjBox; }
|
||||
|
||||
/// Returns the bounding box for this object in world coordinates
|
||||
const Box3F& getWorldBox() const { return mWorldBox; }
|
||||
|
||||
/// Returns the bounding sphere for this object in world coordinates
|
||||
const SphereF& getWorldSphere() const { return mWorldSphere; }
|
||||
|
||||
/// Returns the center of the bounding box in world coordinates
|
||||
Point3F getBoxCenter() const { return (mWorldBox.min + mWorldBox.max) * 0.5f; }
|
||||
|
||||
/// Sets the Object -> World transform
|
||||
///
|
||||
/// @param mat New transform matrix
|
||||
virtual void setTransform(const MatrixF & mat);
|
||||
|
||||
/// Sets the scale for the object
|
||||
/// @param scale Scaling values
|
||||
virtual void setScale(const VectorF & scale);
|
||||
|
||||
/// This sets the render transform for this object
|
||||
/// @param mat New render transform
|
||||
virtual void setRenderTransform(const MatrixF &mat);
|
||||
|
||||
/// Returns the render transform
|
||||
const MatrixF& getRenderTransform() const { return mRenderObjToWorld; }
|
||||
|
||||
/// Returns the render transform to convert world to local coordinates
|
||||
const MatrixF& getRenderWorldTransform() const { return mRenderWorldToObj; }
|
||||
|
||||
/// Returns the render world box
|
||||
const Box3F& getRenderWorldBox() const { return mRenderWorldBox; }
|
||||
|
||||
/// Builds a convex hull for this object.
|
||||
///
|
||||
/// Think of a convex hull as a low-res mesh which covers, as tightly as
|
||||
/// possible, the object mesh, and is used as a collision mesh.
|
||||
/// @param box
|
||||
/// @param convex Convex mesh generated (out)
|
||||
virtual void buildConvex(const Box3F& box,Convex* convex);
|
||||
|
||||
/// Builds a list of polygons which intersect a bounding volume.
|
||||
///
|
||||
/// This will use either the sphere or the box, not both, the
|
||||
/// SceneObject implimentation ignores sphere.
|
||||
///
|
||||
/// @see AbstractPolyList
|
||||
/// @param polyList Poly list build (out)
|
||||
/// @param box Box bounding volume
|
||||
/// @param sphere Sphere bounding volume
|
||||
virtual bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere);
|
||||
|
||||
/// Builds a collision tree of all the polygons which collide with a bounding volume.
|
||||
///
|
||||
/// @note Not implemented in SceneObject. @see TerrainBlock::buildCollisionBSP
|
||||
/// @param tree BSP tree built (out)
|
||||
/// @param box Box bounding volume
|
||||
/// @param sphere Sphere bounding volume
|
||||
virtual BSPNode* buildCollisionBSP(BSPTree *tree, const Box3F &box, const SphereF &sphere);
|
||||
|
||||
/// Casts a ray and obtain collision information, returns true if RayInfo is modified.
|
||||
///
|
||||
/// @param start Start point of ray
|
||||
/// @param end End point of ray
|
||||
/// @param info Collision information obtained (out)
|
||||
virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info);
|
||||
|
||||
virtual bool collideBox(const Point3F &start, const Point3F &end, RayInfo* info);
|
||||
|
||||
/// Returns the position of the object.
|
||||
Point3F getPosition() const;
|
||||
|
||||
/// Returns the render-position of the object.
|
||||
///
|
||||
/// @see getRenderTransform
|
||||
Point3F getRenderPosition() const;
|
||||
|
||||
/// Sets the position of the object
|
||||
void setPosition(const Point3F &pos);
|
||||
|
||||
/// Gets the velocity of the object
|
||||
virtual Point3F getVelocity() const;
|
||||
|
||||
/// Sets the velocity of the object
|
||||
/// @param v Velocity
|
||||
virtual void setVelocity(const Point3F &v);
|
||||
|
||||
/// @}
|
||||
|
||||
public:
|
||||
/// @name Zones
|
||||
///
|
||||
/// A zone is a portalized section of an InteriorInstance, and an InteriorInstance can manage more than one zone.
|
||||
/// There is always at least one zone in the world, zone 0, which represens the whole world. Any
|
||||
/// other zone will be in an InteriorInstance. Torque keeps track of the zones containing an object
|
||||
/// as it moves throughout the world. An object can exists in multiple zones at once.
|
||||
/// @{
|
||||
|
||||
/// Returns true if this object is managing zones.
|
||||
///
|
||||
/// This is only true in the case of InteriorInstances which have zones in them.
|
||||
bool isManagingZones() const;
|
||||
|
||||
/// Gets the index of the first zone this object manages in the collection of zones.
|
||||
U32 getZoneRangeStart() const { return mZoneRangeStart; }
|
||||
|
||||
/// Gets the number of zones containing this object.
|
||||
U32 getNumCurrZones() const { return mNumCurrZones; }
|
||||
|
||||
/// Returns the nth zone containing this object.
|
||||
U32 getCurrZone(const U32 index) const;
|
||||
|
||||
/// If an object exists in multiple zones, this method will give you the
|
||||
/// number and indices of these zones (storing them in the provided variables).
|
||||
///
|
||||
/// @param obj Object in question.
|
||||
/// @param zones Indices of zones containing the object. (out)
|
||||
/// @param numZones Number of elements in the returned array. (out)
|
||||
virtual bool getOverlappingZones(SceneObject* obj, U32* zones, U32* numZones);
|
||||
|
||||
/// Returns the zone containing p.
|
||||
///
|
||||
/// @param p Point to test.
|
||||
virtual U32 getPointZone(const Point3F& p);
|
||||
|
||||
/// This is called on a zone managing object to scope all the zones managed.
|
||||
///
|
||||
/// @param rootPosition Camera position
|
||||
/// @param rootDistance Camera visible distance
|
||||
/// @param zoneScopeState Array of booleans which line up with the collection of zones, marked true if that zone is scoped (out)
|
||||
virtual bool scopeObject(const Point3F& rootPosition,
|
||||
const F32 rootDistance,
|
||||
bool* zoneScopeState);
|
||||
/// @}
|
||||
|
||||
/// Called when the object is supposed to render itself.
|
||||
///
|
||||
/// @param state Current rendering state.
|
||||
/// @see SceneState
|
||||
/// @param image Image associated with this object to render.
|
||||
/// @see SceneRenderImage
|
||||
virtual void renderObject(SceneState *state, SceneRenderImage *image);
|
||||
virtual void renderShadow( SceneState *state, SceneRenderImage *image){}
|
||||
|
||||
/// Called when the SceneGraph is ready for the registration of RenderImages.
|
||||
///
|
||||
/// @see SceneState
|
||||
///
|
||||
/// @param state SceneState
|
||||
/// @param stateKey State key of the current SceneState
|
||||
/// @param startZone Base zone index
|
||||
/// @param modifyBaseZoneState If true, the object needs to modify the zone state.
|
||||
virtual bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone,
|
||||
const bool modifyBaseZoneState = false);
|
||||
|
||||
/// Adds object to the client or server container depending on the object
|
||||
void addToScene();
|
||||
|
||||
/// Removes the object from the client/server container
|
||||
void removeFromScene();
|
||||
|
||||
//-------------------------------------- Derived class interface
|
||||
// Overrides
|
||||
protected:
|
||||
bool onAdd();
|
||||
void onRemove();
|
||||
|
||||
// Overrideables
|
||||
protected:
|
||||
/// Called when this is added to the SceneGraph.
|
||||
///
|
||||
/// @param graph SceneGraph this is getting added to
|
||||
virtual bool onSceneAdd(SceneGraph *graph);
|
||||
|
||||
/// Called when this is removed from the SceneGraph
|
||||
virtual void onSceneRemove();
|
||||
|
||||
/// Called when the size of the object changes
|
||||
virtual void onScaleChanged();
|
||||
|
||||
/// @name Portals
|
||||
/// @{
|
||||
|
||||
/// This is used by a portal controling object to transform the base-modelview
|
||||
/// used by the scenegraph for rendering to the modelview it needs to render correctly.
|
||||
///
|
||||
/// @see MirrorSubObject
|
||||
///
|
||||
/// @param portalIndex Index of portal in the list of portals controlled by the object.
|
||||
/// @param oldMV Current modelview matrix used by the SceneGraph (in)
|
||||
/// @param newMV New modelview to be used by the SceneGraph (out)
|
||||
virtual void transformModelview(const U32 portalIndex, const MatrixF& oldMV, MatrixF* newMV);
|
||||
|
||||
/// Used to tranform the position of a point based on a portal.
|
||||
///
|
||||
/// @param portalIndex Index of a portal to transform by.
|
||||
/// @param point Point to transform.
|
||||
virtual void transformPosition(const U32 portalIndex, Point3F& point);
|
||||
|
||||
/// Returns a new view frustum for the portal.
|
||||
///
|
||||
/// @param portalIndex Which portal in the list of portals the object controls
|
||||
/// @param oldFrustum Current frustum.
|
||||
/// @param nearPlane Near clipping plane.
|
||||
/// @param farPlane Far clipping plane.
|
||||
/// @param oldViewport Current viewport.
|
||||
/// @param newFrustum New view frustum to use. (out)
|
||||
/// @param newViewport New viewport to use. (out)
|
||||
/// @param flippedMatrix Should the object should use a flipped matrix to calculate viewport and frustum?
|
||||
virtual bool computeNewFrustum(const U32 portalIndex,
|
||||
const F64* oldFrustum,
|
||||
const F64 nearPlane,
|
||||
const F64 farPlane,
|
||||
const RectI& oldViewport,
|
||||
F64* newFrustum,
|
||||
RectI& newViewport,
|
||||
const bool flippedMatrix);
|
||||
|
||||
/// Called before things are to be rendered from the portals point of view, to set up
|
||||
/// everything the portal needs to render correctly.
|
||||
///
|
||||
/// @param portalIndex Index of portal to use.
|
||||
/// @param pCurrState Current SceneState
|
||||
/// @param pParentState SceneState used before this portal was activated
|
||||
virtual void openPortal(const U32 portalIndex,
|
||||
SceneState* pCurrState,
|
||||
SceneState* pParentState);
|
||||
|
||||
/// Called after rendering of a portal is complete, this resets the states
|
||||
/// the previous call to openPortal() changed.
|
||||
///
|
||||
/// @param portalIndex Index of portal to use.
|
||||
/// @param pCurrState Current SceneState
|
||||
/// @param pParentState SceneState used before this portal was activated
|
||||
virtual void closePortal(const U32 portalIndex,
|
||||
SceneState* pCurrState,
|
||||
SceneState* pParentState);
|
||||
public:
|
||||
|
||||
/// Returns the plane of the portal in world space.
|
||||
///
|
||||
/// @param portalIndex Index of portal to use.
|
||||
/// @param plane Plane of the portal in world space (out)
|
||||
virtual void getWSPortalPlane(const U32 portalIndex, PlaneF *plane);
|
||||
|
||||
/// @}
|
||||
|
||||
protected:
|
||||
/// Sets the mLastState and mLastStateKey.
|
||||
///
|
||||
/// @param state SceneState to set as the last state
|
||||
/// @param key Key to set as the last state key
|
||||
void setLastState(SceneState *state, U32 key);
|
||||
|
||||
/// Returns true if the provided SceneState and key are set as this object's
|
||||
/// last state and key.
|
||||
///
|
||||
/// @param state SceneState in question
|
||||
/// @param key State key in question
|
||||
bool isLastState(SceneState *state, U32 key) const;
|
||||
|
||||
|
||||
/// @name Traversal State
|
||||
///
|
||||
/// The SceneGraph traversal is recursive and the traversal state of an object
|
||||
/// can be one of three things:
|
||||
/// - Pending - The object has not yet been examined for zone traversal.
|
||||
/// - Working - The object is currently having its zones traversed.
|
||||
/// - Done - The object has had all of its zones traversed or doesn't manage zones.
|
||||
///
|
||||
/// @note These states were formerly referred to as TraverseColor, with White, Black, and
|
||||
/// Gray; this was changed in Torque 1.2 by Pat "KillerBunny" Wilson. This code is
|
||||
/// only used internal to this class
|
||||
/// @{
|
||||
|
||||
// These two replaced by TraversalState because that makes more sense -KB
|
||||
//void setTraverseColor(TraverseColor);
|
||||
//TraverseColor getTraverseColor() const;
|
||||
// ph34r teh muskrat! - Travis Colure
|
||||
|
||||
/// This sets the traversal state of the object.
|
||||
///
|
||||
/// @note This is used internally; you should not normally need to call it.
|
||||
/// @param s Traversal state to assign
|
||||
void setTraversalState( TraversalState s );
|
||||
|
||||
/// Returns the traversal state of this object
|
||||
TraversalState getTraversalState() const;
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Lighting
|
||||
/// @{
|
||||
|
||||
struct LightingInfo
|
||||
{
|
||||
LightingInfo();
|
||||
|
||||
bool mUseInfo;
|
||||
bool mDirty;
|
||||
ColorF mDefaultColor;
|
||||
//ColorF mAlarmColor;
|
||||
|
||||
//SimObjectPtr<SceneObject> mInterior;
|
||||
|
||||
bool mHasLastColor;
|
||||
ColorF mLastColor;
|
||||
U32 mLastTime;
|
||||
|
||||
static LightInfo smAmbientLight;
|
||||
|
||||
//enum
|
||||
//{
|
||||
// Interior = 0,
|
||||
// Terrain,
|
||||
// };
|
||||
//U32 mLightSource;
|
||||
};
|
||||
|
||||
struct posinfo
|
||||
{
|
||||
Point2I lexelspace;
|
||||
ColorF lexel;
|
||||
};
|
||||
|
||||
public:
|
||||
/// Gets the color of the ambient light in the area of the object and
|
||||
/// stores it in the provided ColorF.
|
||||
///
|
||||
/// @param col Ambient color (out)
|
||||
virtual bool getLightingAmbientColor(ColorF * col);
|
||||
|
||||
LightingInfo mLightingInfo; ///< Lighting info for this object
|
||||
|
||||
/// @}
|
||||
|
||||
protected:
|
||||
|
||||
/// @name Transform and Collision Members
|
||||
/// @{
|
||||
|
||||
///
|
||||
Container* mContainer;
|
||||
|
||||
MatrixF mObjToWorld; ///< Transform from object space to world space
|
||||
MatrixF mWorldToObj; ///< Transform from world space to object space (inverse)
|
||||
Point3F mObjScale; ///< Object scale
|
||||
|
||||
Box3F mObjBox; ///< Bounding box in object space
|
||||
Box3F mWorldBox; ///< Bounding box in world space
|
||||
SphereF mWorldSphere; ///< Bounding sphere in world space
|
||||
|
||||
MatrixF mRenderObjToWorld; ///< Render matrix to transform object space to world space
|
||||
MatrixF mRenderWorldToObj; ///< Render matrix to transform world space to object space
|
||||
Box3F mRenderWorldBox; ///< Render bounding box in world space
|
||||
SphereF mRenderWorldSphere; ///< Render bounxing sphere in world space
|
||||
|
||||
/// Regenerates the world-space bounding box and bounding sphere
|
||||
void resetWorldBox();
|
||||
|
||||
/// Regenerates the render-world-space bounding box and sphere
|
||||
void resetRenderWorldBox();
|
||||
|
||||
SceneObjectRef* mZoneRefHead;
|
||||
SceneObjectRef* mBinRefHead;
|
||||
|
||||
U32 mBinMinX;
|
||||
U32 mBinMaxX;
|
||||
U32 mBinMinY;
|
||||
U32 mBinMaxY;
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Container Interface
|
||||
///
|
||||
/// When objects are searched, we go through all the zones and ask them for
|
||||
/// all of their objects. Because an object can exist in multiple zones, the
|
||||
/// container sequence key is set to the id of the current search. Then, while
|
||||
/// searching, we check to see if an object's sequence key is the same as the
|
||||
/// current search key. If it is, it will NOT be added to the list of returns
|
||||
/// since it has already been processed.
|
||||
///
|
||||
/// @{
|
||||
|
||||
U32 mContainerSeqKey; ///< Container sequence key
|
||||
|
||||
/// Returns the container sequence key
|
||||
U32 getContainerSeqKey() const { return mContainerSeqKey; }
|
||||
|
||||
/// Sets the container sequence key
|
||||
void setContainerSeqKey(const U32 key) { mContainerSeqKey = key; }
|
||||
/// @}
|
||||
|
||||
public:
|
||||
|
||||
/// Returns a pointer to the container that contains this object
|
||||
Container* getContainer() { return mContainer; }
|
||||
|
||||
protected:
|
||||
S32 mCollisionCount;
|
||||
|
||||
bool mGlobalBounds;
|
||||
|
||||
public:
|
||||
/// Returns the type mask for this object
|
||||
U32 getTypeMask() { return(mTypeMask); }
|
||||
|
||||
const bool isGlobalBounds() const
|
||||
{
|
||||
return mGlobalBounds;
|
||||
}
|
||||
|
||||
/// If global bounds are set to be true, then the object is assumed to
|
||||
/// have an infinitely large bounding box for collision and rendering
|
||||
/// purposes.
|
||||
///
|
||||
/// They can't be toggled currently.
|
||||
void setGlobalBounds()
|
||||
{
|
||||
if(mContainer)
|
||||
mContainer->removeFromBins(this);
|
||||
|
||||
mGlobalBounds = true;
|
||||
mObjBox.min.set(-1e10, -1e10, -1e10);
|
||||
mObjBox.max.set( 1e10, 1e10, 1e10);
|
||||
|
||||
if(mContainer)
|
||||
mContainer->insertIntoBins(this);
|
||||
}
|
||||
|
||||
|
||||
/// @name Rendering Members
|
||||
/// @{
|
||||
protected:
|
||||
SceneGraph* mSceneManager; ///< SceneGraph that controls this object
|
||||
U32 mZoneRangeStart; ///< Start of range of zones this object controls, 0xFFFFFFFF == no zones
|
||||
|
||||
U32 mNumCurrZones; ///< Number of zones this object exists in
|
||||
|
||||
private:
|
||||
TraversalState mTraversalState; ///< State of this object in the SceneGraph traversal - DON'T MESS WITH THIS
|
||||
SceneState* mLastState; ///< Last SceneState that was used to render this object.
|
||||
U32 mLastStateKey; ///< Last state key that was used to render this object.
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Persist and console
|
||||
/// @{
|
||||
public:
|
||||
static void initPersistFields();
|
||||
void inspectPostApply();
|
||||
DECLARE_CONOBJECT(SceneObject);
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
extern Container gServerContainer;
|
||||
extern Container gClientContainer;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//-------------------------------------- Inlines
|
||||
//
|
||||
inline bool SceneObject::isManagingZones() const
|
||||
{
|
||||
return mZoneRangeStart != 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
inline void SceneObject::setLastState(SceneState* state, U32 key)
|
||||
{
|
||||
mLastState = state;
|
||||
mLastStateKey = key;
|
||||
}
|
||||
|
||||
inline bool SceneObject::isLastState(SceneState* state, U32 key) const
|
||||
{
|
||||
return (mLastState == state && mLastStateKey == key);
|
||||
}
|
||||
|
||||
inline void SceneObject::setTraversalState( TraversalState s ) {
|
||||
mTraversalState = s;
|
||||
}
|
||||
|
||||
inline SceneObject::TraversalState SceneObject::getTraversalState() const {
|
||||
return mTraversalState;
|
||||
}
|
||||
|
||||
inline U32 SceneObject::getCurrZone(const U32 index) const
|
||||
{
|
||||
// Not the most efficient way to do this, walking the list,
|
||||
// but it's an uncommon call...
|
||||
SceneObjectRef* walk = mZoneRefHead;
|
||||
for (U32 i = 0; i < index; i++)
|
||||
{
|
||||
walk = walk->nextInObj;
|
||||
AssertFatal(walk!=NULL, "Error, too few object refs!");
|
||||
}
|
||||
AssertFatal(walk!=NULL, "Error, too few object refs!");
|
||||
|
||||
return walk->zone;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
inline SceneObjectRef* Container::allocateObjectRef()
|
||||
{
|
||||
if (mFreeRefPool == NULL)
|
||||
{
|
||||
addRefPoolBlock();
|
||||
}
|
||||
AssertFatal(mFreeRefPool!=NULL, "Error, should always have a free reference here!");
|
||||
|
||||
SceneObjectRef* ret = mFreeRefPool;
|
||||
mFreeRefPool = mFreeRefPool->nextInObj;
|
||||
|
||||
ret->nextInObj = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline void Container::freeObjectRef(SceneObjectRef* trash)
|
||||
{
|
||||
trash->object = NULL;
|
||||
trash->nextInBin = NULL;
|
||||
trash->prevInBin = NULL;
|
||||
trash->nextInObj = mFreeRefPool;
|
||||
mFreeRefPool = trash;
|
||||
}
|
||||
|
||||
inline void Container::findObjects(U32 mask, FindCallback callback, void *key)
|
||||
{
|
||||
for (Link* itr = mStart.next; itr != &mEnd; itr = itr->next) {
|
||||
SceneObject* ptr = static_cast<SceneObject*>(itr);
|
||||
if ((ptr->getType() & mask) != 0 && !ptr->mCollisionCount)
|
||||
(*callback)(ptr,key);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _H_SCENEOBJECT_
|
||||
|
456
engine/sim/simPath.cc
Executable file
456
engine/sim/simPath.cc
Executable file
@ -0,0 +1,456 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "sim/simPath.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "sim/pathManager.h"
|
||||
#include "dgl/dgl.h"
|
||||
#include "sceneGraph/sceneState.h"
|
||||
#include "math/mathIO.h"
|
||||
#include "core/bitStream.h"
|
||||
|
||||
extern bool gEditingMission;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//-------------------------------------- Console functions and cmp funcs
|
||||
//
|
||||
namespace {
|
||||
|
||||
static ColorF cubeColors[8] = {
|
||||
ColorF(0, 0, 0),
|
||||
ColorF(1, 0, 0),
|
||||
ColorF(0, 1, 0),
|
||||
ColorF(0, 0, 1),
|
||||
ColorF(1, 1, 0),
|
||||
ColorF(1, 0, 1),
|
||||
ColorF(0, 1, 1),
|
||||
ColorF(1, 1, 1)
|
||||
};
|
||||
|
||||
static Point3F cubePoints[8] = {
|
||||
Point3F(-1, -1, -1),
|
||||
Point3F(-1, -1, 1),
|
||||
Point3F(-1, 1, -1),
|
||||
Point3F(-1, 1, 1),
|
||||
Point3F( 1, -1, -1),
|
||||
Point3F( 1, -1, 1),
|
||||
Point3F( 1, 1, -1),
|
||||
Point3F( 1, 1, 1)
|
||||
};
|
||||
|
||||
static U32 cubeFaces[6][4] = {
|
||||
{ 0, 2, 6, 4 },
|
||||
{ 0, 2, 3, 1 },
|
||||
{ 0, 1, 5, 4 },
|
||||
{ 3, 2, 6, 7 },
|
||||
{ 7, 6, 4, 5 },
|
||||
{ 3, 7, 5, 1 }
|
||||
};
|
||||
|
||||
void wireCube(F32 size, Point3F pos)
|
||||
{
|
||||
glDisable(GL_CULL_FACE);
|
||||
|
||||
for(int i = 0; i < 6; i++)
|
||||
{
|
||||
glBegin(GL_LINE_LOOP);
|
||||
for(int vert = 0; vert < 4; vert++)
|
||||
{
|
||||
int idx = cubeFaces[i][vert];
|
||||
glColor3f(cubeColors[idx].red, cubeColors[idx].green, cubeColors[idx].blue);
|
||||
glVertex3f(cubePoints[idx].x * size + pos.x, cubePoints[idx].y * size + pos.y, cubePoints[idx].z * size + pos.z);
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static Point3F wedgePoints[4] = {
|
||||
Point3F(-1, -1, 0),
|
||||
Point3F( 0, 1, 0),
|
||||
Point3F( 1, -1, 0),
|
||||
Point3F( 0,-.75, .5),
|
||||
};
|
||||
|
||||
static U32 wedgeFaces[4][3] = {
|
||||
{ 0, 3, 1 },
|
||||
{ 3, 1, 2 },
|
||||
{ 0, 3, 2 },
|
||||
{ 0, 2, 1 }
|
||||
};
|
||||
|
||||
|
||||
void wireWedge(F32 size, Point3F pos)
|
||||
{
|
||||
glDisable(GL_CULL_FACE);
|
||||
|
||||
for(int i = 0; i < 4; i++)
|
||||
{
|
||||
glBegin(GL_LINE_LOOP);
|
||||
for(int vert = 0; vert < 3; vert++)
|
||||
{
|
||||
int idx = wedgeFaces[i][vert];
|
||||
glColor3f(0,1,0);
|
||||
glVertex3f(wedgePoints[idx].x * size + pos.x, wedgePoints[idx].y * size + pos.y, wedgePoints[idx].z * size + pos.z);
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
}
|
||||
|
||||
ConsoleFunction( pathOnMissionLoadDone, void, 1, 1, "Load all path information from interiors.")
|
||||
{
|
||||
// Need to load subobjects for all loaded interiors...
|
||||
SimGroup* pMissionGroup = dynamic_cast<SimGroup*>(Sim::findObject("MissionGroup"));
|
||||
AssertFatal(pMissionGroup != NULL, "Error, mission done loading and no mission group?");
|
||||
|
||||
U32 currStart = 0;
|
||||
U32 currEnd = 1;
|
||||
Vector<SimGroup*> groups;
|
||||
groups.push_back(pMissionGroup);
|
||||
|
||||
while (true) {
|
||||
for (U32 i = currStart; i < currEnd; i++) {
|
||||
for (SimGroup::iterator itr = groups[i]->begin(); itr != groups[i]->end(); itr++) {
|
||||
if (dynamic_cast<SimGroup*>(*itr) != NULL)
|
||||
groups.push_back(static_cast<SimGroup*>(*itr));
|
||||
}
|
||||
}
|
||||
|
||||
if (groups.size() == currEnd) {
|
||||
break;
|
||||
} else {
|
||||
currStart = currEnd;
|
||||
currEnd = groups.size();
|
||||
}
|
||||
}
|
||||
|
||||
for (U32 i = 0; i < groups.size(); i++) {
|
||||
Path* pPath = dynamic_cast<Path*>(groups[i]);
|
||||
if (pPath)
|
||||
pPath->updatePath();
|
||||
}
|
||||
}
|
||||
|
||||
S32 FN_CDECL cmpPathObject(const void* p1, const void* p2)
|
||||
{
|
||||
SimObject* o1 = *((SimObject**)p1);
|
||||
SimObject* o2 = *((SimObject**)p2);
|
||||
|
||||
Marker* m1 = dynamic_cast<Marker*>(o1);
|
||||
Marker* m2 = dynamic_cast<Marker*>(o2);
|
||||
|
||||
if (m1 == NULL && m2 == NULL)
|
||||
return 0;
|
||||
else if (m1 != NULL && m2 == NULL)
|
||||
return 1;
|
||||
else if (m1 == NULL && m2 != NULL)
|
||||
return -1;
|
||||
else {
|
||||
// Both markers...
|
||||
return S32(m1->mSeqNum) - S32(m2->mSeqNum);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace {}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//-------------------------------------- Implementation
|
||||
//
|
||||
IMPLEMENT_CONOBJECT(Path);
|
||||
|
||||
Path::Path()
|
||||
{
|
||||
mPathIndex = NoPathIndex;
|
||||
mIsLooping = true;
|
||||
}
|
||||
|
||||
Path::~Path()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
void Path::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
addField("isLooping", TypeBool, Offset(mIsLooping, Path));
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
bool Path::onAdd()
|
||||
{
|
||||
if(!Parent::onAdd())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Path::onRemove()
|
||||
{
|
||||
//
|
||||
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// Sort the markers objects into sequence order
|
||||
void Path::sortMarkers()
|
||||
{
|
||||
dQsort(objectList.address(), objectList.size(), sizeof(SimObject*), cmpPathObject);
|
||||
}
|
||||
|
||||
void Path::updatePath()
|
||||
{
|
||||
// If we need to, allocate a path index from the manager
|
||||
if (mPathIndex == NoPathIndex)
|
||||
mPathIndex = gServerPathManager->allocatePathId();
|
||||
|
||||
sortMarkers();
|
||||
|
||||
Vector<Point3F> positions;
|
||||
Vector<QuatF> rotations;
|
||||
Vector<U32> times;
|
||||
Vector<U32> smoothingTypes;
|
||||
|
||||
for (iterator itr = begin(); itr != end(); itr++)
|
||||
{
|
||||
Marker* pMarker = dynamic_cast<Marker*>(*itr);
|
||||
if (pMarker != NULL)
|
||||
{
|
||||
Point3F pos;
|
||||
pMarker->getTransform().getColumn(3, &pos);
|
||||
positions.push_back(pos);
|
||||
|
||||
QuatF rot;
|
||||
rot.set(pMarker->getTransform());
|
||||
rotations.push_back(rot);
|
||||
|
||||
times.push_back(pMarker->mMSToNext);
|
||||
smoothingTypes.push_back(pMarker->mSmoothingType);
|
||||
}
|
||||
}
|
||||
|
||||
// DMMTODO: Looping paths.
|
||||
gServerPathManager->updatePath(mPathIndex, positions, rotations, times, smoothingTypes);
|
||||
}
|
||||
|
||||
void Path::addObject(SimObject* obj)
|
||||
{
|
||||
Parent::addObject(obj);
|
||||
|
||||
if (mPathIndex != NoPathIndex) {
|
||||
// If we're already finished, and this object is a marker, then we need to
|
||||
// update our path information...
|
||||
if (dynamic_cast<Marker*>(obj) != NULL)
|
||||
updatePath();
|
||||
}
|
||||
}
|
||||
|
||||
void Path::removeObject(SimObject* obj)
|
||||
{
|
||||
bool recalc = dynamic_cast<Marker*>(obj) != NULL;
|
||||
|
||||
Parent::removeObject(obj);
|
||||
|
||||
if (mPathIndex != NoPathIndex && recalc == true)
|
||||
updatePath();
|
||||
}
|
||||
|
||||
ConsoleMethod(Path,getPathId,S32,2, 2,"getPathId();")
|
||||
{
|
||||
return object->getPathIndex();
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//--------------------------------------------------------------------------
|
||||
IMPLEMENT_CO_NETOBJECT_V1(Marker);
|
||||
Marker::Marker()
|
||||
{
|
||||
// Not ghostable unless we're editing...
|
||||
mNetFlags.clear(Ghostable);
|
||||
|
||||
mTypeMask = MarkerObjectType;
|
||||
|
||||
mSeqNum = 0;
|
||||
mMSToNext = 1000;
|
||||
mSmoothingType = SmoothingTypeSpline;
|
||||
mKnotType = KnotTypeNormal;
|
||||
}
|
||||
|
||||
Marker::~Marker()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
static EnumTable::Enums markerEnums[] =
|
||||
{
|
||||
{ Marker::SmoothingTypeSpline , "Spline" },
|
||||
{ Marker::SmoothingTypeLinear , "Linear" },
|
||||
//{ Marker::SmoothingTypeAccelerate , "Accelerate" },
|
||||
};
|
||||
static EnumTable markerSmoothingTable(2, &markerEnums[0]);
|
||||
|
||||
static EnumTable::Enums knotEnums[] =
|
||||
{
|
||||
{ Marker::KnotTypeNormal , "Normal" },
|
||||
{ Marker::KnotTypePositionOnly, "Position Only" },
|
||||
{ Marker::KnotTypeKink, "Kink" },
|
||||
};
|
||||
static EnumTable markerKnotTable(3, &knotEnums[0]);
|
||||
|
||||
|
||||
void Marker::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
|
||||
addGroup("Misc");
|
||||
addField("seqNum", TypeS32, Offset(mSeqNum, Marker));
|
||||
addField("type", TypeEnum, Offset(mKnotType, Marker), 1, &markerKnotTable);
|
||||
addField("msToNext", TypeS32, Offset(mMSToNext, Marker));
|
||||
addField("smoothingType", TypeEnum, Offset(mSmoothingType, Marker), 1, &markerSmoothingTable);
|
||||
endGroup("Misc");
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
bool Marker::onAdd()
|
||||
{
|
||||
if(!Parent::onAdd())
|
||||
return false;
|
||||
|
||||
mObjBox = Box3F(Point3F(-.25, -.25, -.25), Point3F(.25, .25, .25));
|
||||
resetWorldBox();
|
||||
|
||||
if(gEditingMission)
|
||||
onEditorEnable();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Marker::onRemove()
|
||||
{
|
||||
if(gEditingMission)
|
||||
onEditorDisable();
|
||||
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
void Marker::onGroupAdd()
|
||||
{
|
||||
mSeqNum = getGroup()->size();
|
||||
}
|
||||
|
||||
|
||||
/// Enable scoping so we can see this thing on the client.
|
||||
void Marker::onEditorEnable()
|
||||
{
|
||||
mNetFlags.set(Ghostable);
|
||||
setScopeAlways();
|
||||
addToScene();
|
||||
}
|
||||
|
||||
/// Disable scoping so we can see this thing on the client
|
||||
void Marker::onEditorDisable()
|
||||
{
|
||||
removeFromScene();
|
||||
mNetFlags.clear(Ghostable);
|
||||
clearScopeAlways();
|
||||
}
|
||||
|
||||
|
||||
/// Tell our parent that this Path has been modified
|
||||
void Marker::inspectPostApply()
|
||||
{
|
||||
Path *path = dynamic_cast<Path*>(getGroup());
|
||||
if (path)
|
||||
path->updatePath();
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
bool Marker::prepRenderImage(SceneState* state, const U32 stateKey,
|
||||
const U32 /*startZone*/, const bool /*modifyBaseState*/)
|
||||
{
|
||||
if (isLastState(state, stateKey))
|
||||
return false;
|
||||
setLastState(state, stateKey);
|
||||
|
||||
// This should be sufficient for most objects that don't manage zones, and
|
||||
// don't need to return a specialized RenderImage...
|
||||
if (state->isObjectRendered(this)) {
|
||||
SceneRenderImage* image = new SceneRenderImage;
|
||||
image->obj = this;
|
||||
state->insertRenderImage(image);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void Marker::renderObject(SceneState* state, SceneRenderImage*)
|
||||
{
|
||||
AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry");
|
||||
|
||||
RectI viewport;
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glPushMatrix();
|
||||
dglGetViewport(&viewport);
|
||||
|
||||
// Uncomment this if this is a "simple" (non-zone managing) object
|
||||
state->setupObjectProjection(this);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glPushMatrix();
|
||||
dglMultMatrix(&mObjToWorld);
|
||||
glScalef(mObjScale.x, mObjScale.y, mObjScale.z);
|
||||
|
||||
// RENDER CODE HERE
|
||||
wireWedge(.25, Point3F(0, 0, 0));
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glPopMatrix();
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glPopMatrix();
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
dglSetViewport(viewport);
|
||||
|
||||
AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit");
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
U32 Marker::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
|
||||
{
|
||||
U32 retMask = Parent::packUpdate(con, mask, stream);
|
||||
|
||||
// Note that we don't really care about efficiency here, since this is an
|
||||
// edit-only ghost...
|
||||
stream->writeAffineTransform(mObjToWorld);
|
||||
|
||||
return retMask;
|
||||
}
|
||||
|
||||
void Marker::unpackUpdate(NetConnection* con, BitStream* stream)
|
||||
{
|
||||
Parent::unpackUpdate(con, stream);
|
||||
|
||||
// Transform
|
||||
MatrixF otow;
|
||||
stream->readAffineTransform(&otow);
|
||||
|
||||
setTransform(otow);
|
||||
}
|
||||
|
111
engine/sim/simPath.h
Executable file
111
engine/sim/simPath.h
Executable file
@ -0,0 +1,111 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _SIMPATH_H_
|
||||
#define _SIMPATH_H_
|
||||
|
||||
#ifndef _SCENEOBJECT_H_
|
||||
#include "sim/sceneObject.h"
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// A path!
|
||||
class Path : public SimGroup
|
||||
{
|
||||
typedef SimGroup Parent;
|
||||
|
||||
public:
|
||||
enum {
|
||||
NoPathIndex = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
|
||||
private:
|
||||
U32 mPathIndex;
|
||||
bool mIsLooping;
|
||||
|
||||
protected:
|
||||
bool onAdd();
|
||||
void onRemove();
|
||||
|
||||
public:
|
||||
Path();
|
||||
~Path();
|
||||
|
||||
void addObject(SimObject*);
|
||||
void removeObject(SimObject*);
|
||||
|
||||
void sortMarkers();
|
||||
void updatePath();
|
||||
bool isLooping() { return mIsLooping; }
|
||||
U32 getPathIndex() const;
|
||||
|
||||
DECLARE_CONOBJECT(Path);
|
||||
static void initPersistFields();
|
||||
};
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
class Marker : public SceneObject
|
||||
{
|
||||
typedef SceneObject Parent;
|
||||
friend class Path;
|
||||
|
||||
public:
|
||||
enum {
|
||||
SmoothingTypeLinear,
|
||||
SmoothingTypeSpline,
|
||||
SmoothingTypeAccelerate,
|
||||
};
|
||||
|
||||
enum {
|
||||
KnotTypeNormal,
|
||||
KnotTypePositionOnly,
|
||||
KnotTypeKink,
|
||||
};
|
||||
|
||||
|
||||
U32 mSeqNum;
|
||||
U32 mSmoothingType;
|
||||
U32 mKnotType;
|
||||
|
||||
U32 mMSToNext;
|
||||
|
||||
// Rendering
|
||||
protected:
|
||||
bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState);
|
||||
void renderObject(SceneState *state, SceneRenderImage *image);
|
||||
|
||||
protected:
|
||||
bool onAdd();
|
||||
void onRemove();
|
||||
void onGroupAdd();
|
||||
|
||||
void onEditorEnable();
|
||||
void onEditorDisable();
|
||||
|
||||
|
||||
public:
|
||||
Marker();
|
||||
~Marker();
|
||||
|
||||
DECLARE_CONOBJECT(Marker);
|
||||
static void initPersistFields();
|
||||
void inspectPostApply();
|
||||
|
||||
U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream);
|
||||
void unpackUpdate(NetConnection *conn, BitStream *stream);
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//--------------------------------------------------------------------------
|
||||
inline U32 Path::getPathIndex() const
|
||||
{
|
||||
return mPathIndex;
|
||||
}
|
||||
|
||||
|
||||
#endif // _H_PATH
|
||||
|
Reference in New Issue
Block a user