Torque3D/Engine/source/T3D/gameBase/gameBase.cpp
Areloch 9b907e77ee Swaps some references from the windows SDK-specific FLT_MAX to T3D's F32_MAX
Takes the makeFullPath in findTSShapeConstructor and turn it into a string before passing it along to the Filename to make stricter compilers happy
Removed some referenced to fields that don't exist in the current build
Removed unneeded ASM language activation for the cmake files
Adjustments to material map assembling macros to better comply to stricter compilers
2020-05-13 02:10:11 -05:00

809 lines
23 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
// Copyright (C) 2015 Faust Logic, Inc.
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
#include "platform/platform.h"
#include "T3D/gameBase/gameBase.h"
#include "console/consoleTypes.h"
#include "console/engineAPI.h"
#include "console/consoleInternal.h"
#include "core/stream/bitStream.h"
#include "sim/netConnection.h"
#include "T3D/gameBase/gameConnection.h"
#include "math/mathIO.h"
#include "T3D/gameBase/moveManager.h"
#include "T3D/gameBase/gameProcess.h"
#ifdef TORQUE_DEBUG_NET_MOVES
#include "T3D/aiConnection.h"
#endif
#ifdef TORQUE_AFX_ENABLED
#include "afx/arcaneFX.h"
#endif
//----------------------------------------------------------------------------
// Ghost update relative priority values
static F32 sUpFov = 1.0;
static F32 sUpDistance = 0.4f;
static F32 sUpVelocity = 0.4f;
static F32 sUpSkips = 0.2f;
static F32 sUpInterest = 0.2f;
//----------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(GameBaseData);
ConsoleDocClass( GameBaseData,
"@brief Scriptable, demo-able datablock. Used by GameBase objects.\n\n"
"@see GameBase\n"
"@ingroup gameObjects\n"
);
IMPLEMENT_CALLBACK( GameBaseData, onAdd, void, ( GameBase* obj ), ( obj ),
"@brief Called when the object is added to the scene.\n\n"
"@param obj the GameBase object\n\n"
"@tsexample\n"
"datablock GameBaseData(MyObjectData)\n"
"{\n"
" category = \"Misc\";\n"
"};\n\n"
"function MyObjectData::onAdd( %this, %obj )\n"
"{\n"
" echo( \"Added \" @ %obj.getName() @ \" to the scene.\" );\n"
"}\n\n"
"function MyObjectData::onNewDataBlock( %this, %obj )\n"
"{\n"
" echo( \"Assign \" @ %this.getName() @ \" datablock to \" %obj.getName() );\n"
"}\n\n"
"function MyObjectData::onRemove( %this, %obj )\n"
"{\n"
" echo( \"Removed \" @ %obj.getName() @ \" to the scene.\" );\n"
"}\n\n"
"function MyObjectData::onMount( %this, %obj, %mountObj, %node )\n"
"{\n"
" echo( %obj.getName() @ \" mounted to \" @ %mountObj.getName() );\n"
"}\n\n"
"function MyObjectData::onUnmount( %this, %obj, %mountObj, %node )\n"
"{\n"
" echo( %obj.getName() @ \" unmounted from \" @ %mountObj.getName() );\n"
"}\n\n"
"@endtsexample\n" );
IMPLEMENT_CALLBACK( GameBaseData, onNewDataBlock, void, ( GameBase* obj ), ( obj ),
"@brief Called when the object has a new datablock assigned.\n\n"
"@param obj the GameBase object\n\n"
"@see onAdd for an example\n" );
IMPLEMENT_CALLBACK( GameBaseData, onRemove, void, ( GameBase* obj ), ( obj ),
"@brief Called when the object is removed from the scene.\n\n"
"@param obj the GameBase object\n\n"
"@see onAdd for an example\n" );
IMPLEMENT_CALLBACK( GameBaseData, onMount, void, ( SceneObject* obj, SceneObject* mountObj, S32 node ), ( obj, mountObj, node ),
"@brief Called when the object is mounted to another object in the scene.\n\n"
"@param obj the GameBase object being mounted\n"
"@param mountObj the object we are mounted to\n"
"@param node the mountObj node we are mounted to\n\n"
"@see onAdd for an example\n" );
IMPLEMENT_CALLBACK( GameBaseData, onUnmount, void, ( SceneObject* obj, SceneObject* mountObj, S32 node ), ( obj, mountObj, node ),
"@brief Called when the object is unmounted from another object in the scene.\n\n"
"@param obj the GameBase object being unmounted\n"
"@param mountObj the object we are unmounted from\n"
"@param node the mountObj node we are unmounted from\n\n"
"@see onAdd for an example\n" );
IMPLEMENT_CALLBACK( GameBase, setControl, void, ( bool controlled ), ( controlled ),
"@brief Called when the client controlling the object changes.\n\n"
"@param controlled true if a client now controls this object, false if no "
"client controls this object.\n" );
GameBaseData::GameBaseData()
{
mCategory = "";
mPacked = false;
}
GameBaseData::GameBaseData(const GameBaseData& other, bool temp_clone) : SimDataBlock(other, temp_clone)
{
mPacked = other.mPacked;
mCategory = other.mCategory;
//mReloadSignal = other.mReloadSignal; // DO NOT copy the mReloadSignal member.
}
void GameBaseData::inspectPostApply()
{
Parent::inspectPostApply();
// Tell interested parties ( like objects referencing this datablock )
// that we have been modified and they might want to rebuild...
mReloadSignal.trigger();
}
bool GameBaseData::onAdd()
{
if (!Parent::onAdd())
return false;
return true;
}
void GameBaseData::initPersistFields()
{
addGroup("Scripting");
addField( "category", TypeCaseString, Offset(mCategory, GameBaseData ),
"The group that this datablock will show up in under the \"Scripted\" "
"tab in the World Editor Library." );
endGroup("Scripting");
Parent::initPersistFields();
}
bool GameBaseData::preload(bool server, String &errorStr)
{
if (!Parent::preload(server, errorStr))
return false;
mPacked = false;
return true;
}
void GameBaseData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
mPacked = 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 = false;
//----------------------------------------------------------------------------
IMPLEMENT_CO_NETOBJECT_V1(GameBase);
ConsoleDocClass( GameBase,
"@brief Base class for game objects which use datablocks, networking, are "
"editable, and need to process ticks.\n\n"
"@ingroup gameObjects\n"
);
GameBase::GameBase()
: mDataBlock( NULL ),
mControllingClient( NULL ),
mCurrentWaterObject( NULL )
{
mNetFlags.set(Ghostable);
mTypeMask |= GameBaseObjectType;
mProcessTag = 0;
// From ProcessObject
mIsGameBase = true;
#ifdef TORQUE_DEBUG_NET_MOVES
mLastMoveId = 0;
mTicksSinceLastMove = 0;
mIsAiControlled = false;
#endif
mCameraFov = 90.f;
}
GameBase::~GameBase()
{
#ifdef TORQUE_AFX_ENABLED
if (mScope_registered)
arcaneFX::unregisterScopedObject(this);
#endif
}
//----------------------------------------------------------------------------
bool GameBase::onAdd()
{
if ( !Parent::onAdd() )
return false;
// Datablock must be initialized on the server.
// Client datablock are initialized by the initial update.
#ifdef TORQUE_AFX_ENABLED
if (isClientObject())
{
if (mScope_id > 0 && !mScope_registered)
arcaneFX::registerScopedObject(this);
}
else
{
if ( mDataBlock && !onNewDataBlock( mDataBlock, false ) )
return false;
}
#else
if ( isServerObject() && mDataBlock && !onNewDataBlock( mDataBlock, false ) )
return false;
#endif
setProcessTick( true );
return true;
}
void GameBase::onRemove()
{
#ifdef TORQUE_AFX_ENABLED
if (mScope_registered)
arcaneFX::unregisterScopedObject(this);
#endif
// EDITOR FEATURE: Remove us from the reload signal of our datablock.
if ( mDataBlock )
mDataBlock->mReloadSignal.remove( this, &GameBase::_onDatablockModified );
Parent::onRemove();
}
bool GameBase::onNewDataBlock( GameBaseData *dptr, bool reload )
{
// EDITOR FEATURE: Remove us from old datablock's reload signal and
// add us to the new one.
if ( !reload )
{
if ( mDataBlock )
mDataBlock->mReloadSignal.remove( this, &GameBase::_onDatablockModified );
if ( dptr )
dptr->mReloadSignal.notify( this, &GameBase::_onDatablockModified );
}
mDataBlock = dptr;
if ( !mDataBlock )
return false;
#ifdef TORQUE_AFX_ENABLED
// Don't set mask when new datablock is a temp-clone.
if (mDataBlock->isTempClone())
return true;
#endif
setMaskBits(DataBlockMask);
return true;
}
void GameBase::_onDatablockModified()
{
AssertFatal( mDataBlock, "GameBase::onDatablockModified - mDataBlock is NULL." );
onNewDataBlock( mDataBlock, true );
}
void GameBase::inspectPostApply()
{
Parent::inspectPostApply();
setMaskBits(ExtendedInfoMask);
}
//----------------------------------------------------------------------------
void GameBase::processTick(const Move * move)
{
#ifdef TORQUE_DEBUG_NET_MOVES
if (!move)
mTicksSinceLastMove++;
const char * srv = isClientObject() ? "client" : "server";
const char * who = "";
if (isClientObject())
{
if (this == (GameBase*)GameConnection::getConnectionToServer()->getControlObject())
who = " player";
else
who = " ghost";
if (mIsAiControlled)
who = " ai";
}
if (isServerObject())
{
if (dynamic_cast<AIConnection*>(getControllingClient()))
{
who = " ai";
mIsAiControlled = true;
}
else if (getControllingClient())
{
who = " player";
mIsAiControlled = false;
}
else
{
who = "";
mIsAiControlled = false;
}
}
U32 moveid = mLastMoveId+mTicksSinceLastMove;
if (move)
moveid = move->id;
if (getTypeMask() & GameBaseHiFiObjectType)
{
if (move)
Con::printf("Processing (%s%s id %i) move %i",srv,who,getId(), move->id);
else
Con::printf("Processing (%s%s id %i) move %i (%i)",srv,who,getId(),mLastMoveId+mTicksSinceLastMove,mTicksSinceLastMove);
}
if (move)
{
mLastMoveId = move->id;
mTicksSinceLastMove=0;
}
#endif
}
void GameBase::interpolateTick(F32 dt)
{
// PATHSHAPE
updateRenderChangesByParent();
// PATHSHAPE END
}
//----------------------------------------------------------------------------
F32 GameBase::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips)
{
TORQUE_UNUSED(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 * 1.5f;
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 (getTypeMask() & (PlayerObjectType || VehicleObjectType ))
wInterest = 0.75f;
else if (getTypeMask() & ProjectileObjectType)
{
// Projectiles are more interesting if they
// are heading for us.
wInterest = 0.30f;
dot = -mDot(pos,getVelocity());
if (dot > 0.0f)
wInterest += 0.20 * dot;
}
else
{
if (getTypeMask() & 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 (plus children)
//
return
wFov * sUpFov +
wDistance * sUpDistance +
wVelocity * sUpVelocity +
wSkips * sUpSkips +
wInterest * sUpInterest +
getNumChildren();
}
//----------------------------------------------------------------------------
bool GameBase::setDataBlock(GameBaseData* dptr)
{
if (isGhost() || isProperlyAdded()) {
if (mDataBlock != dptr)
return onNewDataBlock(dptr,false);
}
else
mDataBlock = dptr;
return true;
}
//--------------------------------------------------------------------------
void GameBase::scriptOnAdd()
{
// Script onAdd() must be called by the leaf class after
// everything is ready.
if (mDataBlock && !isGhost())
mDataBlock->onAdd_callback( this );
}
void GameBase::scriptOnNewDataBlock()
{
// Script onNewDataBlock() must be called by the leaf class
// after everything is loaded.
if (mDataBlock && !isGhost())
mDataBlock->onNewDataBlock_callback( this );
}
void GameBase::scriptOnRemove()
{
// Script onRemove() must be called by leaf class while
// the object state is still valid.
if (!isGhost() && mDataBlock)
mDataBlock->onRemove_callback( this );
}
//----------------------------------------------------------------------------
void GameBase::setControllingClient(GameConnection* client)
{
if (isClientObject())
{
if (mControllingClient)
setControl_callback( 0 );
if (client)
setControl_callback( 1 );
}
mControllingClient = client;
}
U32 GameBase::getPacketDataChecksum(GameConnection * connection)
{
// just write the packet data into a buffer
// then we can CRC the buffer. This should always let us
// know when there is a checksum problem.
static U8 buffer[1500] = { 0, };
BitStream stream(buffer, sizeof(buffer));
writePacketData(connection, &stream);
U32 byteCount = stream.getPosition();
U32 ret = CRC::calculateCRC(buffer, byteCount, 0xFFFFFFFF);
dMemset(buffer, 0, byteCount);
return ret;
}
void GameBase::writePacketData(GameConnection*, BitStream*)
{
}
void GameBase::readPacketData(GameConnection*, BitStream*)
{
}
U32 GameBase::packUpdate( NetConnection *connection, U32 mask, BitStream *stream )
{
U32 retMask = Parent::packUpdate( connection, mask, stream );
if ( stream->writeFlag( mask & ScaleMask ) )
{
// Only write one bit if the scale is one.
if ( stream->writeFlag( mObjScale != Point3F::One ) )
mathWrite( *stream, mObjScale );
}
if ( stream->writeFlag( ( mask & DataBlockMask ) && mDataBlock != NULL ) )
{
stream->writeRangedU32( mDataBlock->getId(),
DataBlockObjectIdFirst,
DataBlockObjectIdLast );
if ( stream->writeFlag( mNetFlags.test( NetOrdered ) ) )
stream->writeInt( mOrderGUID, 16 );
}
#ifdef TORQUE_DEBUG_NET_MOVES
stream->write(mLastMoveId);
stream->writeFlag(mIsAiControlled);
#endif
#ifdef TORQUE_AFX_ENABLED
if (stream->writeFlag(mask & ScopeIdMask))
{
if (stream->writeFlag(mScope_refs > 0))
stream->writeInt(mScope_id, SCOPE_ID_BITS);
}
#endif
return retMask;
}
void GameBase::unpackUpdate(NetConnection *con, BitStream *stream)
{
Parent::unpackUpdate( con, stream );
// ScaleMask
if ( stream->readFlag() )
{
if ( stream->readFlag() )
{
VectorF scale;
mathRead( *stream, &scale );
setScale( scale );
}
else
setScale( Point3F::One );
}
// DataBlockMask
if ( stream->readFlag() )
{
GameBaseData *dptr = 0;
SimObjectId id = stream->readRangedU32( DataBlockObjectIdFirst,
DataBlockObjectIdLast );
if ( stream->readFlag() )
mOrderGUID = stream->readInt( 16 );
if ( !Sim::findObject( id, dptr ) || !setDataBlock( dptr ) )
con->setLastError( "Invalid packet GameBase::unpackUpdate()" );
}
#ifdef TORQUE_DEBUG_NET_MOVES
stream->read(&mLastMoveId);
mTicksSinceLastMove = 0;
mIsAiControlled = stream->readFlag();
#endif
#ifdef TORQUE_AFX_ENABLED
if (stream->readFlag())
{
mScope_id = (stream->readFlag()) ? (U16) stream->readInt(SCOPE_ID_BITS) : 0;
mScope_refs = 0;
}
#endif
}
void GameBase::onMount( SceneObject *obj, S32 node )
{
deleteNotify( obj );
// Are we mounting to a GameBase object?
GameBase *gbaseObj = dynamic_cast<GameBase*>( obj );
if ( gbaseObj && gbaseObj->getControlObject() != this && gbaseObj->getControllingObject() != this)
processAfter( gbaseObj );
if (!isGhost()) {
setMaskBits(MountedMask);
mDataBlock->onMount_callback( this, obj, node );
}
}
void GameBase::onUnmount( SceneObject *obj, S32 node )
{
clearNotify(obj);
GameBase *gbaseObj = dynamic_cast<GameBase*>( obj );
if ( gbaseObj && gbaseObj->getControlObject() != this )
clearProcessAfter();
if (!isGhost()) {
setMaskBits(MountedMask);
mDataBlock->onUnmount_callback( this, obj, node );
}
}
bool GameBase::setDataBlockProperty( void *obj, const char *index, const char *db)
{
if( db == NULL || !db[ 0 ] )
{
Con::errorf( "GameBase::setDataBlockProperty - Can't unset datablock on GameBase objects" );
return false;
}
GameBase* object = static_cast< GameBase* >( obj );
GameBaseData* data;
if( Sim::findObject( db, data ) )
return object->setDataBlock( data );
Con::errorf( "GameBase::setDatablockProperty - Could not find data block \"%s\"", db );
return false;
}
MoveList* GameBase::getMoveList()
{
return mControllingClient ? mControllingClient->mMoveList : NULL;
}
//----------------------------------------------------------------------------
DefineEngineMethod( GameBase, getDataBlock, S32, (),,
"@brief Get the datablock used by this object.\n\n"
"@return the datablock this GameBase is using."
"@see setDataBlock()\n")
{
return object->getDataBlock()? object->getDataBlock()->getId(): 0;
}
//----------------------------------------------------------------------------
DefineEngineMethod( GameBase, setDataBlock, bool, ( GameBaseData* data ),,
"@brief Assign this GameBase to use the specified datablock.\n\n"
"@param data new datablock to use\n"
"@return true if successful, false if failed."
"@see getDataBlock()\n")
{
return ( data && object->setDataBlock(data) );
}
//----------------------------------------------------------------------------
void GameBase::initPersistFields()
{
addGroup( "Game" );
addProtectedField( "dataBlock", TYPEID< GameBaseData >(), Offset(mDataBlock, GameBase),
&setDataBlockProperty, &defaultProtectedGetFn,
"Script datablock used for game objects." );
endGroup( "Game" );
Parent::initPersistFields();
}
void GameBase::consoleInit()
{
#ifdef TORQUE_DEBUG
Con::addVariable( "GameBase::boundingBox", TypeBool, &gShowBoundingBox,
"@brief Toggles on the rendering of the bounding boxes for certain types of objects in scene.\n\n"
"@ingroup GameBase" );
#endif
}
DefineEngineMethod( GameBase, applyImpulse, bool, ( Point3F pos, VectorF vel ),,
"@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
"@param pos impulse world position\n"
"@param vel impulse velocity (impulse force F = m * v)\n"
"@return Always true\n"
"@note Not all objects that derrive from GameBase have this defined.\n")
{
object->applyImpulse(pos,vel);
return true;
}
DefineEngineMethod( GameBase, applyRadialImpulse, void, ( Point3F origin, F32 radius, F32 magnitude ),,
"@brief Applies a radial impulse to the object using the given origin and force.\n\n"
"@param origin World point of origin of the radial impulse.\n"
"@param radius The radius of the impulse area.\n"
"@param magnitude The strength of the impulse.\n"
"@note Not all objects that derrive from GameBase have this defined.\n")
{
object->applyRadialImpulse( origin, radius, magnitude );
}
// PATHSHAPE
// Console Methods for attach children. can't put them in sceneobject because //
// we want the processafter functions////////////////////////////////////////////
DefineEngineMethod(GameBase, attachChild, bool, (GameBase* _subObject), (nullAsType<GameBase*>()), "(SceneObject subObject)"
"attach an object to this one, preserving its present transform.")
{
if (_subObject != nullptr)
{
if (_subObject->getParent() != object){
Con::errorf("Object is (%d)", _subObject->getId());
_subObject->clearProcessAfter();
_subObject->processAfter(object);
return object->attachChild(_subObject);
}
else
return false;
}
else
{
Con::errorf("Couldn't addObject()!");
return false;
}
}
DefineEngineMethod(GameBase, detachChild, bool, (GameBase* _subObject), (nullAsType<GameBase*>()), "(SceneObject subObject)"
"attach an object to this one, preserving its present transform.")
{
if (_subObject != nullptr)
{
_subObject->clearProcessAfter();
return _subObject->attachToParent(NULL);
}
else
{
return false;
}
}//end
// PATHSHAPE END