//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "game/gameBase.h" #include "console/consoleTypes.h" #include "console/consoleInternal.h" #include "core/bitStream.h" #include "sim/netConnection.h" #include "game/gameConnection.h" #include "math/mathIO.h" #include "dgl/dgl.h" //---------------------------------------------------------------------------- // Ghost update relative priority values static F32 sUpFov = 1.0; static F32 sUpDistance = 0.4; static F32 sUpVelocity = 0.4; static F32 sUpSkips = 0.2; static F32 sUpOwnership = 0.2; static F32 sUpInterest = 0.2; //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- IMPLEMENT_CO_DATABLOCK_V1(GameBaseData); GameBaseData::GameBaseData() { category = ""; className = ""; packed = false; } bool GameBaseData::onAdd() { if (!Parent::onAdd()) return false; // Link our object name to the datablock class name and // then onto our C++ class name. const char* name = getName(); if (name && name[0] && getClassRep()) { bool linkSuccess = false; Namespace *parent = getClassRep()->getNameSpace(); if (className && className[0] && dStricmp(className, parent->mName)) { linkSuccess = Con::linkNamespaces(parent->mName,className); if(linkSuccess) linkSuccess = Con::linkNamespaces(className,name); } else linkSuccess = Con::linkNamespaces(parent->mName,name); if(linkSuccess) mNameSpace = Con::lookupNamespace(name); } // If no className was specified, set it to our C++ class name if (!className || !className[0]) className = getClassRep()->getClassName(); return true; } void GameBaseData::initPersistFields() { Parent::initPersistFields(); addField("category", TypeCaseString, Offset(category, GameBaseData)); addField("className", TypeString, Offset(className, GameBaseData)); } bool GameBaseData::preload(bool server, char errorBuffer[256]) { if (!Parent::preload(server, errorBuffer)) return false; packed = false; return true; } void GameBaseData::unpackData(BitStream* stream) { Parent::unpackData(stream); packed = true; } //---------------------------------------------------------------------------- bool UNPACK_DB_ID(BitStream * stream, U32 & id) { if (stream->readFlag()) { id = stream->readRangedU32(DataBlockObjectIdFirst,DataBlockObjectIdLast); return true; } return false; } bool PACK_DB_ID(BitStream * stream, U32 id) { if (stream->writeFlag(id)) { stream->writeRangedU32(id,DataBlockObjectIdFirst,DataBlockObjectIdLast); return true; } return false; } bool PRELOAD_DB(U32 & id, SimDataBlock ** data, bool server, const char * clientMissing, const char * serverMissing) { if (server) { if (*data) id = (*data)->getId(); else if (server && serverMissing) { Con::errorf(ConsoleLogEntry::General,serverMissing); return false; } } else { if (id && !Sim::findObject(id,*data) && clientMissing) { Con::errorf(ConsoleLogEntry::General,clientMissing); return false; } } return true; } //---------------------------------------------------------------------------- bool GameBase::gShowBoundingBox; //---------------------------------------------------------------------------- IMPLEMENT_CO_NETOBJECT_V1(GameBase); GameBase::GameBase() { mNetFlags.set(Ghostable); mTypeMask |= GameBaseObjectType; mProcessLink.next = mProcessLink.prev = this; mAfterObject = 0; mProcessTag = 0; mLastDelta = 0; mDataBlock = 0; mProcessTick = true; mNameTag = ""; mControllingClient = 0; } GameBase::~GameBase() { plUnlink(); } //---------------------------------------------------------------------------- bool GameBase::onAdd() { if (!Parent::onAdd() || !mDataBlock) return false; if (isClientObject()) { // Client datablock are initialized by the initial update gClientProcessList.addObject(this); } else { // Datablock must be initialized on the server if (!onNewDataBlock(mDataBlock)) return false; gServerProcessList.addObject(this); } return true; } void GameBase::onRemove() { plUnlink(); Parent::onRemove(); } bool GameBase::onNewDataBlock(GameBaseData* dptr) { mDataBlock = dptr; if (!mDataBlock) return false; setMaskBits(DataBlockMask); return true; } void GameBase::inspectPostApply() { Parent::inspectPostApply(); setMaskBits(ExtendedInfoMask); } //---------------------------------------------------------------------------- void GameBase::processTick(const Move*) { mLastDelta = 0; } void GameBase::interpolateTick(F32 delta) { mLastDelta = delta; } void GameBase::advanceTime(F32) { } //---------------------------------------------------------------------------- F32 GameBase::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips) { updateMask; // Calculate a priority used to decide if this object // will be updated on the client. All the weights // are calculated 0 -> 1 Then weighted together at the // end to produce a priority. Point3F pos; getWorldBox().getCenter(&pos); pos -= camInfo->pos; F32 dist = pos.len(); if (dist == 0.0f) dist = 0.001f; pos *= 1.0f / dist; // Weight based on linear distance, the basic stuff. F32 wDistance = (dist < camInfo->visibleDistance)? 1.0f - (dist / camInfo->visibleDistance): 0.0f; // Weight by field of view, objects directly in front // will be weighted 1, objects behind will be 0 F32 dot = mDot(pos,camInfo->orientation); bool inFov = dot > camInfo->cosFov; F32 wFov = inFov? 1.0f: 0; // Weight by linear velocity parallel to the viewing plane // (if it's the field of view, 0 if it's not). F32 wVelocity = 0.0f; if (inFov) { Point3F vec; mCross(camInfo->orientation,getVelocity(),&vec); wVelocity = (vec.len() * camInfo->fov) / (camInfo->fov * camInfo->visibleDistance); if (wVelocity > 1.0f) wVelocity = 1.0f; } // Weight by interest. F32 wInterest; if (getType() & PlayerObjectType) wInterest = 0.75f; else if (getType() & ProjectileObjectType) { // Projectiles are more interesting if they // are heading for us. wInterest = 0.30f; F32 dot = -mDot(pos,getVelocity()); if (dot > 0.0f) wInterest += 0.20 * dot; } else { if (getType() & ItemObjectType) wInterest = 0.25f; else // Everything else is less interesting. wInterest = 0.0f; } // Weight by updateSkips F32 wSkips = updateSkips * 0.5; // Calculate final priority, should total to about 1.0f // return wFov * sUpFov + wDistance * sUpDistance + wVelocity * sUpVelocity + wSkips * sUpSkips + wInterest * sUpInterest; } void GameBase::drawBoundingBox(bool useRenderTransform) { const MatrixF & mat = useRenderTransform ? getRenderTransform() : getTransform(); Box3F wbox = useRenderTransform ? getRenderWorldBox() : mWorldBox; glDisable(GL_DEPTH_TEST); Point3F box; glPushMatrix(); dglMultMatrix(&mat); box = (mObjBox.min + mObjBox.max) * 0.5; glTranslatef(box.x,box.y,box.z); box = (mObjBox.max - mObjBox.min) * 0.5; glScalef(box.x,box.y,box.z); glColor3f(1, 0, 1); dglWireCube(Point3F(1,1,1),Point3F(0,0,0)); glPopMatrix(); glPushMatrix(); box = (wbox.min + wbox.max) * 0.5; glTranslatef(box.x,box.y,box.z); box = (wbox.max - wbox.min) * 0.5; glScalef(box.x,box.y,box.z); glColor3f(0, 1, 1); dglWireCube(Point3F(1,1,1),Point3F(0,0,0)); glPopMatrix(); glEnable(GL_DEPTH_TEST); } //---------------------------------------------------------------------------- bool GameBase::setDataBlock(GameBaseData* dptr) { if (isGhost() || isProperlyAdded()) { if (mDataBlock != dptr) return onNewDataBlock(dptr); } else mDataBlock = dptr; return true; } //-------------------------------------------------------------------------- void GameBase::scriptOnAdd() { // Script onAdd() must be called by the leaf class after // everything is ready. if (!isGhost()) Con::executef(mDataBlock,2,"onAdd",scriptThis()); } void GameBase::scriptOnNewDataBlock() { // Script onNewDataBlock() must be called by the leaf class // after everything is loaded. if (!isGhost()) Con::executef(mDataBlock,2,"onNewDataBlock",scriptThis()); } void GameBase::scriptOnRemove() { // Script onRemove() must be called by leaf class while // the object state is still valid. if (!isGhost() && mDataBlock) Con::executef(mDataBlock,2,"onRemove",scriptThis()); } //-------------------------------------------------------------------------- void GameBase::plUnlink() { mProcessLink.next->mProcessLink.prev = mProcessLink.prev; mProcessLink.prev->mProcessLink.next = mProcessLink.next; mProcessLink.next = mProcessLink.prev = this; } void GameBase::plLinkAfter(GameBase* obj) { // Link this after obj mProcessLink.next = obj->mProcessLink.next; mProcessLink.prev = obj; obj->mProcessLink.next = this; mProcessLink.next->mProcessLink.prev = this; } void GameBase::plLinkBefore(GameBase* obj) { // Link this before obj mProcessLink.next = obj; mProcessLink.prev = obj->mProcessLink.prev; obj->mProcessLink.prev = this; mProcessLink.prev->mProcessLink.next = this; } void GameBase::plJoin(GameBase* head) { GameBase *tail1 = head->mProcessLink.prev; GameBase *tail2 = mProcessLink.prev; tail1->mProcessLink.next = this; mProcessLink.prev = tail1; tail2->mProcessLink.next = head; head->mProcessLink.prev = tail2; } //---------------------------------------------------------------------------- void GameBase::processAfter(GameBase* obj) { mAfterObject = obj; if ((const GameBase*)obj->mAfterObject == this) obj->mAfterObject = 0; if (isGhost()) gClientProcessList.markDirty(); else gServerProcessList.markDirty(); } void GameBase::clearProcessAfter() { mAfterObject = 0; } //---------------------------------------------------------------------------- void GameBase::setControllingClient(GameConnection* client) { if (isClientObject()) { if (mControllingClient) Con::executef(this, 3, "setControl","0"); if (client) Con::executef(this, 3, "setControl","1"); } mControllingClient = client; setMaskBits(ControlMask); } U32 GameBase::getPacketDataChecksum(GameConnection*) { return 0; } void GameBase::writePacketData(GameConnection*, BitStream*) { } void GameBase::readPacketData(GameConnection*, BitStream*) { } U32 GameBase::packUpdate(NetConnection *, U32 mask, BitStream *stream) { // Check the mask for the ScaleMask; if it's true, pass that in. if (stream->writeFlag( mask & ScaleMask ) ) { mathWrite( *stream, Parent::getScale() ); } if (stream->writeFlag((mask & DataBlockMask) && mDataBlock != NULL)) { stream->writeRangedU32(mDataBlock->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); } // cafTODO: ControlMask return 0; } void GameBase::unpackUpdate(NetConnection *con, BitStream *stream) { if (stream->readFlag()) { VectorF scale; mathRead( *stream, &scale ); setScale( scale ); } if (stream->readFlag()) { GameBaseData* dptr = 0; SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); if (!Sim::findObject(id,dptr) || !setDataBlock(dptr)) con->setLastError("Invalid packet GameBase::unpackUpdate()"); } } //---------------------------------------------------------------------------- ConsoleMethod( GameBase, getDataBlock, S32, 2, 2, "()" "Return the datablock this GameBase is using.") { return object->getDataBlock()? object->getDataBlock()->getId(): 0; } //---------------------------------------------------------------------------- ConsoleMethod(GameBase, setDataBlock, bool, 3, 3, "(DataBlock db)" "Assign this GameBase to use the specified datablock.") { GameBaseData* data; if (Sim::findObject(argv[2],data)) { return object->setDataBlock(data); } Con::errorf("Could not find data block \"%s\"",argv[2]); return false; } //---------------------------------------------------------------------------- IMPLEMENT_CONSOLETYPE(GameBaseData) IMPLEMENT_GETDATATYPE(GameBaseData) IMPLEMENT_SETDATATYPE(GameBaseData) void GameBase::initPersistFields() { Parent::initPersistFields(); addGroup("Misc"); addField("nameTag", TypeCaseString, Offset(mNameTag, GameBase)); addField("dataBlock", TypeGameBaseDataPtr, Offset(mDataBlock, GameBase)); endGroup("Misc"); } void GameBase::consoleInit() { #ifdef TORQUE_DEBUG Con::addVariable("GameBase::boundingBox", TypeBool, &gShowBoundingBox); #endif }