engine/game/gameConnection.cc
2024-01-07 04:36:33 +00:00

1636 lines
49 KiB
C++

//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/dnet.h"
#include "console/consoleTypes.h"
#include "console/simBase.h"
#include "game/gameConnection.h"
#include "game/shapeBase.h"
#include "core/bitStream.h"
#include "sim/pathManager.h"
#include "audio/audio.h"
#include "game/targetManager.h"
#include "game/netDispatch.h"
#include "game/game.h"
#include "scenegraph/sceneGraph.h"
#include "game/gameConnectionEvents.h"
#include "game/auth.h"
//----------------------------------------------------------------------------
#define MAX_VOICE_CHANNELS 3
#define MAX_MOVE_PACKET_SENDS 4
//----------------------------------------------------------------------------
const U32 targetCollisionMask = (TerrainObjectType |
InteriorObjectType |
PlayerObjectType |
VehicleObjectType |
StaticShapeObjectType |
ForceFieldObjectType |
TurretObjectType);
void GameConnection::sendTargetToServer(S32 targetId, Point3F pos)
{
AssertFatal((targetId >= -1) && (targetId < TargetManager::MaxTargets), "GameConnection::sendTargetToServer: invalid target id");
postNetEvent(new SetServerTargetEvent(targetId, pos));
}
bool GameConnection::sendLOSTarget()
{
MatrixF transform;
Point3F pos;
if(ShapeBase *cam = getControlObject())
{
cam->getEyeTransform(&transform);
transform.getColumn(3, &pos);
VectorF vec;
transform.mulV(VectorF(0,1000,0), &vec);
RayInfo rayInfo;
Point3F endPos = pos + vec;
cam->disableCollision();
if (gClientContainer.castRay(pos, endPos, targetCollisionMask, &rayInfo))
{
cam->enableCollision();
GameBase *obj = dynamic_cast<GameBase *>(rayInfo.object);
S32 target = -1;
if(obj)
target = obj->getTarget();
sendTargetToServer(target, rayInfo.point);
return true;
}
cam->enableCollision();
}
return false;
}
void GameConnection::setServerTarget(S32 targetId, Point3F targetPos)
{
AssertFatal((targetId >= -1) && (targetId < TargetManager::MaxTargets), "GameConnection::setServerTarget: invalid target id");
mTargetId = targetId;
mTargetPos = targetPos;
}
void GameConnection::sendTargetTo(NetConnection *conn, bool assign)
{
if((mTargetId < -1) || (mTargetId >= TargetManager::MaxTargets))
return;
if(!isServerConnection())
conn->postNetEvent(new TargetToEvent(mTargetId, mTargetPos, assign));
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT(GameConnection);
bool GameConnection::mFirstPerson = true;
S32 GameConnection::mLagThresholdMS = 0;
S32 GameConnection::smVoiceConnections[MaxClients];
//----------------------------------------------------------------------------
GameConnection::GameConnection(bool ghostFrom, bool ghostTo, bool sendEvents)
: NetConnection(ghostFrom, ghostTo, sendEvents)
{
mControlObject = NULL;
mLastMoveAck = 0;
mControlStateSkipCount = 0;
mLastClientMove = 0;
mFirstMoveIndex = 0;
mMoveCredit = MaxMoveCount;
mDataBlockModifiedKey = 0;
mMaxDataBlockModifiedKey = 0;
mAuthInfo = NULL;
mControlObjectModifyKey = 0;
mAckedControlObjectModifyKey = 0;
mRemoteControlObjectModifyKey = 0;
mLastSentControlObjectModifyKey = 0;
mMissionCRC = 0xffffffff;
mDamageFlash = mWhiteOut = 0;
mSelfLocked = false;
mSelfHomed = false;
mSeekerTracking = false;
mSeekerMode = ShapeBase::NotLocked;
mCameraPos = 0;
mCameraSpeed = 10;
mCameraFov = 90.f;
mUpdateCameraFov = false;
mAIControlled = false;
// voice stream
mWouldListenTo.setSize(MaxClients);
mWouldListenTo.set();
mListeningTo.setSize(MaxClients);
mListeningTo.clear();
mMaxVoicechannels = 0;
mCurVoicechannels = 0;
mVoiceDecodingMask = 0;
mVoiceEncodingLevel = -1;
mPinged = false;
mJammed = false;
mDisconnectReason[0] = 0;
for(U32 i = 0; i < TargetManager::TargetFreeMaskSize; i++)
mTargetVisibleMask[i] = 0;
mSensorGroup = 0;
mInCommanderMap = false;
mLockAudioHandle = NULL_AUDIOHANDLE;
mHomingAudioHandle = NULL_AUDIOHANDLE;
mTargetLockedAudioHandle = NULL_AUDIOHANDLE;
mTargetingAudioHandle = NULL_AUDIOHANDLE;
mTargetId = -1;
mTargetPos.set(0.f, 0.f, 0.f);
//blackout vars
mBlackOut = 0.0f;
mBlackOutTimeMS = 0;
mBlackOutStartTimeMS = 0;
mFadeToBlack = false;
}
GameConnection::~GameConnection()
{
if (mLockAudioHandle != NULL_AUDIOHANDLE)
alxStop(mLockAudioHandle);
if (mHomingAudioHandle != NULL_AUDIOHANDLE)
alxStop(mHomingAudioHandle);
if (mTargetLockedAudioHandle != NULL_AUDIOHANDLE)
alxStop(mTargetLockedAudioHandle);
if (mTargetingAudioHandle != NULL_AUDIOHANDLE)
alxStop(mTargetingAudioHandle);
mLockAudioHandle = NULL_AUDIOHANDLE;
mHomingAudioHandle = NULL_AUDIOHANDLE;
mTargetLockedAudioHandle = NULL_AUDIOHANDLE;
mTargetingAudioHandle = NULL_AUDIOHANDLE;
delete mAuthInfo;
}
void GameConnection::setAuthInfo(const AuthInfo *info)
{
mAuthInfo = new AuthInfo;
*mAuthInfo = *info;
}
const AuthInfo *GameConnection::getAuthInfo()
{
return mAuthInfo;
}
//----------------------------------------------------------------------------
void GameConnection::setControlObject(ShapeBase *obj)
{
if(obj == mControlObject)
return;
// if this is a connection to a client, update the control object modify key:
if(!isServerConnection())
mControlObjectModifyKey++;
if(mControlObject)
mControlObject->setControllingClient(0);
if(obj)
{
if (ShapeBase* coo = obj->getControllingObject())
coo->setControlObject(0);
if (GameConnection *con = obj->getControllingClient())
con->setControlObject(0);
obj->setControllingClient(this);
}
mControlObject = obj;
setScopeObject(mControlObject);
// if this is a client then set the fov and active image
if(isServerConnection() && mControlObject)
{
F32 fov = mControlObject->getDefaultCameraFov();
GameSetCameraFov(fov);
}
}
static S32 sChaseQueueSize = 0;
static MatrixF* sChaseQueue = 0;
static S32 sChaseQueueHead = 0;
static S32 sChaseQueueTail = 0;
bool GameConnection::getControlCameraTransform(F32 dt, MatrixF* mat)
{
ShapeBase* obj = getControlObject();
if(!obj)
return false;
ShapeBase* cObj = obj;
while((cObj = cObj->getControlObject()) != 0)
{
if(cObj->useObjsEyePoint())
obj = cObj;
}
if (dt) {
if (mFirstPerson || obj->onlyFirstPerson()) {
if (mCameraPos > 0)
if ((mCameraPos -= mCameraSpeed * dt) <= 0)
mCameraPos = 0;
}
else {
if (mCameraPos < 1)
if ((mCameraPos += mCameraSpeed * dt) > 1)
mCameraPos = 1;
}
}
if (!sChaseQueueSize || mFirstPerson || obj->onlyFirstPerson())
obj->getCameraTransform(&mCameraPos,mat);
else {
MatrixF& hm = sChaseQueue[sChaseQueueHead];
MatrixF& tm = sChaseQueue[sChaseQueueTail];
obj->getCameraTransform(&mCameraPos,&hm);
*mat = tm;
if (dt) {
if ((sChaseQueueHead += 1) >= sChaseQueueSize)
sChaseQueueHead = 0;
if (sChaseQueueHead == sChaseQueueTail)
if ((sChaseQueueTail += 1) >= sChaseQueueSize)
sChaseQueueTail = 0;
}
}
return true;
}
// bool GameConnection::getControlCameraFov(F32 * fov)
// {
// if(ShapeBase * obj = getControlObject())
// {
// ShapeBase* cObj = obj;
// while((cObj = cObj->getControlObject()) != 0)
// {
// if(cObj->useObjsEyePoint())
// obj = cObj;
// }
// *fov = obj->getCameraFov();
// return(true);
// }
// return(false);
// }
bool GameConnection::getControlCameraFov(F32 * fov)
{
//find the last control object in the chain (client->player->turret->whatever...)
ShapeBase *obj = getControlObject();
ShapeBase *cObj = NULL;
while (obj)
{
cObj = obj;
obj = obj->getControlObject();
}
if (cObj)
{
*fov = cObj->getCameraFov();
return(true);
}
return(false);
}
bool GameConnection::isValidControlCameraFov(F32 fov)
{
//find the last control object in the chain (client->player->turret->whatever...)
ShapeBase *obj = getControlObject();
ShapeBase *cObj = NULL;
while (obj)
{
cObj = obj;
obj = obj->getControlObject();
}
if (cObj)
return (cObj->isValidCameraFov(fov));
return(true);
}
bool GameConnection::setControlCameraFov(F32 fov)
{
//find the last control object in the chain (client->player->turret->whatever...)
ShapeBase *obj = getControlObject();
ShapeBase *cObj = NULL;
while (obj)
{
cObj = obj;
obj = obj->getControlObject();
}
if (cObj)
{
// allow shapebase to clamp fov to its datablock values
cObj->setCameraFov(mClampF(fov, MinCameraFov, MaxCameraFov));
fov = cObj->getCameraFov();
// server fov of client has 1degree resolution
if(S32(fov) != S32(mCameraFov))
mUpdateCameraFov = true;
mCameraFov = fov;
return(true);
}
return(false);
}
bool GameConnection::getControlCameraVelocity(Point3F *vel)
{
if (ShapeBase* obj = getControlObject()) {
*vel = obj->getVelocity();
return true;
}
return false;
}
void GameConnection::setObjectActiveImage(ShapeBase * obj, U32 slot)
{
AssertFatal(obj, "GameConnection::setControlObjectActiveImage: invalid obj ptr");
AssertFatal(slot < ShapeBase::MaxMountedImages, "GameConnection::setControlObjectActiveImage: image slot out of range");
if(isServerConnection())
return;
S32 gi = getGhostIndex(obj);
if(gi == -1)
return;
postNetEvent(new SetObjectActiveImageEvent(U32(gi), slot));
}
//----------------------------------------------------------------------------
bool GameConnection::onAdd()
{
if (!Parent::onAdd())
return false;
if (!isServerConnection())
{
// set the voice id
AssertFatal(MaxClients < 255, "Must increase voice id size");
for(U32 i = 1; i <= MaxClients; i++)
if(!smVoiceConnections[i])
{
smVoiceConnections[i] = getId();
mVoiceID = i;
break;
}
}
return true;
}
void GameConnection::onRemove()
{
if(isNetworkConnection())
{
Con::printf("Issuing Disconnect packet.");
// send a disconnect packet...
U32 serverConnectSequence, clientConnectSequence;
getSequences(&clientConnectSequence, &serverConnectSequence);
BitStream *out = BitStream::getPacketStream();
out->write(U8(Disconnect));
out->write(serverConnectSequence);
out->write(clientConnectSequence);
out->writeString(mDisconnectReason);
BitStream::sendPacketStream(getNetAddress());
}
if(!isServerConnection())
Con::executef(this, 2, "onDrop", mDisconnectReason);
if (mControlObject)
mControlObject->setControllingClient(0);
Parent::onRemove();
// clear the voice id
smVoiceConnections[mVoiceID] = 0;
mVoiceID = 0;
}
void GameConnection::setDisconnectReason(const char *str)
{
dStrncpy(mDisconnectReason, str, sizeof(mDisconnectReason) - 1);
mDisconnectReason[sizeof(mDisconnectReason) - 1] = 0;
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
void GameConnection::handleRecordedBlock(U32 type, U32 size, void *data)
{
switch(type)
{
case BlockTypeMove:
pushMove(*((Move *) data));
if(isRecording()) // put it back into the stream
recordBlock(Sim::getCurrentTime(), type, size, data);
break;
default:
Parent::handleRecordedBlock(type, size, data);
break;
}
}
void GameConnection::writeDemoStartBlock(ResizeBitStream *stream)
{
// write all the data blocks to the stream:
for(SimObjectId i = DataBlockObjectIdFirst; i <= DataBlockObjectIdLast; i++)
{
SimDataBlock *data;
if(Sim::findObject(i, data))
{
stream->writeFlag(true);
SimDataBlockEvent evt(data);
evt.pack(this, stream);
stream->validate();
}
}
stream->writeFlag(false);
stream->write(mFirstPerson);
stream->write(mCameraPos);
stream->write(mCameraSpeed);
stream->write(mLastMoveAck);
stream->write(mLastClientMove);
stream->write(mFirstMoveIndex);
stream->write(U32(mMoveList.size()));
for(U32 j = 0; j < mMoveList.size(); j++)
mMoveList[j].pack(stream);
Parent::writeDemoStartBlock(stream);
gTargetManager->writeDemoStartBlock(stream, this);
}
void GameConnection::readDemoStartBlock(BitStream *stream)
{
while(stream->readFlag())
{
SimDataBlockEvent evt;
evt.unpack(this, stream);
evt.process(this);
}
stream->read(&mFirstPerson);
stream->read(&mCameraPos);
stream->read(&mCameraSpeed);
stream->read(&mLastMoveAck);
stream->read(&mLastClientMove);
stream->read(&mFirstMoveIndex);
U32 size;
Move mv;
stream->read(&size);
mMoveList.clear();
while(size--)
{
mv.unpack(stream);
pushMove(mv);
}
Parent::readDemoStartBlock(stream);
gTargetManager->readDemoStartBlock(stream, this);
}
void GameConnection::demoPlaybackComplete()
{
static const char *demoPlaybackArgv[1] = { "demoPlaybackComplete" };
Sim::postCurrentEvent(Sim::getRootGroup(), new SimConsoleEvent(1, demoPlaybackArgv, false));
Parent::demoPlaybackComplete();
}
//----------------------------------------------------------------------------
void GameConnection::readPacket(BitStream *bstream)
{
char stringBuf[256];
stringBuf[0] = 0;
bstream->setStringBuffer(stringBuf);
clearCompression();
if (isServerConnection())
{
mLastMoveAck = bstream->readInt(32);
if (mLastMoveAck < mFirstMoveIndex)
mLastMoveAck = mFirstMoveIndex;
if(mLastMoveAck > mLastClientMove)
mLastClientMove = mLastMoveAck;
while(mFirstMoveIndex < mLastMoveAck)
{
AssertFatal(mMoveList.size(), "Popping off too many moves!");
mMoveList.pop_front();
mFirstMoveIndex++;
}
mDamageFlash = 0;
mWhiteOut = 0;
if(bstream->readFlag())
{
if(bstream->readFlag())
mDamageFlash = bstream->readFloat(7);
if(bstream->readFlag())
mWhiteOut = bstream->readFloat(7) * 1.5;
}
if(bstream->readFlag())
{
mSelfLocked = bstream->readFlag();
mSelfHomed = bstream->readFlag();
}
else
{
mSelfLocked = false;
mSelfHomed = false;
}
if(bstream->readFlag())
{
mSeekerTracking = bstream->readFlag();
mSeekerMode = bstream->readRangedU32(ShapeBase::NotLocked, ShapeBase::LockPosition);
if(mSeekerMode == ShapeBase::LockObject)
{
if(bstream->readFlag())
mSeekerObject = (ShapeBase *) resolveGhost(bstream->readRangedU32(0, MaxGhostCount-1));
else
mSeekerObject = NULL;
}
else if( mSeekerMode == ShapeBase::LockPosition )
{
bstream->read(&mTargetPos.x);
bstream->read(&mTargetPos.y);
bstream->read(&mTargetPos.z);
}
else
mSeekerObject = NULL;
}
else
{
mSeekerTracking = false;
mSeekerMode = ShapeBase::NotLocked;
mSeekerObject = NULL;
}
bool pinged = bstream->readFlag();
if(pinged != mPinged)
{
Con::executef(this, 2, "sensorPing", Con::getIntArg(pinged));
mPinged = pinged;
}
bool jammed = bstream->readFlag();
if(jammed != mJammed)
{
Con::executef(this, 2, "sensorJammed", Con::getIntArg(jammed));
mJammed = jammed;
}
if (bstream->readFlag())
{
if(bstream->readFlag())
{
// the control object is dirty...
// so we get an update:
mLastClientMove = mLastMoveAck;
bool callScript = false;
if(mControlObject.isNull())
callScript = true;
S32 gIndex = bstream->readInt(10);
ShapeBase* obj = static_cast<ShapeBase*>(resolveGhost(gIndex));
if (obj != mControlObject)
setControlObject(obj);
obj->readPacketData(this, bstream);
if(callScript)
Con::executef(this, 2, "initialControlSet");
}
else
{
// read out the compression point
Point3F pos;
bstream->read(&pos.x);
bstream->read(&pos.y);
bstream->read(&pos.z);
setCompressionPoint(pos);
}
}
while(bstream->readFlag())
{
U32 index = bstream->readInt(4);
U32 mask = bstream->readInt(32);
U32 start = index << 5;
while(mask)
{
if(mask & 1)
{
TargetInfo *ct = gTargetManager->getClientTarget(start);
ct->sensorFlags ^= TargetInfo::VisibleToSensor;
}
mask >>= 1;
start++;
}
}
// server forcing a fov change?
if(bstream->readFlag())
{
S32 fov = bstream->readInt(8);
setControlCameraFov(fov);
// don't bother telling the server if we were able to set the fov
F32 setFov;
if(getControlCameraFov(&setFov) && (S32(setFov) == fov))
mUpdateCameraFov = false;
// update the games fov info
GameSetCameraFov(fov);
}
}
else
{
bool fp = bstream->readFlag();
if(fp)
mCameraPos = 0;
else
mCameraPos = 1;
if(bstream->readFlag())
{
// the client wants a new control object state
U32 remoteKey = bstream->readInt(5);
if(remoteKey != mRemoteControlObjectModifyKey)
{
mControlObjectModifyKey++;
mRemoteControlObjectModifyKey = remoteKey;
}
}
moveReadPacket(bstream);
// check fov change.. 1degree granularity on server
if(bstream->readFlag())
{
S32 fov = mClamp(bstream->readInt(8), S32(MinCameraFov), S32(MaxCameraFov));
setControlCameraFov(fov);
// may need to force client back to a valid fov
F32 setFov;
if(getControlCameraFov(&setFov) && (S32(setFov) == fov))
mUpdateCameraFov = false;
}
}
Parent::readPacket(bstream);
clearCompression();
bstream->setStringBuffer(NULL);
}
void GameConnection::writePacket(BitStream *bstream, PacketNotify *note)
{
char stringBuf[256];
clearCompression();
stringBuf[0] = 0;
bstream->setStringBuffer(stringBuf);
GamePacketNotify *gnote = (GamePacketNotify *) note;
gnote->controlObjectModifyKey = 0;
U32 startPos = bstream->getCurPos();
if (isServerConnection())
{
bstream->writeFlag(mCameraPos == 0);
if(bstream->writeFlag(mControlObjectModifyKey != mAckedControlObjectModifyKey))
{
gnote->controlObjectModifyKey = mControlObjectModifyKey;
mLastSentControlObjectModifyKey = mControlObjectModifyKey;
bstream->writeInt(mControlObjectModifyKey, 5);
}
moveWritePacket(bstream);
// camera fov changed? (server fov resolution is 1 degree)
if(bstream->writeFlag(mUpdateCameraFov))
{
bstream->writeInt(mClamp(S32(mCameraFov), S32(MinCameraFov), S32(MaxCameraFov)), 8);
mUpdateCameraFov = false;
}
DEBUG_LOG(("PKLOG %d CLIENTMOVES: %d", getId(), bstream->getCurPos() - startPos));
}
else
{
// The only time mMoveList will not be empty at this
// point is during a change in control object.
bstream->writeInt(mLastMoveAck - mMoveList.size(),32);
TargetInfo *info = NULL;
S32 gIndex = -1;
// get the ghost index of the control object, and write out
// all the damage flash, white out and missile tracking information
if (!mControlObject.isNull())
{
gIndex = getGhostIndex(mControlObject);
info = mControlObject->getTargetInfo();
F32 flash = mControlObject->getDamageFlash();
F32 whiteOut = mControlObject->getWhiteOut();
if(bstream->writeFlag(flash != 0 || whiteOut != 0))
{
if(bstream->writeFlag(flash != 0))
bstream->writeFloat(flash, 7);
if(bstream->writeFlag(whiteOut != 0))
bstream->writeFloat(whiteOut/1.5, 7);
}
S32 lockCount = mControlObject->getLockCount(),
homingCount = mControlObject->getHomingCount();
// get the lock and homing counts off our mount as well..
ShapeBase *mount = mControlObject->getObjectMount();
if(mount)
{
lockCount += mount->getLockCount();
homingCount += mount->getHomingCount();
}
if(bstream->writeFlag(lockCount | homingCount))
{
bstream->writeFlag(lockCount > 0);
bstream->writeFlag(homingCount > 0);
}
ShapeBase *co = mControlObject->getControlObject();
if(!co)
co = mControlObject;
bool tracking = co->isTracking();
ShapeBase::LockMode mode = co->getLockMode();
ShapeBase *target = co->getLockedTarget();
if(bstream->writeFlag(tracking || mode != ShapeBase::NotLocked))
{
bstream->writeFlag(tracking);
bstream->writeRangedU32(mode, ShapeBase::NotLocked, ShapeBase::LockPosition);
if(mode == ShapeBase::LockObject)
{
S32 gi = -1;
if(target)
gi = getGhostIndex(target);
if(bstream->writeFlag(gi != -1))
bstream->writeRangedU32(U32(gi), 0, MaxGhostCount - 1);
}
else if( mode == ShapeBase::LockPosition )
{
Point3F targetPos = co->getLockedPosition();
bstream->write(targetPos.x);
bstream->write(targetPos.y);
bstream->write(targetPos.z);
}
}
}
else
{
bstream->writeFlag(false);
bstream->writeFlag(false);
bstream->writeFlag(false);
}
// always write out sensor ping stuff...
bstream->writeFlag(info ? info->sensorFlags & TargetInfo::SensorPinged : false);
bstream->writeFlag(info ? info->sensorFlags & TargetInfo::SensorJammed : false);
if (bstream->writeFlag(gIndex != -1))
{
// assume that the control object will write in a compression point
if(bstream->writeFlag(mControlObjectModifyKey != mAckedControlObjectModifyKey || mControlStateSkipCount >= ControlStateSkipAmount))
{
bstream->writeInt(gIndex,10);
// write out the control object - if it returns false, that means we have
// to send it again soon.
if(mControlObject->writePacketData(this, bstream))
{
gnote->controlObjectModifyKey = mControlObjectModifyKey;
mLastSentControlObjectModifyKey = mControlObjectModifyKey;
mControlStateSkipCount = 0;
}
}
else
{
// we'll have to use the control object's position as the compression point
// should make this lower res for better space usage:
mControlStateSkipCount++;
Point3F coPos = mControlObject->getPosition();
bstream->write(coPos.x);
bstream->write(coPos.y);
bstream->write(coPos.z);
setCompressionPoint(coPos);
}
}
DEBUG_LOG(("PKLOG %d CONTROLOBJECTSTATE: %d", getId(), bstream->getCurPos() - startPos));
startPos = bstream->getCurPos();
// now write the visible target mask
U32 *pingMask = gTargetManager->mSensorInfoArray[mSensorGroup].targetPingMask;
for(U32 i = 0; i < TargetManager::TargetFreeMaskSize; i++)
{
gnote->xorVisibleMask[i] = pingMask[i] ^ mTargetVisibleMask[i];
mTargetVisibleMask[i] = pingMask[i];
if(gnote->xorVisibleMask[i])
{
bstream->writeFlag(true);
bstream->writeInt(i, 4);
bstream->writeInt(gnote->xorVisibleMask[i], 32);
}
}
bstream->writeFlag(false);
// server forcing client fov?
gnote->cameraFov = -1;
if(bstream->writeFlag(mUpdateCameraFov))
{
gnote->cameraFov = mClamp(S32(mCameraFov), S32(MinCameraFov), S32(MaxCameraFov));
bstream->writeInt(gnote->cameraFov, 8);
mUpdateCameraFov = false;
}
DEBUG_LOG(("PKLOG %d PINGCAMSTATE: %d", getId(), bstream->getCurPos() - startPos));
}
Parent::writePacket(bstream, note);
clearCompression();
bstream->setStringBuffer(NULL);
}
void GameConnection::updateLockTones()
{
// we've locked our target
if( (mSeekerMode == ShapeBase::LockObject) || (mSeekerMode == ShapeBase::LockPosition) )
{
Con::executef(this, 2, "onTargetLocked", "true");
}
else
{
Con::executef(this, 2, "onTargetLocked", "false");
}
// hot targets in our view
if( mSeekerTracking )
{
Con::executef(this, 2, "onTrackingTarget", "true");
}
else
{
Con::executef(this, 2, "onTrackingTarget", "false");
}
}
void GameConnection::updateLockWarnings()
{
// someone has a lock on us
if( mSelfLocked )
{
if(!mSelfHomed)
Con::executef(this, 2, "onLockWarning", "true");
}
else
{
Con::executef(this, 2, "onLockWarning", "false");
}
// missile in the air, homing in on us
if( mSelfHomed )
{
Con::executef(this, 2, "onHomeWarning", "true");
}
else
{
Con::executef(this, 2, "onHomeWarning", "false");
}
}
void GameConnection::detectLag()
{
//see if we're lagging...
S32 curTime = Sim::getCurrentTime();
if (curTime - mLastPacketTime > mLagThresholdMS)
{
if (!mLagging)
{
mLagging = true;
Con::executef(this, 2, "setLagIcon", "true");
}
}
else if (mLagging)
{
mLagging = false;
Con::executef(this, 2, "setLagIcon", "false");
}
}
GameConnection::GamePacketNotify::GamePacketNotify()
{
// need to fill in empty notifes for demo start block
cameraFov = 0;
controlObjectModifyKey = 0;
for(U32 i = 0; i < TargetManager::TargetFreeMaskSize; i++)
xorVisibleMask[i] = 0;
}
NetConnection::PacketNotify *GameConnection::allocNotify()
{
return new GamePacketNotify;
}
void GameConnection::packetReceived(PacketNotify *note)
{
//record the time so we can tell if we're lagging...
mLastPacketTime = Sim::getCurrentTime();
GamePacketNotify *gnote = (GamePacketNotify *) note;
if(gnote->controlObjectModifyKey)
mAckedControlObjectModifyKey = gnote->controlObjectModifyKey;
Parent::packetReceived(note);
}
void GameConnection::packetDropped(PacketNotify *note)
{
Parent::packetDropped(note);
GamePacketNotify *gnote = (GamePacketNotify *) note;
for(U32 i = 0; i < TargetManager::TargetFreeMaskSize; i++)
mTargetVisibleMask[i] ^= gnote->xorVisibleMask[i];
if(gnote->cameraFov != -1)
mUpdateCameraFov = true;
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
void GameConnection::play2D(const AudioProfile* profile)
{
postNetEvent(new Sim2DAudioEvent(profile));
}
void GameConnection::play3D(const AudioProfile* profile, const MatrixF *transform)
{
if (transform) {
if (mControlObject) {
// Only post the event if it's within audible range
// of the control object.
Point3F ear,pos;
transform->getColumn(3,&pos);
mControlObject->getTransform().getColumn(3,&ear);
if ((ear - pos).len() < profile->mDescriptionObject->mDescription.mMaxDistance)
postNetEvent(new Sim3DAudioEvent(profile,transform));
}
else
postNetEvent(new Sim3DAudioEvent(profile,transform));
}
else
play2D(profile);
}
//--------------------------------------
bool GameConnection::getListenState(U8 voiceId)
{
return(mWouldListenTo.test(voiceId));
}
void GameConnection::listenTo(U8 voiceId, bool tf)
{
if (tf)
mWouldListenTo.set(voiceId);
else
{
// terminate any existing stream
GameConnection *itr = static_cast<GameConnection*>(getConnectionList());
while (itr != NULL)
{
if (!itr->isServerConnection())
itr->stopListening(mVoiceID);
itr = static_cast<GameConnection*>(itr->getNext());
}
// refuse any future request to talk to me
mWouldListenTo.clear(voiceId);
}
}
//--------------------------------------
void GameConnection::listenToAll()
{
mWouldListenTo.set();
}
//--------------------------------------
void GameConnection::listenToNone()
{
mWouldListenTo.clear();
}
bool GameConnection::canListen(GameConnection * con)
{
AssertFatal(con, "GameConnection::canListen: invalid connection passed!");
#ifndef DEBUG
// never allow a connection to listen to self
if(con == this)
return(false);
#endif
// Can't listen if no channels are available:
if ( mMaxVoicechannels == 0 )
return( false );
// make sure encoder/decoder's match
if(con->getVoiceEncodingLevel() < 0)
return(false);
if(!(mVoiceDecodingMask & (1 << con->getVoiceEncodingLevel())))
return(false);
// check the listen mask for this group
U32 listenMask = gTargetManager->getSensorGroupListenMask(getSensorGroup());
return(listenMask & (1 << con->getSensorGroup()));
}
//--------------------------------------
bool GameConnection::willListen(U8 voiceId)
{
AssertFatal(voiceId < MaxClients, "GameConnection::willListen: invalid voice id");
if(voiceId >= MaxClients)
return false;
// would we consider listening to this person?
if (mWouldListenTo.test(voiceId))
{
// can we listen to another person?
if (mCurVoicechannels < mMaxVoicechannels)
{
if (mListeningTo.test(voiceId))
stopListening(voiceId); // just in case
mListeningTo.set(voiceId);
mCurVoicechannels++;
return true;
}
else
{
// notify client that someone attempted to talk
Con::evaluatef("commandToClient(%d, 'playerStartTalking', %d, 0);", getId(), smVoiceConnections[voiceId]);
}
}
return false;
}
//--------------------------------------
void GameConnection::stopListening(U8 voiceId)
{
AssertFatal(voiceId < MaxClients, "GameConnection::stopListening: invalid voice id");
if(voiceId >= MaxClients)
return;
// if this client is talking to me quit listening and terminiate voice stream
if (mListeningTo.test(voiceId))
{
mCurVoicechannels--;
AssertWarn(0, "GameConnection::stopListening: Need to terminate existing stream & send event");
}
else
{
if (mWouldListenTo.test(voiceId))
{
// notify client that someone quit talking
Con::evaluatef("commandToClient(%d, 'playerStoppedTalking', %d, 0);", getId(), smVoiceConnections[voiceId]);
}
}
mListeningTo.clear(voiceId);
}
void GameConnection::setVoiceChannels(S32 num)
{
mMaxVoicechannels = mClamp(num, 0, MAX_VOICE_CHANNELS); // 0-MAX_VOICE_CHANNELS active talking channels
}
//----------------------------------------------------------------------------
// Object scoping callback
static void commanderScopeCallback(SceneObject* obj, S32 value)
{
const U32 scopeMask = (TerrainObjectType |
InteriorObjectType |
WaterObjectType |
PlayerObjectType |
VehicleObjectType |
StaticShapeObjectType);
GameConnection * con = reinterpret_cast<GameConnection*>(value);
if(obj->isScopeable() && (obj->getType() & scopeMask))
con->objectInScope(obj);
}
void GameConnection::doneScopingScene()
{
// scope all in commander map
if(mInCommanderMap)
gServerContainer.findObjects(-1, commanderScopeCallback, reinterpret_cast<S32>(this));
else
{
// scope any visible object from sensorVisibleSet
SimSet * scopeSet = Sim::getScopeSensorVisibleSet();
for(SimSet::iterator itr = scopeSet->begin(); itr != scopeSet->end(); itr++)
{
AssertFatal(((*itr)->getType() & ShapeBaseObjectType), "GameConnection: invalid object in scope set");
ShapeBase * obj = static_cast<ShapeBase*>(*itr);
if(obj->isHidden() || (obj->getTarget() == -1) || !obj->isScopeable() ||
!gTargetManager->isTargetVisible(obj->getTarget(), getSensorGroup()))
continue;
objectInScope(obj);
}
}
}
//----------------------------------------------------------------------------
void GameConnection::setSensorGroup(U32 group)
{
if(group == mSensorGroup)
return;
if(!isServerConnection())
{
gTargetManager->clientSensorGroupChanged(this, group);
postNetEvent(new SetSensorGroupEvent(group));
}
mSensorGroup = group;
}
void GameConnection::resetVisibleMasks()
{
for(U32 i = 0; i < TargetManager::TargetFreeMaskSize; i++)
mTargetVisibleMask[i] = 0;
}
//----------------------------------------------------------------------------
//localconnection only blackout functions
void GameConnection::setBlackOut(bool fadeToBlack, S32 timeMS)
{
mFadeToBlack = fadeToBlack;
mBlackOutStartTimeMS = Sim::getCurrentTime();
mBlackOutTimeMS = timeMS;
//if timeMS <= 0 set the value instantly
if (mBlackOutTimeMS <= 0)
mBlackOut = (mFadeToBlack ? 1.0f : 0.0f);
}
F32 GameConnection::getBlackOut()
{
S32 curTime = Sim::getCurrentTime();
//see if we're in the middle of a black out
if (curTime < mBlackOutStartTimeMS + mBlackOutTimeMS)
{
S32 elapsedTime = curTime - mBlackOutStartTimeMS;
F32 timePercent = F32(elapsedTime) / F32(mBlackOutTimeMS);
mBlackOut = (mFadeToBlack ? timePercent : 1.0f - timePercent);
}
else
mBlackOut = (mFadeToBlack ? 1.0f : 0.0f);
//return the blackout time
return mBlackOut;
}
void GameConnection::handleGhostMessage(S32 message, U32 sequence, U32 ghostCount)
{
if(isServerConnection())
{
if(message == GhostAlwaysStarting)
{
// Authentication removed.
// if(!clientAuth.valid)
// Sim::postEvent( Sim::getRootGroup(), new QuitEvent(), Sim::getTargetTime() + 10000 );
}
}
Parent::handleGhostMessage(message, sequence, ghostCount);
}
//----------------------------------------------------------------------------
static void cTransmitDataBlocks(SimObject *obj, S32, const char **argv)
{
GameConnection *cptr = (GameConnection *) obj;
cptr->setDataBlockSequence(dAtoi(argv[2]));
SimDataBlockGroup *g = Sim::getDataBlockGroup();
//g->sort();
// check for the early out:
U32 groupCount = g->size();
if(!groupCount)
return;
S32 key = cptr->getDataBlockModifiedKey();
// find the first one we haven't sent:
U32 i;
for(i = 0; i < groupCount; i++)
if(( (SimDataBlock *)(*g)[i])->getModifiedKey() > key)
break;
if(i == groupCount)
{
Con::executef(cptr, 2, "dataBlocksDone", Con::getIntArg(cptr->getDataBlockSequence()));
return;
}
cptr->setMaxDataBlockModifiedKey(key);
U32 max = getMin(i + DataBlockQueueCount, groupCount);
for(;i < max; i++)
{
SimDataBlock *data = (SimDataBlock *)(*g)[i];
cptr->postNetEvent(new SimDataBlockEvent(data, i, groupCount, cptr->getDataBlockSequence()));
}
}
static void cActivateGhosting(SimObject *obj, S32, const char **)
{
NetConnection *cptr = (NetConnection *) obj;
cptr->activateGhosting();
}
static void cResetGhosting(SimObject *obj, S32, const char **)
{
NetConnection *cptr = (NetConnection *) obj;
cptr->resetGhosting();
}
static bool cSetControlObject(SimObject *obj, S32, const char **argv)
{
GameConnection *cptr = (GameConnection *) obj;
ShapeBase *gb;
if(!Sim::findObject(argv[2], gb))
return false;
cptr->setControlObject(gb);
return true;
}
static int cGetControlObject(SimObject *obj, S32, const char **argv)
{
argv;
GameConnection *cptr = (GameConnection *) obj;
SimObject* cp = cptr->getControlObject();
return cp? cp->getId(): 0;
}
static bool cIsAIControlled(SimObject *obj, S32, const char **)
{
GameConnection *cptr = (GameConnection *) obj;
return cptr->isAIControlled();
}
static bool cPlay2D(SimObject *obj, S32, const char **argv)
{
GameConnection *cptr = (GameConnection *) obj;
AudioProfile *profile;
if(!Sim::findObject(argv[2], profile))
return false;
cptr->play2D(profile);
return true;
}
static bool cPlay3D(SimObject *obj, S32, const char **argv)
{
GameConnection *cptr = (GameConnection *) obj;
AudioProfile *profile;
if(!Sim::findObject(argv[2], profile))
return false;
Point3F pos(0,0,0);
AngAxisF aa;
aa.axis.set(0,0,1);
aa.angle = 0;
dSscanf(argv[3],"%f %f %f %f %f %f %f",
&pos.x,&pos.y,&pos.z,&aa.axis.x,&aa.axis.y,&aa.axis.z,&aa.angle);
MatrixF mat;
aa.setMatrix(&mat);
mat.setColumn(3,pos);
cptr->play3D(profile,&mat);
return true;
}
static bool cChaseCam(SimObject *obj, S32, const char **argv)
{
GameConnection *cptr = (GameConnection *) obj;
S32 size = dAtoi(argv[2]);
if (size != sChaseQueueSize) {
delete [] sChaseQueue;
sChaseQueue = 0;
sChaseQueueSize = size;
sChaseQueueHead = sChaseQueueTail = 0;
if (size) {
sChaseQueue = new MatrixF[size];
return true;
}
}
return false;
}
static void cSetSensorGroup(SimObject *obj, S32, const char **argv)
{
GameConnection *cptr = (GameConnection *) obj;
if(!cptr->isServerConnection())
cptr->setSensorGroup(dAtoi(argv[2]));
}
static S32 cGetSensorGroup(SimObject *obj, S32, const char **)
{
GameConnection *con = static_cast<GameConnection*>(obj);
return(S32(con->getSensorGroup()));
}
static bool cSendLOSTarget(SimObject *obj, S32, const char **)
{
GameConnection *cptr = (GameConnection *) obj;
return cptr->sendLOSTarget();
}
static void cSendTargetToServer(SimObject * obj, S32, const char ** argv)
{
S32 targetId = dAtoi(argv[2]);
if((targetId < -1) || (targetId >= TargetManager::MaxTargets))
{
Con::errorf(ConsoleLogEntry::General, "GameConnection::sendTargetToServer: invalid target id [%s]", argv[2]);
return;
}
GameConnection * con = static_cast<GameConnection*>(obj);
Point3F pos(0,0,0);
dSscanf(argv[3], "%f %f %f", &pos.x, &pos.y, &pos.z);
con->sendTargetToServer(targetId, pos);
}
static void cSendTargetTo(SimObject *obj, S32, const char **argv)
{
GameConnection *cptr = (GameConnection *) obj;
NetConnection *dest;
if(Sim::findObject(argv[2], dest))
cptr->sendTargetTo(dest, dAtob(argv[3]));
}
static S32 cGetTargetId(SimObject *obj, S32, const char **)
{
GameConnection * con = static_cast<GameConnection*>(obj);
if(con->isServerConnection())
return(-1);
return(con->getTargetId());
}
static S32 cGetHomingCount(SimObject *obj, S32, const char **)
{
GameConnection * con = static_cast<GameConnection*>(obj);
if(con->isServerConnection())
return(-1);
return(con->getSelfHomed());
}
static const char * cGetTargetPos(SimObject *obj, S32, const char **)
{
GameConnection * con = static_cast<GameConnection*>(obj);
if(con->isServerConnection())
return("");
Point3F pos = con->getTargetPos();
char * buf = Con::getReturnBuffer(128);
dSprintf(buf, 128, "%f %f %f", pos.x, pos.y, pos.z);
return(buf);
}
static void cSetTargetId(SimObject *obj, S32, const char **argv)
{
GameConnection *con = static_cast<GameConnection*>(obj);
S32 targetId = dAtoi(argv[2]);
if((targetId < -1) || (targetId >= TargetManager::MaxTargets))
{
Con::errorf(ConsoleLogEntry::General, "GameConnection::cSetTargetId: invalid target id [%d]", targetId);
return;
}
con->setTargetId(targetId);
}
static void cSetTargetPos(SimObject *obj, S32, const char **argv)
{
GameConnection * con = static_cast<GameConnection*>(obj);
Point3F pos(0,0,0);
dSscanf(argv[2],"%f %f %f", &pos.x, &pos.y, &pos.z);
con->setTargetPos(pos);
}
static bool cIsScopingCommanderMap(SimObject *obj, S32, const char **)
{
GameConnection * con = static_cast<GameConnection *>(obj);
return(con->isScopingCommanderMap());
}
static void cScopeCommanderMap(SimObject *obj, S32, const char ** argv)
{
GameConnection * con = static_cast<GameConnection*>(obj);
con->scopeCommanderMap(dAtob(argv[2]));
}
// Camera: (fov in degrees) --------------------------------------------------
static void cSetControlCameraFov(SimObject * obj, S32, const char ** argv)
{
GameConnection * con = static_cast<GameConnection*>(obj);
con->setControlCameraFov(dAtoi(argv[2]));
}
static F32 cGetControlCameraFov(SimObject * obj, S32, const char **)
{
GameConnection * con = static_cast<GameConnection*>(obj);
F32 fov = 0.f;
if(!con->getControlCameraFov(&fov))
return(0.f);
return(fov);
}
//--------------------------------------------------------------------------
static bool cListenEnabled( SimObject* obj, S32, const char** )
{
return( static_cast<GameConnection*>( obj )->listenEnabled() );
}
static bool cGetListenState(SimObject *obj, S32, const char **argv)
{
GameConnection * me = (GameConnection *) obj;
GameConnection * him = dynamic_cast<GameConnection*>(Sim::findObject(argv[2]));
if(!him)
return(false);
return(me->getListenState(him->getVoiceID()));
}
static bool cCanListenTo(SimObject *obj, S32, const char **argv)
{
GameConnection* me = (GameConnection*) obj;
GameConnection* him = dynamic_cast<GameConnection*>(Sim::findObject(argv[2]));
if ( !him )
return( false );
return( me->canListen( him ) );
}
static void cListenTo(SimObject *obj, S32, const char **argv)
{
GameConnection * me = (GameConnection *) obj;
GameConnection * him = dynamic_cast<GameConnection*>(Sim::findObject(argv[2]));
if(!him)
return;
me->listenTo(him->getVoiceID(), dAtob(argv[3]));
}
static void cListenToAll(SimObject *obj, S32, const char **argv)
{
argv;
GameConnection *cptr = (GameConnection *) obj;
cptr->listenToAll();
}
static void cListenToNone(SimObject *obj, S32, const char **argv)
{
argv;
GameConnection *cptr = (GameConnection *) obj;
cptr->listenToNone();
}
static void cSetVoiceChannels(SimObject *obj, S32, const char **argv)
{
GameConnection *cptr = (GameConnection *) obj;
cptr->setVoiceChannels(dAtoi(argv[2]));
}
static void cSetVoiceDecodingMask(SimObject *obj, S32, const char ** argv)
{
GameConnection *cptr = (GameConnection *) obj;
cptr->setVoiceDecodingMask(dAtoi(argv[2]));
}
static void cSetVoiceEncodingLevel(SimObject *obj, S32, const char ** argv)
{
GameConnection *cptr = (GameConnection *) obj;
cptr->setVoiceEncodingLevel(dAtoi(argv[2]));
}
//--------------------------------------------------------------------------
static void cSetObjectActiveImage(SimObject * ptr, S32, const char ** argv)
{
GameConnection * con = static_cast<GameConnection*>(ptr);
if(con->isServerConnection())
return;
ShapeBase * obj = dynamic_cast<ShapeBase*>(Sim::findObject(argv[2]));
if(!obj)
{
Con::errorf(ConsoleLogEntry::General, "GameConnection::cSetObjectActiveImage: invalid object %s", argv[2]);
return;
}
U32 imageSlot = U32(dAtoi(argv[3]));
if(imageSlot >= ShapeBase::MaxMountedImages)
{
Con::errorf(ConsoleLogEntry::General, "GameConnection::cSetControlObjectActiveImage: image slot out of range [%d]", imageSlot);
return;
}
con->setObjectActiveImage(obj, imageSlot);
}
static void cSetBlackOut(SimObject * ptr, S32, const char ** argv)
{
GameConnection * con = static_cast<GameConnection*>(ptr);
con->setBlackOut(dAtob(argv[2]), dAtoi(argv[3]));
}
static void cSetMissionCRC(SimObject * obj, S32, const char ** argv)
{
GameConnection * con = static_cast<GameConnection*>(obj);
if(con->isServerConnection())
return;
con->postNetEvent(new SetMissionCRCEvent(dAtoi(argv[2])));
}
//--------------------------------------------------------------------------
void GameConnection::consoleInit()
{
Con::addVariable("firstPerson", TypeBool, &mFirstPerson);
Con::addVariable("pref::Net::lagThreshold", TypeS32, &mLagThresholdMS);
Con::addCommand("GameConnection", "chaseCam", cChaseCam, "conn.chaseCam(size)", 3, 3);
Con::addCommand("GameConnection", "setControlCameraFov", cSetControlCameraFov, "conn.setControlCameraFov(fov)", 3, 3);
Con::addCommand("GameConnection", "getControlCameraFov", cGetControlCameraFov, "conn.getControlCameraFov()", 2, 2);
Con::addCommand("GameConnection", "setSensorGroup", cSetSensorGroup, "conn.setSensorGroup(groupId)", 3, 3);
Con::addCommand("GameConnection", "getSensorGroup", cGetSensorGroup, "conn.getSensorGroup()", 2, 2);
Con::addCommand("GameConnection", "transmitDataBlocks", cTransmitDataBlocks, "conn.transmitDataBlocks(seq)", 3, 3);
Con::addCommand("GameConnection", "activateGhosting", cActivateGhosting, "conn.activateGhosting()", 2, 2);
Con::addCommand("GameConnection", "resetGhosting", cResetGhosting, "conn.resetGhosting()", 2, 2);
Con::addCommand("GameConnection", "setControlObject", cSetControlObject, "conn.setControlObject(%obj)", 3, 3);
Con::addCommand("GameConnection", "getControlObject", cGetControlObject, "conn.getControlObject()", 2, 2);
Con::addCommand("GameConnection", "isAIControlled", cIsAIControlled, "conn.isAIControlled()", 2, 2);
Con::addCommand("GameConnection", "setObjectActiveImage", cSetObjectActiveImage, "conn.setObjectActiveImage(obj, imageSlot)", 4, 4);
Con::addCommand("GameConnection", "play2D", cPlay2D, "conn.play2D(AudioProfile)", 3, 3);
Con::addCommand("GameConnection", "play3D", cPlay3D, "conn.play3D(AudioProfile,Transform)", 4, 4);
Con::addCommand("GameConnection", "sendLOSTarget", cSendLOSTarget, "conn.sendLOSTarget()", 2, 2);
Con::addCommand("GameConnection", "sendTargetToServer", cSendTargetToServer, "conn.sendTargetToServer(id, pos)", 4, 4);
Con::addCommand("GameConnection", "sendTargetTo", cSendTargetTo, "conn.sendTargetTo(conn, assign)", 4, 4);
Con::addCommand("GameConnection", "getTargetId", cGetTargetId, "conn.getTargetId()", 2, 2);
Con::addCommand("GameConnection", "getTargetPos", cGetTargetPos, "conn.getTargetPos()", 2, 2);
Con::addCommand("GameConnection", "setTargetId", cSetTargetId, "conn.setTargetId(targetId)", 3, 3);
Con::addCommand("GameConnection", "setTargetPos", cSetTargetPos, "conn.setTargetPos(Point3F)", 3, 3);
Con::addCommand("GameConnection", "getHomingCount", cGetHomingCount, "conn.getHomingCount()", 2, 2);
Con::addCommand("GameConnection", "isScopingCommanderMap", cIsScopingCommanderMap, "conn.isScopingCommanderMap()", 2, 2);
Con::addCommand("GameConnection", "scopeCommanderMap", cScopeCommanderMap, "conn.scopeCommanderMap(bool)", 3, 3);
Con::addCommand("GameConnection", "listenEnabled", cListenEnabled, "conn.listenEnabled()", 2, 2);
Con::addCommand("GameConnection", "getListenState", cGetListenState, "conn.getListenState(clientId)", 3, 3);
Con::addCommand("GameConnection", "canListenTo", cCanListenTo, "conn.canListen(clientId)", 3, 3);
Con::addCommand("GameConnection", "listenTo", cListenTo, "conn.listenTo(clientId, true|false)", 4, 4);
Con::addCommand("GameConnection", "listenToAll", cListenToAll, "conn.listenToAll()", 2, 2);
Con::addCommand("GameConnection", "listenToNone", cListenToNone, "conn.listenToNone()", 2, 2);
Con::addCommand("GameConnection", "setVoiceChannels", cSetVoiceChannels, "conn.setVoiceChannels(0-3)", 3, 3);
Con::addCommand("GameConnection", "setVoiceDecodingMask", cSetVoiceDecodingMask, "conn.setVoiceDecodingMask(mask)", 3, 3);
Con::addCommand("GameConnection", "setVoiceEncodingLevel", cSetVoiceEncodingLevel, "conn.setVoiceEncodingLevel(codecLevel)", 3, 3);
Con::addVariable("specialFog", TypeBool, &SceneGraph::useSpecial);
Con::addCommand("GameConnection", "setBlackOut", cSetBlackOut, "conn.setBlackOut(fadeTOBlackBool, timeMS)", 4, 4);
Con::addCommand("GameConnection", "setMissionCRC", cSetMissionCRC, "conn.setMissionCRC(crc)", 3, 3);
}