Torque3D/Engine/source/T3D/entity.cpp

1837 lines
51 KiB
C++
Raw Normal View History

2016-05-14 04:58:57 +00:00
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "T3D/entity.h"
2016-05-14 04:58:57 +00:00
#include "core/stream/bitStream.h"
#include "console/consoleTypes.h"
#include "console/consoleObject.h"
#include "sim/netConnection.h"
#include "scene/sceneRenderState.h"
#include "scene/sceneManager.h"
#include "T3D/gameBase/gameProcess.h"
#include "console/engineAPI.h"
#include "T3D/gameBase/gameConnection.h"
#include "math/mathIO.h"
#include "math/mTransform.h"
#include "T3D/components/coreInterfaces.h"
#include "T3D/components/render/renderComponentInterface.h"
#include "T3D/components/collision/collisionInterfaces.h"
2016-05-14 04:58:57 +00:00
#include "gui/controls/guiTreeViewCtrl.h"
#include "assets/assetManager.h"
#include "assets/assetQuery.h"
#include "T3D/assets/ComponentAsset.h"
2016-05-14 04:58:57 +00:00
#include "console/consoleInternal.h"
#include "T3D/gameBase/std/stdMoveList.h"
#include "T3D/prefab.h"
//
#include "gfx/sim/debugDraw.h"
//
#include "T3D/sfx/sfx3DWorld.h"
2016-05-14 04:58:57 +00:00
extern bool gEditingMission;
// Client prediction
static F32 sMinWarpTicks = 0.5f; // Fraction of tick at which instant warp occurs
static S32 sMaxWarpTicks = 3; // Max warp duration in ticks
static S32 sMaxPredictionTicks = 30; // Number of ticks to predict
IMPLEMENT_CO_NETOBJECT_V1(Entity);
ConsoleDocClass(Entity,
"@brief Base Entity class.\n\n"
"Entity is typically made up of a shape and up to two particle emitters. In most cases Entity objects are "
"not created directly. They are usually produced automatically by other means, such as through the Explosion "
"class. When an explosion goes off, its ExplosionData datablock determines what Entity to emit.\n"
"@tsexample\n"
"datablock ExplosionData(GrenadeLauncherExplosion)\n"
"{\n"
" // Assiging Entity data\n"
" Entity = GrenadeEntity;\n\n"
" // Adjust how Entity is ejected\n"
" EntityThetaMin = 10;\n"
" EntityThetaMax = 60;\n"
" EntityNum = 4;\n"
" EntityNumVariance = 2;\n"
" EntityVelocity = 25;\n"
" EntityVelocityVariance = 5;\n\n"
" // Note: other ExplosionData properties are not listed for this example\n"
"};\n"
"@endtsexample\n\n"
"@note Entity are client side only objects.\n"
"@see EntityData\n"
"@see ExplosionData\n"
"@see Explosion\n"
"@ingroup FX\n"
);
Entity::Entity()
{
//mTypeMask |= DynamicShapeObjectType | StaticObjectType | ;
mTypeMask |= EntityObjectType;
mNetFlags.set(Ghostable | ScopeAlways);
mPos = Point3F(0, 0, 0);
mRot = Point3F(0, 0, 0);
mDelta.pos = mDelta.posVec = Point3F::Zero;
mDelta.rot[0].identity();
mDelta.rot[1].identity();
mDelta.warpOffset.set(0.0f, 0.0f, 0.0f);
mDelta.warpTicks = mDelta.warpCount = 0;
mDelta.dt = 1.0f;
mDelta.move = NullMove;
mComponents.clear();
mStartComponentUpdate = false;
mInitialized = false;
mGameObjectAssetId = StringTable->insert("");
2016-05-14 04:58:57 +00:00
}
Entity::~Entity()
{
}
void Entity::initPersistFields()
{
Parent::initPersistFields();
removeField("DataBlock");
addGroup("Transform");
removeField("Position");
addProtectedField("Position", TypePoint3F, Offset(mPos, Entity), &_setPosition, &_getPosition, "Object world orientation.");
removeField("Rotation");
addProtectedField("Rotation", TypeRotationF, Offset(mRot, Entity), &_setRotation, &_getRotation, "Object world orientation.");
//These are basically renamed mountPos/Rot. pretty much there for conveinence
addField("LocalPosition", TypeMatrixPosition, Offset(mMount.xfm, Entity), "Position we are mounted at ( object space of our mount object ).");
addField("LocalRotation", TypeMatrixRotation, Offset(mMount.xfm, Entity), "Rotation we are mounted at ( object space of our mount object ).");
endGroup("Transform");
addGroup("GameObject");
addProtectedField("gameObjectName", TypeGameObjectAssetPtr, Offset(mGameObjectAsset, Entity), &_setGameObject, &defaultProtectedGetFn,
"The asset Id used for the game object this entity is based on.");
endGroup("GameObject");
2016-05-14 04:58:57 +00:00
}
//
bool Entity::_setPosition(void *object, const char *index, const char *data)
{
Entity* so = static_cast<Entity*>(object);
if (so)
{
Point3F pos;
if (!dStrcmp(data, ""))
pos = Point3F(0, 0, 0);
else
Con::setData(TypePoint3F, &pos, 0, 1, &data);
so->setTransform(pos, so->mRot);
}
return false;
}
const char * Entity::_getPosition(void* obj, const char* data)
{
Entity* so = static_cast<Entity*>(obj);
if (so)
{
Point3F pos = so->getPosition();
static const U32 bufSize = 256;
char* returnBuffer = Con::getReturnBuffer(bufSize);
dSprintf(returnBuffer, bufSize, "%g %g %g", pos.x, pos.y, pos.z);
return returnBuffer;
}
return "0 0 0";
}
bool Entity::_setRotation(void *object, const char *index, const char *data)
{
Entity* so = static_cast<Entity*>(object);
if (so)
{
RotationF rot;
Con::setData(TypeRotationF, &rot, 0, 1, &data);
//so->mRot = rot;
//MatrixF mat = rot.asMatrixF();
//mat.setPosition(so->getPosition());
//so->setTransform(mat);
so->setTransform(so->getPosition(), rot);
}
return false;
}
const char * Entity::_getRotation(void* obj, const char* data)
{
Entity* so = static_cast<Entity*>(obj);
if (so)
{
EulerF eulRot = so->mRot.asEulerF();
static const U32 bufSize = 256;
char* returnBuffer = Con::getReturnBuffer(bufSize);
dSprintf(returnBuffer, bufSize, "%g %g %g", mRadToDeg(eulRot.x), mRadToDeg(eulRot.y), mRadToDeg(eulRot.z));
return returnBuffer;
}
return "0 0 0";
}
bool Entity::onAdd()
{
if (!Parent::onAdd())
return false;
mObjBox = Box3F(Point3F(-0.5, -0.5, -0.5), Point3F(0.5, 0.5, 0.5));
2016-05-14 04:58:57 +00:00
resetWorldBox();
setObjectBox(mObjBox);
addToScene();
//Make sure we get positioned
setMaskBits(TransformMask);
setMaskBits(NamespaceMask);
2016-05-14 04:58:57 +00:00
return true;
}
void Entity::onRemove()
{
clearComponents(true);
2016-05-14 04:58:57 +00:00
removeFromScene();
onDataSet.removeAll();
Parent::onRemove();
}
void Entity::onPostAdd()
{
mInitialized = true;
//everything's done and added. go ahead and initialize the components
for (U32 i = 0; i < mComponents.size(); i++)
{
mComponents[i]->onComponentAdd();
}
if (isMethod("onAdd"))
Con::executef(this, "onAdd");
}
bool Entity::_setGameObject(void *object, const char *index, const char *data)
{
Entity *e = static_cast<Entity*>(object);
// Sanity!
AssertFatal(data != NULL, "Cannot use a NULL asset Id.");
return true; //rbI->setMeshAsset(data);
}
2016-05-14 04:58:57 +00:00
void Entity::setDataField(StringTableEntry slotName, const char *array, const char *value)
{
Parent::setDataField(slotName, array, value);
onDataSet.trigger(this, slotName, value);
}
void Entity::onStaticModified(const char* slotName, const char* newValue)
{
Parent::onStaticModified(slotName, newValue);
onDataSet.trigger(this, slotName, newValue);
}
//Updating
void Entity::processTick(const Move* move)
{
if (!isHidden())
{
if (mDelta.warpCount < mDelta.warpTicks)
{
mDelta.warpCount++;
// Set new pos.
mObjToWorld.getColumn(3, &mDelta.pos);
mDelta.pos += mDelta.warpOffset;
mDelta.rot[0] = mDelta.rot[1];
mDelta.rot[1].interpolate(mDelta.warpRot[0], mDelta.warpRot[1], F32(mDelta.warpCount) / mDelta.warpTicks);
setTransform(mDelta.pos, mDelta.rot[1]);
// Pos backstepping
mDelta.posVec.x = -mDelta.warpOffset.x;
mDelta.posVec.y = -mDelta.warpOffset.y;
mDelta.posVec.z = -mDelta.warpOffset.z;
}
else
{
if (isMounted())
{
MatrixF mat;
mMount.object->getMountTransform(mMount.node, mMount.xfm, &mat);
Parent::setTransform(mat);
Parent::setRenderTransform(mat);
}
else
{
if (!move)
{
if (isGhost())
{
// If we haven't run out of prediction time,
// predict using the last known move.
if (mPredictionCount-- <= 0)
return;
move = &mDelta.move;
}
else
{
move = &NullMove;
}
}
}
}
Move prevMove = lastMove;
if (move != NULL)
lastMove = *move;
else
lastMove = NullMove;
if (move && isServerObject())
{
if ((move->y != 0 || prevMove.y != 0)
|| (move->x != 0 || prevMove.x != 0)
|| (move->z != 0 || prevMove.x != 0))
{
if (isMethod("moveVectorEvent"))
Con::executef(this, "moveVectorEvent", move->x, move->y, move->z);
}
if (move->yaw != 0)
{
if (isMethod("moveYawEvent"))
Con::executef(this, "moveYawEvent", move->yaw);
}
if (move->pitch != 0)
{
if (isMethod("movePitchEvent"))
Con::executef(this, "movePitchEvent", move->pitch);
}
if (move->roll != 0)
{
if (isMethod("moveRollEvent"))
Con::executef(this, "moveRollEvent", move->roll);
}
for (U32 i = 0; i < MaxTriggerKeys; i++)
{
if (move->trigger[i] != prevMove.trigger[i])
{
if (isMethod("moveTriggerEvent"))
Con::executef(this, "moveTriggerEvent", i, move->trigger[i]);
}
}
}
// Save current rigid state interpolation
mDelta.posVec = getPosition();
mDelta.rot[0] = mRot.asQuatF();
//Handle any script updates, which can include physics stuff
if (isServerObject() && isMethod("processTick"))
2016-05-14 04:58:57 +00:00
Con::executef(this, "processTick");
// Wrap up interpolation info
mDelta.pos = getPosition();
mDelta.posVec -= getPosition();
mDelta.rot[1] = mRot.asQuatF();
setTransform(getPosition(), mRot);
2016-05-14 04:58:57 +00:00
}
}
void Entity::advanceTime(F32 dt)
{
}
void Entity::interpolateTick(F32 dt)
{
if (dt == 0.0f)
{
setRenderTransform(mDelta.pos, mDelta.rot[1]);
}
else
{
QuatF rot;
rot.interpolate(mDelta.rot[1], mDelta.rot[0], dt);
Point3F pos = mDelta.pos + mDelta.posVec * dt;
setRenderTransform(pos, rot);
}
mDelta.dt = dt;
}
//Render
void Entity::prepRenderImage(SceneRenderState *state)
{
}
//Networking
U32 Entity::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
{
U32 retMask = Parent::packUpdate(con, mask, stream);
if (stream->writeFlag(mask & TransformMask))
{
stream->writeCompressedPoint(mPos);
mathWrite(*stream, getRotation());
2016-05-14 04:58:57 +00:00
mDelta.move.pack(stream);
stream->writeFlag(!(mask & NoWarpMask));
}
if (stream->writeFlag(mask & BoundsMask))
{
mathWrite(*stream, mObjBox);
}
//pass our behaviors around
if (mask & ComponentsMask || mask & InitialUpdateMask)
{
stream->writeFlag(true);
//now, we run through a list of our to-be-sent behaviors and begin sending them
//if any fail, we keep our list and re-queue the mask
S32 componentCount = mToLoadComponents.size();
//build our 'ready' list
//This requires both the instance and the instances' template to be prepped(if the template hasn't been ghosted,
//then we know we shouldn't be passing the instance's ghosts around yet)
U32 ghostedCompCnt = 0;
for (U32 i = 0; i < componentCount; i++)
{
if (con->getGhostIndex(mToLoadComponents[i]) != -1)
ghostedCompCnt++;
}
if (ghostedCompCnt != 0)
{
stream->writeFlag(true);
stream->writeFlag(mStartComponentUpdate);
//if not all the behaviors have been ghosted, we'll need another pass
if (ghostedCompCnt != componentCount)
retMask |= ComponentsMask;
//write the currently ghosted behavior count
stream->writeInt(ghostedCompCnt, 16);
for (U32 i = 0; i < mToLoadComponents.size(); i++)
{
//now fetch them and pass the ghost
S32 ghostIndex = con->getGhostIndex(mToLoadComponents[i]);
if (ghostIndex != -1)
{
stream->writeInt(ghostIndex, NetConnection::GhostIdBitSize);
mToLoadComponents.erase(i);
i--;
mStartComponentUpdate = false;
}
}
}
else if (componentCount)
{
//on the odd chance we have behaviors to ghost, but NONE of them have been yet, just set the flag now
stream->writeFlag(false);
retMask |= ComponentsMask;
}
else
stream->writeFlag(false);
}
else
stream->writeFlag(false);
/*if (stream->writeFlag(mask & NamespaceMask))
{
const char* name = getName();
if (stream->writeFlag(name && name[0]))
stream->writeString(String(name));
if (stream->writeFlag(mSuperClassName && mSuperClassName[0]))
stream->writeString(String(mSuperClassName));
if (stream->writeFlag(mClassName && mClassName[0]))
stream->writeString(String(mClassName));
}*/
2016-05-14 04:58:57 +00:00
return retMask;
}
void Entity::unpackUpdate(NetConnection *con, BitStream *stream)
{
Parent::unpackUpdate(con, stream);
if (stream->readFlag())
{
Point3F pos;
stream->readCompressedPoint(&pos);
2016-05-14 04:58:57 +00:00
RotationF rot;
mathRead(*stream, &rot);
2016-05-14 04:58:57 +00:00
mDelta.move.unpack(stream);
if (stream->readFlag() && isProperlyAdded())
{
// Determine number of ticks to warp based on the average
// of the client and server velocities.
Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt;
mDelta.warpOffset = pos - cp;
// Calc the distance covered in one tick as the average of
// the old speed and the new speed from the server.
VectorF vel = pos - mDelta.pos;
F32 dt, as = vel.len() * 0.5 * TickSec;
// Cal how many ticks it will take to cover the warp offset.
// If it's less than what's left in the current tick, we'll just
// warp in the remaining time.
if (!as || (dt = mDelta.warpOffset.len() / as) > sMaxWarpTicks)
dt = mDelta.dt + sMaxWarpTicks;
else
dt = (dt <= mDelta.dt) ? mDelta.dt : mCeil(dt - mDelta.dt) + mDelta.dt;
// Adjust current frame interpolation
if (mDelta.dt)
{
mDelta.pos = cp + (mDelta.warpOffset * (mDelta.dt / dt));
mDelta.posVec = (cp - mDelta.pos) / mDelta.dt;
QuatF cr;
cr.interpolate(mDelta.rot[1], mDelta.rot[0], mDelta.dt);
mDelta.rot[1].interpolate(cr, rot.asQuatF(), mDelta.dt / dt);
2016-05-14 04:58:57 +00:00
mDelta.rot[0].extrapolate(mDelta.rot[1], cr, mDelta.dt);
}
// Calculated multi-tick warp
mDelta.warpCount = 0;
mDelta.warpTicks = (S32)(mFloor(dt));
if (mDelta.warpTicks)
{
mDelta.warpOffset = pos - mDelta.pos;
mDelta.warpOffset /= mDelta.warpTicks;
mDelta.warpRot[0] = mDelta.rot[1];
mDelta.warpRot[1] = rot.asQuatF();
}
}
else
{
// Set the entity to the server position
mDelta.dt = 0;
mDelta.pos = pos;
mDelta.posVec.set(0, 0, 0);
mDelta.rot[1] = mDelta.rot[0] = rot.asQuatF();
mDelta.warpCount = mDelta.warpTicks = 0;
setTransform(pos, rot);
}
}
if (stream->readFlag())
{
mathRead(*stream, &mObjBox);
resetWorldBox();
}
if (stream->readFlag())
{
//are we passing any behaviors currently?
if (stream->readFlag())
{
//if we've just started the update, clear our behaviors
if (stream->readFlag())
clearComponents(false);
S32 componentCount = stream->readInt(16);
for (U32 i = 0; i < componentCount; i++)
{
S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize);
addComponent(dynamic_cast<Component*>(con->resolveGhost(gIndex)));
}
}
}
/*if (stream->readFlag())
{
if (stream->readFlag())
{
char name[256];
stream->readString(name);
assignName(name);
}
if (stream->readFlag())
{
char superClassname[256];
stream->readString(superClassname);
mSuperClassName = superClassname;
}
if (stream->readFlag())
{
char classname[256];
stream->readString(classname);
mClassName = classname;
}
linkNamespaces();
}*/
2016-05-14 04:58:57 +00:00
}
//Manipulation
void Entity::setTransform(const MatrixF &mat)
{
MatrixF oldTransform = getTransform();
2016-05-14 04:58:57 +00:00
if (isMounted())
{
// Use transform from mounted object
Point3F newPos = mat.getPosition();
Point3F parentPos = mMount.object->getTransform().getPosition();
Point3F newOffset = newPos - parentPos;
if (!newOffset.isZero())
{
mPos = newOffset;
}
Point3F matEul = mat.toEuler();
if (matEul != Point3F(0, 0, 0))
{
Point3F mountEul = mMount.object->getTransform().toEuler();
Point3F diff = matEul - mountEul;
mRot = diff;
}
else
{
mRot = Point3F(0, 0, 0);
}
RotationF addRot = mRot + RotationF(mMount.object->getTransform());
MatrixF transf = addRot.asMatrixF();
transf.setPosition(mPos + mMount.object->getPosition());
Parent::setTransform(transf);
if (transf != oldTransform)
setMaskBits(TransformMask);
2016-05-14 04:58:57 +00:00
}
else
{
//Are we part of a prefab?
/*Prefab* p = Prefab::getPrefabByChild(this);
if (p)
{
//just let our prefab know we moved
p->childTransformUpdated(this, mat);
}*/
//else
{
//mRot.set(mat);
//Parent::setTransform(mat);
RotationF rot = RotationF(mat);
EulerF tempRot = rot.asEulerF(RotationF::Degrees);
Point3F pos;
mat.getColumn(3,&pos);
setTransform(pos, rot);
}
}
}
void Entity::setTransform(Point3F position, RotationF rotation)
{
MatrixF oldTransform = getTransform();
2016-05-14 04:58:57 +00:00
if (isMounted())
{
mPos = position;
mRot = rotation;
RotationF addRot = mRot + RotationF(mMount.object->getTransform());
MatrixF transf = addRot.asMatrixF();
transf.setPosition(mPos + mMount.object->getPosition());
Parent::setTransform(transf);
if (transf != oldTransform)
setMaskBits(TransformMask);
2016-05-14 04:58:57 +00:00
}
else
{
/*MatrixF newMat, imat, xmat, ymat, zmat;
Point3F radRot = Point3F(mDegToRad(rotation.x), mDegToRad(rotation.y), mDegToRad(rotation.z));
xmat.set(EulerF(radRot.x, 0, 0));
ymat.set(EulerF(0.0f, radRot.y, 0.0f));
zmat.set(EulerF(0, 0, radRot.z));
imat.mul(zmat, xmat);
newMat.mul(imat, ymat);*/
MatrixF newMat = rotation.asMatrixF();
newMat.setColumn(3, position);
mPos = position;
mRot = rotation;
//if (isServerObject())
// setMaskBits(TransformMask);
//setTransform(temp);
// This test is a bit expensive so turn it off in release.
#ifdef TORQUE_DEBUG
//AssertFatal( mat.isAffine(), "SceneObject::setTransform() - Bad transform (non affine)!" );
#endif
//PROFILE_SCOPE(Entity_setTransform);
// Update the transforms.
Parent::setTransform(newMat);
onTransformSet.trigger(&newMat);
Point3F newPos = newMat.getPosition();
RotationF newRot = newMat;
Point3F oldPos = oldTransform.getPosition();
RotationF oldRot = oldTransform;
if (newPos != oldPos || newRot != oldRot)
setMaskBits(TransformMask);
2016-05-14 04:58:57 +00:00
}
}
void Entity::setRenderTransform(const MatrixF &mat)
{
Parent::setRenderTransform(mat);
}
void Entity::setRenderTransform(Point3F position, RotationF rotation)
{
if (isMounted())
{
mPos = position;
mRot = rotation;
RotationF addRot = mRot + RotationF(mMount.object->getTransform());
MatrixF transf = addRot.asMatrixF();
transf.setPosition(mPos + mMount.object->getPosition());
Parent::setRenderTransform(transf);
}
else
{
MatrixF newMat = rotation.asMatrixF();
newMat.setColumn(3, position);
mPos = position;
mRot = rotation;
Parent::setRenderTransform(newMat);
onTransformSet.trigger(&newMat);
}
}
MatrixF Entity::getTransform()
{
if (isMounted())
{
MatrixF mat;
//Use transform from mount
mMount.object->getMountTransform(mMount.node, mMount.xfm, &mat);
Point3F transPos = mat.getPosition() + mPos;
mat.mul(mRot.asMatrixF());
mat.setPosition(transPos);
return mat;
}
else
{
return Parent::getTransform();
}
}
void Entity::setMountOffset(Point3F posOffset)
{
if (isMounted())
{
mMount.xfm.setColumn(3, posOffset);
//mPos = posOffset;
setMaskBits(MountedMask);
}
}
void Entity::setMountRotation(EulerF rotOffset)
{
if (isMounted())
{
MatrixF temp, imat, xmat, ymat, zmat;
Point3F radRot = Point3F(mDegToRad(rotOffset.x), mDegToRad(rotOffset.y), mDegToRad(rotOffset.z));
xmat.set(EulerF(radRot.x, 0, 0));
ymat.set(EulerF(0.0f, radRot.y, 0.0f));
zmat.set(EulerF(0, 0, radRot.z));
imat.mul(zmat, xmat);
temp.mul(imat, ymat);
temp.setColumn(3, mMount.xfm.getPosition());
mMount.xfm = temp;
2016-05-14 04:58:57 +00:00
setMaskBits(MountedMask);
}
}
//
void Entity::getCameraTransform(F32* pos, MatrixF* mat)
{
Vector<CameraInterface*> updaters = getComponents<CameraInterface>();
for (Vector<CameraInterface*>::iterator it = updaters.begin(); it != updaters.end(); it++)
{
if ((*it)->getCameraTransform(pos, mat))
{
2016-05-14 04:58:57 +00:00
return;
}
}
}
void Entity::getMountTransform(S32 index, const MatrixF &xfm, MatrixF *outMat)
{
RenderComponentInterface* renderInterface = getComponent<RenderComponentInterface>();
if (renderInterface)
{
renderInterface->getShapeInstance()->animate();
S32 nodeCount = renderInterface->getShapeInstance()->getShape()->nodes.size();
if (index >= 0 && index < nodeCount)
{
MatrixF mountTransform = renderInterface->getShapeInstance()->mNodeTransforms[index];
mountTransform.mul(xfm);
const Point3F& scale = getScale();
// The position of the mount point needs to be scaled.
Point3F position = mountTransform.getPosition();
position.convolve(scale);
mountTransform.setPosition(position);
// Also we would like the object to be scaled to the model.
outMat->mul(mObjToWorld, mountTransform);
return;
}
}
// Then let SceneObject handle it.
Parent::getMountTransform(index, xfm, outMat);
}
void Entity::getRenderMountTransform(F32 delta, S32 index, const MatrixF &xfm, MatrixF *outMat)
{
RenderComponentInterface* renderInterface = getComponent<RenderComponentInterface>();
if (renderInterface && renderInterface->getShapeInstance())
{
renderInterface->getShapeInstance()->animate();
S32 nodeCount = renderInterface->getShapeInstance()->getShape()->nodes.size();
if (index >= 0 && index < nodeCount)
{
MatrixF mountTransform = renderInterface->getShapeInstance()->mNodeTransforms[index];
mountTransform.mul(xfm);
const Point3F& scale = getScale();
// The position of the mount point needs to be scaled.
Point3F position = mountTransform.getPosition();
position.convolve(scale);
mountTransform.setPosition(position);
// Also we would like the object to be scaled to the model.
outMat->mul(getRenderTransform(), mountTransform);
return;
}
}
// Then let SceneObject handle it.
Parent::getMountTransform(index, xfm, outMat);
}
//
//These basically just redirect to any collision behaviors we have
bool Entity::castRay(const Point3F &start, const Point3F &end, RayInfo* info)
{
Vector<CastRayInterface*> updaters = getComponents<CastRayInterface>();
for (Vector<CastRayInterface*>::iterator it = updaters.begin(); it != updaters.end(); it++)
{
if ((*it)->castRay(start, end, info))
{
return true;
}
}
return false;
}
bool Entity::castRayRendered(const Point3F &start, const Point3F &end, RayInfo *info)
{
Vector<CastRayRenderedInterface*> updaters = getComponents<CastRayRenderedInterface>();
for (Vector<CastRayRenderedInterface*>::iterator it = updaters.begin(); it != updaters.end(); it++)
{
if ((*it)->castRayRendered(start, end, info))
{
return true;
}
}
return false;
}
bool Entity::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere)
{
Vector<BuildPolyListInterface*> updaters = getComponents<BuildPolyListInterface>();
for (Vector<BuildPolyListInterface*>::iterator it = updaters.begin(); it != updaters.end(); it++)
{
return (*it)->buildPolyList(context, polyList, box, sphere);
}
return false;
}
void Entity::buildConvex(const Box3F& box, Convex* convex)
{
Vector<BuildConvexInterface*> updaters = getComponents<BuildConvexInterface>();
for (Vector<BuildConvexInterface*>::iterator it = updaters.begin(); it != updaters.end(); it++)
{
(*it)->buildConvex(box, convex);
}
}
//
// Mounting and heirarchy manipulation
void Entity::mountObject(SceneObject* objB, MatrixF txfm)
{
Parent::mountObject(objB, -1, txfm);
Parent::addObject(objB);
}
void Entity::mountObject(SceneObject *obj, S32 node, const MatrixF &xfm)
{
Parent::mountObject(obj, node, xfm);
}
void Entity::onMount(SceneObject *obj, S32 node)
{
deleteNotify(obj);
// Are we mounting to a GameBase object?
Entity *entityObj = dynamic_cast<Entity*>(obj);
if (entityObj && entityObj->getControlObject() != this)
processAfter(entityObj);
if (!isGhost()) {
setMaskBits(MountedMask);
//TODO implement this callback
//onMount_callback( this, obj, node );
}
}
void Entity::onUnmount(SceneObject *obj, S32 node)
{
clearNotify(obj);
Entity *entityObj = dynamic_cast<Entity*>(obj);
if (entityObj && entityObj->getControlObject() != this)
clearProcessAfter();
if (!isGhost()) {
setMaskBits(MountedMask);
//TODO implement this callback
//onUnmount_callback( this, obj, node );
}
}
void Entity::setControllingClient(GameConnection* client)
{
if (isGhost() && gSFX3DWorld)
{
if (gSFX3DWorld->getListener() == this && !client && getControllingClient() && getControllingClient()->isConnectionToServer())
{
// We are the current listener and are no longer a controller object on the
// connection, so clear our listener status.
gSFX3DWorld->setListener(NULL);
}
else if (client && client->isConnectionToServer() && !getControllingObject())
{
// We're on the local client and not controlled by another object, so make
// us the current SFX listener.
gSFX3DWorld->setListener(this);
}
}
Parent::setControllingClient(client);
}
2016-05-14 04:58:57 +00:00
//Heirarchy stuff
void Entity::addObject(SimObject* object)
{
Component* component = dynamic_cast<Component*>(object);
if (component)
{
addComponent(component);
return;
}
Entity* e = dynamic_cast<Entity*>(object);
if (e)
{
MatrixF offset;
//offset.mul(getWorldTransform(), e->getWorldTransform());
//check if we're mounting to a node on a shape we have
String node = e->getDataField("mountNode", NULL);
if (!node.isEmpty())
{
RenderComponentInterface *renderInterface = getComponent<RenderComponentInterface>();
if (renderInterface)
{
TSShape* shape = renderInterface->getShape();
S32 nodeIdx = shape->findNode(node);
mountObject(e, nodeIdx, MatrixF::Identity);
}
else
{
mountObject(e, MatrixF::Identity);
}
}
else
{
/*Point3F posOffset = mPos - e->getPosition();
mPos = posOffset;
RotationF rotOffset = mRot - e->getRotation();
mRot = rotOffset;
setMaskBits(TransformMask);
mountObject(e, MatrixF::Identity);*/
mountObject(e, MatrixF::Identity);
}
//e->setMountOffset(e->getPosition() - getPosition());
//Point3F diff = getWorldTransform().toEuler() - e->getWorldTransform().toEuler();
//e->setMountRotation(Point3F(mRadToDeg(diff.x),mRadToDeg(diff.y),mRadToDeg(diff.z)));
//mountObject(e, offset);
}
else
{
SceneObject* so = dynamic_cast<SceneObject*>(object);
if (so)
{
//get the difference and build it as our offset!
Point3F posOffset = so->getPosition() - mPos;
RotationF rotOffset = RotationF(so->getTransform()) - mRot;
MatrixF offset = rotOffset.asMatrixF();
offset.setPosition(posOffset);
mountObject(so, offset);
return;
}
}
Parent::addObject(object);
}
void Entity::removeObject(SimObject* object)
{
Entity* e = dynamic_cast<Entity*>(object);
if (e)
{
mPos = mPos + e->getPosition();
mRot = mRot + e->getRotation();
unmountObject(e);
setMaskBits(TransformMask);
}
else
{
SceneObject* so = dynamic_cast<SceneObject*>(object);
if (so)
unmountObject(so);
}
Parent::removeObject(object);
}
bool Entity::addComponent(Component *comp)
{
if (comp == NULL)
2016-05-14 04:58:57 +00:00
return false;
//double-check were not re-adding anything
mComponents.push_back(comp);
// Register the component with this owner.
comp->setOwner(this);
//if we've already been added and this is being added after the fact(at runtime),
//then just go ahead and call it's onComponentAdd so it can get to work
if (mInitialized)
comp->onComponentAdd();
onComponentAdded.trigger(comp);
return true;
}
SimObject* Entity::findObjectByInternalName(StringTableEntry internalName, bool searchChildren)
{
for (U32 i = 0; i < mComponents.size(); i++)
{
if (mComponents[i]->getInternalName() == internalName)
{
return mComponents[i];
}
}
return Parent::findObjectByInternalName(internalName, searchChildren);
}
//////////////////////////////////////////////////////////////////////////
bool Entity::removeComponent(Component *comp, bool deleteComponent)
{
if (comp == NULL)
return false;
if(mComponents.remove(comp))
{
AssertFatal(comp->isProperlyAdded(), "Don't know how but a component is not registered w/ the sim");
//setComponentsDirty();
onComponentRemoved.trigger(comp);
comp->onComponentRemove(); //in case the behavior needs to do cleanup on the owner
comp->setOwner(NULL);
2016-05-14 04:58:57 +00:00
if (deleteComponent)
comp->safeDeleteObject();
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
//NOTE:
//The actor class calls this and flags the deletion of the behaviors to false so that behaviors that should no longer be attached during
//a network update will indeed be removed from the object. The reason it doesn't delete them is because when clearing the local behavior
//list, it would delete them, purging the ghost, and causing a crash when the unpack update tried to fetch any existing behaviors' ghosts
//to re-add them. Need to implement a clean clear function that will clear the local list, and only delete unused behaviors during an update.
void Entity::clearComponents(bool deleteComponents)
{
bool srv = isServerObject();
if (!deleteComponents)
{
while (mComponents.size() > 0)
{
removeComponent(mComponents.first(), deleteComponents);
}
}
else
{
while (mComponents.size() > 0)
{
Component* comp = mComponents.first();
if (comp)
{
comp->onComponentRemove(); //in case the behavior needs to do cleanup on the owner
bool removed = mComponents.remove(comp);
//we only need to delete them on the server side. they'll be cleaned up on the client side
//via the ghosting system for us
if (isServerObject())
comp->deleteObject();
}
}
}
}
//////////////////////////////////////////////////////////////////////////
Component *Entity::getComponent(const U32 index) const
{
if (index < mComponents.size())
return mComponents[index];
return NULL;
}
Component *Entity::getComponent(String componentType)
{
for (U32 i = 0; i < mComponents.size(); i++)
{
Component* comp = mComponents[i];
/*String namespaceName = comp->getNamespace()->mName;
//check our namespace first
if (namespaceName == componentType)
{
return comp;
}
else
{*/
//lets scan up, just to be sure
Namespace *NS = comp->getNamespace();
//we shouldn't ever go past Component into net object, as we're no longer dealing with component classes
while (dStrcmp(NS->getName(), "NetObject"))
{
String namespaceName = NS->getName();
if (namespaceName == componentType)
{
return comp;
}
else
{
NS = NS->getParent();
}
}
//}
}
return NULL;
}
void Entity::onInspect()
{
Vector<EditorInspectInterface*> updaters = getComponents<EditorInspectInterface>();
for (Vector<EditorInspectInterface*>::iterator it = updaters.begin(); it != updaters.end(); it++)
{
2016-05-14 04:58:57 +00:00
(*it)->onInspect();
}
}
void Entity::onEndInspect()
{
Vector<EditorInspectInterface*> updaters = getComponents<EditorInspectInterface>();
for (Vector<EditorInspectInterface*>::iterator it = updaters.begin(); it != updaters.end(); it++) {
(*it)->onEndInspect();
}
GuiTreeViewCtrl *editorTree = dynamic_cast<GuiTreeViewCtrl*>(Sim::findObject("EditorTree"));
if (!editorTree)
return;
S32 componentItemIdx = editorTree->findItemByName("Components");
editorTree->removeItem(componentItemIdx, false);
}
static void writeTabs(Stream &stream, U32 count)
{
char tab[] = " ";
while (count--)
stream.write(3, (void*)tab);
}
void Entity::write(Stream &stream, U32 tabStop, U32 flags)
{
// Do *not* call parent on this
/*VectorPtr<ComponentObject *> &componentList = lockComponentList();
// export selected only?
if( ( flags & SelectedOnly ) && !isSelected() )
{
for( BehaviorObjectIterator i = componentList.begin(); i != componentList.end(); i++ )
(*i)->write(stream, tabStop, flags);
goto write_end;
}*/
//catch if we have any written behavior fields already in the file, and clear them. We don't need to double-up
//the entries for no reason.
/*if(getFieldDictionary())
{
//get our dynamic field count, then parse through them to see if they're a behavior or not
//reset it
SimFieldDictionary* fieldDictionary = getFieldDictionary();
SimFieldDictionaryIterator itr(fieldDictionary);
for (S32 i = 0; i < fieldDictionary->getNumFields(); i++)
{
if (!(*itr))
break;
SimFieldDictionary::Entry* entry = *itr;
if(strstr(entry->slotName, "_behavior"))
{
entry->slotName = "";
entry->value = "";
}
++itr;
}
}*/
//all existing written behavior fields should be cleared. now write the object block
writeTabs(stream, tabStop);
char buffer[1024];
dSprintf(buffer, sizeof(buffer), "new %s(%s) {\r\n", getClassName(), getName() ? getName() : "");
stream.write(dStrlen(buffer), buffer);
writeFields(stream, tabStop + 1);
stream.write(1, "\n");
////first, write out our behavior objects
// NOW we write the behavior fields proper
if (mComponents.size() > 0)
{
// Pack out the behaviors into fields
U32 i = 0;
for (U32 i = 0; i < mComponents.size(); i++)
{
writeTabs(stream, tabStop + 1);
char buffer[1024];
dSprintf(buffer, sizeof(buffer), "new %s() {\r\n", mComponents[i]->getClassName());
stream.write(dStrlen(buffer), buffer);
//bi->writeFields( stream, tabStop + 2 );
mComponents[i]->packToStream(stream, tabStop + 2, i - 1, flags);
writeTabs(stream, tabStop + 1);
stream.write(4, "};\r\n");
}
}
//
//if (size() > 0)
// stream.write(2, "\r\n");
for (U32 i = 0; i < size(); i++)
{
SimObject* child = (*this)[i];
if (child->getCanSave())
child->write(stream, tabStop + 1, flags);
}
//stream.write(2, "\r\n");
writeTabs(stream, tabStop);
stream.write(4, "};\r\n");
//write_end:
//unlockComponentList();
}
SimObject* Entity::getTamlChild(const U32 childIndex) const
{
// Sanity!
AssertFatal(childIndex < getTamlChildCount(), "SimSet::getTamlChild() - Child index is out of range.");
// For when the assert is not used.
if (childIndex >= getTamlChildCount())
return NULL;
//we always order components first, child objects second
if (childIndex >= getComponentCount())
return at(childIndex - getComponentCount());
else
return getComponent(childIndex);
}
//
void Entity::onCameraScopeQuery(NetConnection* connection, CameraScopeQuery* query)
{
// Object itself is in scope.
Parent::onCameraScopeQuery(connection, query);
if (CameraInterface* cI = getComponent<CameraInterface>())
{
cI->onCameraScopeQuery(connection, query);
}
}
//
void Entity::setObjectBox(Box3F objBox)
{
mObjBox = objBox;
resetWorldBox();
if (isServerObject())
setMaskBits(BoundsMask);
}
void Entity::updateContainer()
{
PROFILE_SCOPE(Entity_updateContainer);
// Update container drag and buoyancy properties
containerInfo.box = getWorldBox();
//containerInfo.mass = mMass;
getContainer()->findObjects(containerInfo.box, WaterObjectType | PhysicalZoneObjectType, findRouter, &containerInfo);
//mWaterCoverage = info.waterCoverage;
//mLiquidType = info.liquidType;
//mLiquidHeight = info.waterHeight;
//setCurrentWaterObject( info.waterObject );
// This value might be useful as a datablock value,
// This is what allows the player to stand in shallow water (below this coverage)
// without jiggling from buoyancy
/*if (info.waterCoverage >= 0.25f)
{
// water viscosity is used as drag for in water.
// ShapeBaseData drag is used for drag outside of water.
// Combine these two components to calculate this ShapeBase object's
// current drag.
mDrag = (info.waterCoverage * info.waterViscosity) +
(1.0f - info.waterCoverage) * mDrag;
//mBuoyancy = (info.waterDensity / mDataBlock->density) * info.waterCoverage;
}
//mAppliedForce = info.appliedForce;
mGravityMod = info.gravityScale;*/
}
//
void Entity::notifyComponents(String signalFunction, String argA, String argB, String argC, String argD, String argE)
{
for (U32 i = 0; i < mComponents.size(); i++)
{
// We can do this because both are in the string table
Component *comp = mComponents[i];
if (comp->isActive())
{
if (comp->isMethod(signalFunction))
Con::executef(comp, signalFunction, argA, argB, argC, argD, argE);
}
}
}
2016-05-14 04:58:57 +00:00
void Entity::setComponentsDirty()
{
if (mToLoadComponents.empty())
mStartComponentUpdate = true;
//we need to build a list of behaviors that need to be pushed across the network
for (U32 i = 0; i < mComponents.size(); i++)
{
// We can do this because both are in the string table
Component *comp = mComponents[i];
if (comp->isNetworked())
{
bool unique = true;
for (U32 i = 0; i < mToLoadComponents.size(); i++)
{
if (mToLoadComponents[i]->getId() == comp->getId())
{
unique = false;
break;
}
}
if (unique)
mToLoadComponents.push_back(comp);
}
}
setMaskBits(ComponentsMask);
}
void Entity::setComponentDirty(Component *comp, bool forceUpdate)
{
bool found = false;
for (U32 i = 0; i < mComponents.size(); i++)
{
if (mComponents[i]->getId() == comp->getId())
{
mComponents[i]->setOwner(this);
return;
}
}
if (!found)
return;
//if(mToLoadComponents.empty())
// mStartComponentUpdate = true;
/*if (comp->isNetworked() || forceUpdate)
{
bool unique = true;
for (U32 i = 0; i < mToLoadComponents.size(); i++)
{
if (mToLoadComponents[i]->getId() == comp->getId())
{
unique = false;
break;
}
}
if (unique)
mToLoadComponents.push_back(comp);
}
setMaskBits(ComponentsMask);*/
}
DefineEngineMethod(Entity, mountObject, bool,
(SceneObject* objB, TransformF txfm), (MatrixF::Identity),
"@brief Mount objB to this object at the desired slot with optional transform.\n\n"
"@param objB Object to mount onto us\n"
"@param slot Mount slot ID\n"
"@param txfm (optional) mount offset transform\n"
"@return true if successful, false if failed (objB is not valid)")
{
if (objB)
{
//BUG: Unsure how it broke, but atm the default transform passed in here is rotated 180 degrees. This doesn't happen
//for the SceneObject mountobject method. Hackish, but for now, just default to a clean MatrixF::Identity
object->mountObject(objB, /*MatrixF::Identity*/txfm.getMatrix());
return true;
}
return false;
}
DefineEngineMethod(Entity, setMountOffset, void,
(Point3F posOffset), (Point3F(0, 0, 0)),
"@brief Mount objB to this object at the desired slot with optional transform.\n\n"
"@param objB Object to mount onto us\n"
"@param slot Mount slot ID\n"
"@param txfm (optional) mount offset transform\n"
"@return true if successful, false if failed (objB is not valid)")
{
object->setMountOffset(posOffset);
}
DefineEngineMethod(Entity, setMountRotation, void,
(EulerF rotOffset), (EulerF(0, 0, 0)),
"@brief Mount objB to this object at the desired slot with optional transform.\n\n"
"@param objB Object to mount onto us\n"
"@param slot Mount slot ID\n"
"@param txfm (optional) mount offset transform\n"
"@return true if successful, false if failed (objB is not valid)")
{
object->setMountRotation(rotOffset);
}
DefineEngineMethod(Entity, getMountTransform, TransformF, (), ,
"@brief Mount objB to this object at the desired slot with optional transform.\n\n"
"@param objB Object to mount onto us\n"
"@param slot Mount slot ID\n"
"@param txfm (optional) mount offset transform\n"
"@return true if successful, false if failed (objB is not valid)")
{
MatrixF mat;
object->getMountTransform(0, MatrixF::Identity, &mat);
return mat;
}
DefineEngineMethod(Entity, setBox, void,
(Point3F box), (Point3F(1, 1, 1)),
"@brief Mount objB to this object at the desired slot with optional transform.\n\n"
"@param objB Object to mount onto us\n"
"@param slot Mount slot ID\n"
"@param txfm (optional) mount offset transform\n"
"@return true if successful, false if failed (objB is not valid)")
{
object->setObjectBox(Box3F(-box, box));
}
/*DefineConsoleMethod(Entity, callOnComponents, void, (const char* functionName), ,
"Get the number of static fields on the object.\n"
"@return The number of static fields defined on the object.")
{
object->callOnComponents(functionName);
}
ConsoleMethod(Entity, callMethod, void, 3, 64, "(methodName, argi) Calls script defined method\n"
"@param methodName The method's name as a string\n"
"@param argi Any arguments to pass to the method\n"
"@return No return value"
"@note %obj.callMethod( %methodName, %arg1, %arg2, ... );\n")
{
object->callMethodArgList(argc - 1, argv + 2);
}
ConsoleMethod(Entity, addComponents, void, 2, 2, "() - Add all fielded behaviors\n"
"@return No return value")
{
object->addComponents();
}*/
ConsoleMethod(Entity, addComponent, bool, 3, 3, "(Component* bi) - Add a behavior to the object\n"
2016-05-14 04:58:57 +00:00
"@param bi The behavior instance to add"
"@return (bool success) Whether or not the behavior was successfully added")
{
Component *comp = dynamic_cast<Component *>(Sim::findObject(argv[2]));
if (comp != NULL)
{
bool success = object->addComponent(comp);
if (success)
{
//Placed here so we can differentiate against adding a new behavior during runtime, or when we load all
//fielded behaviors on mission load. This way, we can ensure that we only call the callback
//once everything is loaded. This avoids any problems with looking for behaviors that haven't been added yet, etc.
if (comp->isMethod("onBehaviorAdd"))
Con::executef(comp, "onBehaviorAdd");
return true;
}
}
return false;
}
ConsoleMethod(Entity, removeComponent, bool, 3, 4, "(Component* bi, [bool deleteBehavior = true])\n"
2016-05-14 04:58:57 +00:00
"@param bi The behavior instance to remove\n"
"@param deleteBehavior Whether or not to delete the behavior\n"
"@return (bool success) Whether the behavior was successfully removed")
{
bool deleteComponent = true;
if (argc > 3)
deleteComponent = dAtob(argv[3]);
return object->removeComponent(dynamic_cast<Component *>(Sim::findObject(argv[2])), deleteComponent);
}
ConsoleMethod(Entity, clearComponents, void, 2, 2, "() - Clear all behavior instances\n"
"@return No return value")
{
object->clearComponents();
}
ConsoleMethod(Entity, getComponentByIndex, S32, 3, 3, "(int index) - Gets a particular behavior\n"
"@param index The index of the behavior to get\n"
"@return (ComponentInstance bi) The behavior instance you requested")
{
Component *comp = object->getComponent(dAtoi(argv[2]));
return (comp != NULL) ? comp->getId() : 0;
}
DefineConsoleMethod(Entity, getComponent, S32, (String componentName), (""),
"Get the number of static fields on the object.\n"
"@return The number of static fields defined on the object.")
{
Component *comp = object->getComponent(componentName);
return (comp != NULL) ? comp->getId() : 0;
}
/*ConsoleMethod(Entity, getBehaviorByType, S32, 3, 3, "(string BehaviorTemplateName) - gets a behavior\n"
"@param BehaviorTemplateName The name of the template of the behavior instance you want\n"
"@return (ComponentInstance bi) The behavior instance you requested")
{
ComponentInstance *bInstance = object->getComponentByType(StringTable->insert(argv[2]));
return (bInstance != NULL) ? bInstance->getId() : 0;
}*/
/*ConsoleMethod(Entity, reOrder, bool, 3, 3, "(ComponentInstance inst, [int desiredIndex = 0])\n"
"@param inst The behavior instance you want to reorder\n"
"@param desiredIndex The index you want the behavior instance to be reordered to\n"
"@return (bool success) Whether or not the behavior instance was successfully reordered")
{
Component *inst = dynamic_cast<Component *>(Sim::findObject(argv[1]));
if (inst == NULL)
return false;
U32 idx = 0;
if (argc > 2)
idx = dAtoi(argv[2]);
return object->reOrder(inst, idx);
}*/
ConsoleMethod(Entity, getComponentCount, S32, 2, 2, "() - Get the count of behaviors on an object\n"
"@return (int count) The number of behaviors on an object")
{
return object->getComponentCount();
}
DefineConsoleMethod(Entity, setComponentDirty, void, (S32 componentID, bool forceUpdate), (0, false),
"Get the number of static fields on the object.\n"
"@return The number of static fields defined on the object.")
{
/*Component* comp;
if (Sim::findObject(componentID, comp))
object->setComponentDirty(comp, forceUpdate);*/
}
DefineConsoleMethod(Entity, getMoveVector, VectorF, (),,
"Get the number of static fields on the object.\n"
"@return The number of static fields defined on the object.")
{
if (object->getControllingClient() != NULL)
{
//fetch our last move
if (object->lastMove.x != 0 || object->lastMove.y != 0 || object->lastMove.z != 0)
return VectorF(object->lastMove.x, object->lastMove.y, object->lastMove.z);
}
return VectorF::Zero;
}
DefineConsoleMethod(Entity, getMoveRotation, VectorF, (), ,
"Get the number of static fields on the object.\n"
"@return The number of static fields defined on the object.")
{
if(object->getControllingClient() != NULL)
{
//fetch our last move
if (object->lastMove.pitch != 0 || object->lastMove.roll != 0 || object->lastMove.yaw != 0)
return VectorF(object->lastMove.pitch, object->lastMove.roll, object->lastMove.yaw);
}
return VectorF::Zero;
}
DefineConsoleMethod(Entity, getMoveTrigger, bool, (S32 triggerNum), (0),
"Get the number of static fields on the object.\n"
"@return The number of static fields defined on the object.")
{
if (object->getControllingClient() != NULL && triggerNum < MaxTriggerKeys)
{
return object->lastMove.trigger[triggerNum];
}
return false;
}
DefineEngineMethod(Entity, getForwardVector, VectorF, (), ,
"Get the direction this object is facing.\n"
"@return a vector indicating the direction this object is facing.\n"
"@note This is the object's y axis.")
{
VectorF forVec = object->getTransform().getForwardVector();
return forVec;
}
2016-05-14 04:58:57 +00:00
DefineConsoleMethod(Entity, setForwardVector, void, (VectorF newForward), (VectorF(0,0,0)),
"Get the number of static fields on the object.\n"
"@return The number of static fields defined on the object.")
{
object->setForwardVector(newForward);
}
DefineConsoleMethod(Entity, lookAt, void, (Point3F lookPosition),,
"Get the number of static fields on the object.\n"
"@return The number of static fields defined on the object.")
{
//object->setForwardVector(newForward);
}
DefineConsoleMethod(Entity, rotateTo, void, (Point3F lookPosition, F32 degreePerSecond), (1.0),
"Get the number of static fields on the object.\n"
"@return The number of static fields defined on the object.")
{
//object->setForwardVector(newForward);
}
DefineConsoleMethod(Entity, notify, void, (String signalFunction, String argA, String argB, String argC, String argD, String argE),
("", "", "", "", "", ""),
"Triggers a signal call to all components for a certain function.")
{
if (signalFunction == String(""))
return;
object->notifyComponents(signalFunction, argA, argB, argC, argD, argE);
2016-05-14 04:58:57 +00:00
}