mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-19 20:24:49 +00:00
commit
13bf126418
|
|
@ -58,7 +58,7 @@ torqueAddSourceDirectories("platform" "platform/threads" "platform/async"
|
|||
|
||||
torqueAddSourceDirectories("platform/nativeDialogs")
|
||||
# Handle T3D
|
||||
torqueAddSourceDirectories( "T3D" "T3D/assets" "T3D/decal" "T3D/examples" "T3D/fps" "T3D/fx"
|
||||
torqueAddSourceDirectories( "T3D" "T3D/AI" "T3D/assets" "T3D/decal" "T3D/examples" "T3D/fps" "T3D/fx"
|
||||
"T3D/gameBase" "T3D/gameBase/std"
|
||||
"T3D/lighting"
|
||||
"T3D/physics"
|
||||
|
|
|
|||
226
Engine/source/T3D/AI/AIAimTarget.cpp
Normal file
226
Engine/source/T3D/AI/AIAimTarget.cpp
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "AIAimTarget.h"
|
||||
#include "AIController.h"
|
||||
|
||||
static U32 sAILoSMask = TerrainObjectType | StaticShapeObjectType | StaticObjectType;
|
||||
|
||||
F32 AIAimTarget::getTargetDistance(SceneObject* target, bool _checkEnabled)
|
||||
{
|
||||
if (!target)
|
||||
{
|
||||
target = mObj.getPointer();
|
||||
if (!target)
|
||||
return F32_MAX;
|
||||
}
|
||||
|
||||
if (_checkEnabled)
|
||||
{
|
||||
if (target->getTypeMask() & ShapeBaseObjectType)
|
||||
{
|
||||
ShapeBase* shapeBaseCheck = static_cast<ShapeBase*>(target);
|
||||
if (shapeBaseCheck)
|
||||
if (shapeBaseCheck->getDamageState() != ShapeBase::Enabled) return false;
|
||||
}
|
||||
else
|
||||
return F32_MAX;
|
||||
}
|
||||
|
||||
return (getPosition() - target->getPosition()).len();
|
||||
}
|
||||
|
||||
bool AIAimTarget::checkInLos(SceneObject* target, bool _useMuzzle, bool _checkEnabled)
|
||||
{
|
||||
ShapeBase* sbo = dynamic_cast<ShapeBase*>(getCtrl()->getAIInfo()->mObj.getPointer());
|
||||
if (!target)
|
||||
{
|
||||
target = dynamic_cast<ShapeBase*>(mObj.getPointer());
|
||||
if (!target)
|
||||
return false;
|
||||
}
|
||||
if (_checkEnabled)
|
||||
{
|
||||
if (target->getTypeMask() & ShapeBaseObjectType)
|
||||
{
|
||||
ShapeBase* shapeBaseCheck = static_cast<ShapeBase*>(target);
|
||||
if (shapeBaseCheck)
|
||||
if (shapeBaseCheck->getDamageState() != ShapeBase::Enabled) return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
RayInfo ri;
|
||||
|
||||
sbo->disableCollision();
|
||||
|
||||
S32 mountCount = target->getMountedObjectCount();
|
||||
for (S32 i = 0; i < mountCount; i++)
|
||||
{
|
||||
target->getMountedObject(i)->disableCollision();
|
||||
}
|
||||
|
||||
Point3F checkPoint;
|
||||
if (_useMuzzle)
|
||||
sbo->getMuzzlePoint(0, &checkPoint);
|
||||
else
|
||||
{
|
||||
MatrixF eyeMat;
|
||||
sbo->getEyeTransform(&eyeMat);
|
||||
eyeMat.getColumn(3, &checkPoint);
|
||||
}
|
||||
|
||||
bool hit = !gServerContainer.castRay(checkPoint, target->getBoxCenter(), sAILoSMask, &ri);
|
||||
sbo->enableCollision();
|
||||
|
||||
for (S32 i = 0; i < mountCount; i++)
|
||||
{
|
||||
target->getMountedObject(i)->enableCollision();
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
bool AIAimTarget::checkInFoV(SceneObject* target, F32 camFov, bool _checkEnabled)
|
||||
{
|
||||
ShapeBase* sbo = dynamic_cast<ShapeBase*>(getCtrl()->getAIInfo()->mObj.getPointer());
|
||||
if (!target)
|
||||
{
|
||||
target = dynamic_cast<ShapeBase*>(mObj.getPointer());
|
||||
if (!target)
|
||||
return false;
|
||||
}
|
||||
if (_checkEnabled)
|
||||
{
|
||||
if (target->getTypeMask() & ShapeBaseObjectType)
|
||||
{
|
||||
ShapeBase* shapeBaseCheck = static_cast<ShapeBase*>(target);
|
||||
if (shapeBaseCheck)
|
||||
if (shapeBaseCheck->getDamageState() != ShapeBase::Enabled) return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
MatrixF cam = sbo->getTransform();
|
||||
Point3F camPos;
|
||||
VectorF camDir;
|
||||
|
||||
cam.getColumn(3, &camPos);
|
||||
cam.getColumn(1, &camDir);
|
||||
|
||||
camFov = mDegToRad(camFov) / 2;
|
||||
|
||||
Point3F shapePos = target->getBoxCenter();
|
||||
VectorF shapeDir = shapePos - camPos;
|
||||
// Test to see if it's within our viewcone, this test doesn't
|
||||
// actually match the viewport very well, should consider
|
||||
// projection and box test.
|
||||
shapeDir.normalize();
|
||||
F32 dot = mDot(shapeDir, camDir);
|
||||
return (dot > mCos(camFov));
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, setAimLocation, void, (Point3F target), ,
|
||||
"@brief Tells the AIPlayer to aim at the location provided.\n\n"
|
||||
|
||||
"@param target An \"x y z\" position in the game world to target.\n\n"
|
||||
|
||||
"@see getAimLocation()\n")
|
||||
{
|
||||
object->setAim(target);
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, getAimLocation, Point3F, (), ,
|
||||
"@brief Returns the point the AIPlayer is aiming at.\n\n"
|
||||
|
||||
"This will reflect the position set by setAimLocation(), "
|
||||
"or the position of the object that the bot is now aiming at. "
|
||||
"If the bot is not aiming at anything, this value will "
|
||||
"change to whatever point the bot's current line-of-sight intercepts."
|
||||
|
||||
"@return World space coordinates of the object AI is aiming at. Formatted as \"X Y Z\".\n\n"
|
||||
|
||||
"@see setAimLocation()\n"
|
||||
"@see setAimObject()\n")
|
||||
{
|
||||
return object->getAim()->getPosition();
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, setAimObject, void, (const char* objName, Point3F offset), (Point3F::Zero), "( GameBase obj, [Point3F offset] )"
|
||||
"Sets the bot's target object. Optionally set an offset from target location."
|
||||
"@hide")
|
||||
{
|
||||
// Find the target
|
||||
SceneObject* targetObject;
|
||||
if (Sim::findObject(objName, targetObject))
|
||||
{
|
||||
|
||||
object->setAim(targetObject, 0.0f, offset);
|
||||
}
|
||||
else
|
||||
object->setAim(0, 0.0f, offset);
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, clearAim, void, (), , "clears the bot's target.")
|
||||
{
|
||||
object->clearAim();
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, getAimObject, S32, (), ,
|
||||
"@brief Gets the object the AIPlayer is targeting.\n\n"
|
||||
|
||||
"@return Returns -1 if no object is being aimed at, "
|
||||
"or the SimObjectID of the object the AIPlayer is aiming at.\n\n"
|
||||
|
||||
"@see setAimObject()\n")
|
||||
{
|
||||
SceneObject* obj = dynamic_cast<GameBase*>(object->getAim()->mObj.getPointer());
|
||||
return obj ? obj->getId() : -1;
|
||||
}
|
||||
|
||||
|
||||
DefineEngineMethod(AIController, getTargetDistance, F32, (SceneObject* obj, bool checkEnabled), (nullAsType<SceneObject*>(), false),
|
||||
"@brief The distance to a given target.\n"
|
||||
"@obj Object to check. (If blank, it will check the current target).\n"
|
||||
"@checkEnabled check whether the object can take damage and if so is still alive.(Defaults to false)\n")
|
||||
{
|
||||
return object->getAim()->getTargetDistance(obj, checkEnabled);
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, checkInLos, bool, (SceneObject* obj, bool useMuzzle, bool checkEnabled), (nullAsType<ShapeBase*>(), false, false),
|
||||
"@brief Check whether an object is in line of sight.\n"
|
||||
"@obj Object to check. (If blank, it will check the current target).\n"
|
||||
"@useMuzzle Use muzzle position. Otherwise use eye position. (defaults to false).\n"
|
||||
"@checkEnabled check whether the object can take damage and if so is still alive.(Defaults to false)\n")
|
||||
{
|
||||
return object->getAim()->checkInLos(obj, useMuzzle, checkEnabled);
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, checkInFoV, bool, (SceneObject* obj, F32 fov, bool checkEnabled), (nullAsType<ShapeBase*>(), 45.0f, false),
|
||||
"@brief Check whether an object is within a specified veiw cone.\n"
|
||||
"@obj Object to check. (If blank, it will check the current target).\n"
|
||||
"@fov view angle in degrees.(Defaults to 45)\n"
|
||||
"@checkEnabled check whether the object can take damage and if so is still alive.(Defaults to false)\n")
|
||||
{
|
||||
return object->getAim()->checkInFoV(obj, fov, checkEnabled);
|
||||
}
|
||||
41
Engine/source/T3D/AI/AIAimTarget.h
Normal file
41
Engine/source/T3D/AI/AIAimTarget.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef _AIAIMTARGET_H_
|
||||
#define _AIAIMTARGET_H_
|
||||
|
||||
#include "AIInfo.h"
|
||||
struct AIAimTarget : public AIInfo
|
||||
{
|
||||
typedef AIInfo Parent;
|
||||
Point3F mAimOffset;
|
||||
bool mTargetInLOS; // Is target object visible?
|
||||
Point3F getPosition() { return ((mObj.isValid()) ? mObj->getPosition() : mPosition) + mAimOffset; }
|
||||
bool checkInLos(SceneObject* target = NULL, bool _useMuzzle = false, bool _checkEnabled = false);
|
||||
bool checkInFoV(SceneObject* target = NULL, F32 camFov = 45.0f, bool _checkEnabled = false);
|
||||
F32 getTargetDistance(SceneObject* target, bool _checkEnabled);
|
||||
AIAimTarget() = delete;
|
||||
AIAimTarget(AIController* controller) : Parent(controller) { mTargetInLOS = false; };
|
||||
AIAimTarget(AIController* controller, SimObjectPtr<SceneObject> objIn, F32 radIn) : Parent(controller, objIn, radIn) { mTargetInLOS = false; };
|
||||
AIAimTarget(AIController* controller, Point3F pointIn, F32 radIn) : Parent(controller, pointIn, radIn) { mTargetInLOS = false; };
|
||||
};
|
||||
|
||||
#endif
|
||||
916
Engine/source/T3D/AI/AIController.cpp
Normal file
916
Engine/source/T3D/AI/AIController.cpp
Normal file
|
|
@ -0,0 +1,916 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "AIController.h"
|
||||
#include "T3D/player.h"
|
||||
#include "T3D/rigidShape.h"
|
||||
#include "T3D/vehicles/wheeledVehicle.h"
|
||||
#include "T3D/vehicles/flyingVehicle.h"
|
||||
|
||||
|
||||
IMPLEMENT_CONOBJECT(AIController);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void AIController::throwCallback(const char* name)
|
||||
{
|
||||
//Con::warnf("throwCallback: %s", name);
|
||||
Con::executef(mControllerData, name, getIdString()); //controller data callbacks
|
||||
|
||||
GameBase* gbo = dynamic_cast<GameBase*>(getAIInfo()->mObj.getPointer());
|
||||
if (!gbo) return;
|
||||
Con::executef(gbo->getDataBlock(), name, getAIInfo()->mObj->getIdString()); //legacy support for object db callbacks
|
||||
}
|
||||
|
||||
void AIController::initPersistFields()
|
||||
{
|
||||
addProtectedField("ControllerData", TYPEID< AIControllerData >(), Offset(mControllerData, AIController),
|
||||
&setControllerDataProperty, &defaultProtectedGetFn,
|
||||
"Script datablock used for game objects.");
|
||||
addFieldV("MoveSpeed", TypeRangedF32, Offset(mMovement.mMoveSpeed, AIController), &CommonValidators::PositiveFloat,
|
||||
"@brief default move sepeed.");
|
||||
}
|
||||
|
||||
bool AIController::setControllerDataProperty(void* obj, const char* index, const char* db)
|
||||
{
|
||||
if (db == NULL || !db[0])
|
||||
{
|
||||
Con::errorf("AIController::setControllerDataProperty - Can't unset ControllerData on AIController objects");
|
||||
return false;
|
||||
}
|
||||
|
||||
AIController* object = static_cast<AIController*>(obj);
|
||||
AIControllerData* data;
|
||||
if (Sim::findObject(db, data))
|
||||
{
|
||||
object->mControllerData = data;
|
||||
return true;
|
||||
}
|
||||
Con::errorf("AIController::setControllerDataProperty - Could not find ControllerData \"%s\"", db);
|
||||
return false;
|
||||
}
|
||||
|
||||
void AIController::setGoal(AIInfo* targ)
|
||||
{
|
||||
if (mGoal) { delete(mGoal); mGoal = NULL; }
|
||||
|
||||
if (targ->mObj.isValid())
|
||||
{
|
||||
delete(mGoal);
|
||||
mGoal = new AIGoal(this, targ->mObj, targ->mRadius);
|
||||
}
|
||||
else if (targ->mPosSet)
|
||||
{
|
||||
delete(mGoal);
|
||||
mGoal = new AIGoal(this, targ->mPosition, targ->mRadius);
|
||||
}
|
||||
}
|
||||
|
||||
void AIController::setGoal(Point3F loc, F32 rad)
|
||||
{
|
||||
if (mGoal) delete(mGoal);
|
||||
mGoal = new AIGoal(this, loc, rad);
|
||||
}
|
||||
|
||||
void AIController::setGoal(SimObjectPtr<SceneObject> objIn, F32 rad)
|
||||
{
|
||||
if (mGoal) delete(mGoal);
|
||||
mGoal = new AIGoal(this, objIn, rad);
|
||||
}
|
||||
|
||||
void AIController::setAim(Point3F loc, F32 rad, Point3F offset)
|
||||
{
|
||||
if (mAimTarget) delete(mAimTarget);
|
||||
mAimTarget = new AIAimTarget(this, loc, rad);
|
||||
mAimTarget->mAimOffset = offset;
|
||||
}
|
||||
|
||||
void AIController::setAim(SimObjectPtr<SceneObject> objIn, F32 rad, Point3F offset)
|
||||
{
|
||||
if (mAimTarget) delete(mAimTarget);
|
||||
mAimTarget = new AIAimTarget(this, objIn, rad);
|
||||
mAimTarget->mAimOffset = offset;
|
||||
}
|
||||
|
||||
bool AIController::getAIMove(Move* movePtr)
|
||||
{
|
||||
*movePtr = NullMove;
|
||||
ShapeBase* sbo = dynamic_cast<ShapeBase*>(getAIInfo()->mObj.getPointer());
|
||||
if (!sbo) return false;
|
||||
|
||||
// Use the eye as the current position.
|
||||
MatrixF eye;
|
||||
sbo->getEyeTransform(&eye);
|
||||
Point3F location = eye.getPosition();
|
||||
Point3F rotation = sbo->getTransform().toEuler();
|
||||
|
||||
// Test for target location in sight if it's an object. The LOS is
|
||||
// run from the eye position to the center of the object's bounding,
|
||||
// which is not very accurate.
|
||||
if (getAim() && getAim()->mObj)
|
||||
{
|
||||
GameBase* gbo = dynamic_cast<GameBase*>(getAIInfo()->mObj.getPointer());
|
||||
if (getAim()->checkInLos(gbo))
|
||||
{
|
||||
if (!getAim()->mTargetInLOS)
|
||||
{
|
||||
throwCallback("onTargetEnterLOS");
|
||||
getAim()->mTargetInLOS = true;
|
||||
}
|
||||
}
|
||||
else if (getAim()->mTargetInLOS)
|
||||
{
|
||||
throwCallback("onTargetExitLOS");
|
||||
getAim()->mTargetInLOS = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sbo->getDamageState() == ShapeBase::Enabled && getGoal())
|
||||
{
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
if (mMovement.mMoveState != ModeStop)
|
||||
getNav()->updateNavMesh();
|
||||
|
||||
if (getNav()->mPathData.path.isNull())
|
||||
{
|
||||
if (getGoal()->getDist() > mControllerData->mFollowTolerance)
|
||||
{
|
||||
if (getGoal()->mObj.isValid())
|
||||
getNav()->followObject(getGoal()->mObj, mControllerData->mFollowTolerance);
|
||||
else if (getGoal()->mPosSet)
|
||||
getNav()->setPathDestination(getGoal()->getPosition(true));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (getGoal()->getDist() > mControllerData->mFollowTolerance)
|
||||
{
|
||||
SceneObject* obj = getAIInfo()->mObj->getObjectMount();
|
||||
if (!obj)
|
||||
{
|
||||
obj = getAIInfo()->mObj;
|
||||
}
|
||||
RayInfo info;
|
||||
if (obj->getContainer()->castRay(obj->getPosition(), obj->getPosition() - Point3F(0, 0, mControllerData->mHeightTolerance), StaticShapeObjectType, &info))
|
||||
{
|
||||
getNav()->repath();
|
||||
}
|
||||
getGoal()->mInRange = false;
|
||||
}
|
||||
if (getGoal()->getDist() < mControllerData->mFollowTolerance )
|
||||
{
|
||||
getNav()->clearPath();
|
||||
mMovement.mMoveState = ModeStop;
|
||||
|
||||
if (!getGoal()->mInRange)
|
||||
{
|
||||
getGoal()->mInRange = true;
|
||||
throwCallback("onTargetInRange");
|
||||
}
|
||||
else getGoal()->mInRange = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (getGoal()->getDist() < mControllerData->mAttackRadius )
|
||||
{
|
||||
if (!getGoal()->mInFiringRange)
|
||||
{
|
||||
getGoal()->mInFiringRange = true;
|
||||
throwCallback("onTargetInFiringRange");
|
||||
}
|
||||
}
|
||||
else getGoal()->mInFiringRange = false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (getGoal()->getDist() > mControllerData->mFollowTolerance)
|
||||
{
|
||||
if (getGoal()->mObj.isValid())
|
||||
getNav()->followObject(getGoal()->mObj, mControllerData->mFollowTolerance);
|
||||
else if (getGoal()->mPosSet)
|
||||
getNav()->setPathDestination(getGoal()->getPosition(true));
|
||||
|
||||
getGoal()->mInRange = false;
|
||||
}
|
||||
if (getGoal()->getDist() < mControllerData->mFollowTolerance)
|
||||
{
|
||||
mMovement.mMoveState = ModeStop;
|
||||
|
||||
if (!getGoal()->mInRange)
|
||||
{
|
||||
getGoal()->mInRange = true;
|
||||
throwCallback("onTargetInRange");
|
||||
}
|
||||
else getGoal()->mInRange = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (getGoal()->getDist() < mControllerData->mAttackRadius)
|
||||
{
|
||||
if (!getGoal()->mInFiringRange)
|
||||
{
|
||||
getGoal()->mInFiringRange = true;
|
||||
throwCallback("onTargetInFiringRange");
|
||||
}
|
||||
}
|
||||
else getGoal()->mInFiringRange = false;
|
||||
}
|
||||
#endif // TORQUE_NAVIGATION_ENABLED
|
||||
}
|
||||
// Orient towards the aim point, aim object, or towards
|
||||
// our destination.
|
||||
if (getAim() || mMovement.mMoveState != ModeStop)
|
||||
{
|
||||
// Update the aim position if we're aiming for an object or explicit position
|
||||
if (getAim())
|
||||
mMovement.mAimLocation = getAim()->getPosition();
|
||||
else
|
||||
mMovement.mAimLocation = getNav()->getMoveDestination();
|
||||
|
||||
mControllerData->resolveYawPtr(this, location, movePtr);
|
||||
mControllerData->resolvePitchPtr(this, location, movePtr);
|
||||
mControllerData->resolveRollPtr(this, location, movePtr);
|
||||
|
||||
if (mMovement.mMoveState != AIController::ModeStop)
|
||||
{
|
||||
F32 xDiff = getNav()->getMoveDestination().x - location.x;
|
||||
F32 yDiff = getNav()->getMoveDestination().y - location.y;
|
||||
if (mFabs(xDiff) < mControllerData->mMoveTolerance && mFabs(yDiff) < mControllerData->mMoveTolerance)
|
||||
{
|
||||
getNav()->onReachDestination();
|
||||
}
|
||||
else
|
||||
{
|
||||
mControllerData->resolveSpeedPtr(this, location, movePtr);
|
||||
mControllerData->resolveStuckPtr(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mControllerData->resolveTriggerStatePtr(this, movePtr);
|
||||
|
||||
getAIInfo()->mLastPos = getAIInfo()->getPosition();
|
||||
return true;
|
||||
}
|
||||
|
||||
void AIController::clearCover()
|
||||
{
|
||||
// Notify cover that we are no longer on our way.
|
||||
if (getCover() && !getCover()->mCoverPoint.isNull())
|
||||
getCover()->mCoverPoint->setOccupied(false);
|
||||
SAFE_DELETE(mCover);
|
||||
}
|
||||
|
||||
void AIController::Movement::stopMove()
|
||||
{
|
||||
mMoveState = ModeStop;
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
getCtrl()->getNav()->clearPath();
|
||||
getCtrl()->clearCover();
|
||||
getCtrl()->getNav()->clearFollow();
|
||||
#endif
|
||||
}
|
||||
void AIController::Movement::onStuck()
|
||||
{
|
||||
mMoveState = AIController::ModeStuck;
|
||||
getCtrl()->throwCallback("onMoveStuck");
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
if (!getCtrl()->getNav()->getPath().isNull())
|
||||
getCtrl()->getNav()->repath();
|
||||
#endif
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, setMoveSpeed, void, (F32 speed), ,
|
||||
"@brief Sets the move speed for an AI object.\n\n"
|
||||
|
||||
"@param speed A speed multiplier between 0.0 and 1.0. "
|
||||
"This is multiplied by the AIController controlled object's base movement rates (as defined in "
|
||||
"its PlayerData datablock)\n\n"
|
||||
|
||||
"@see getMoveDestination()\n")
|
||||
{
|
||||
object->mMovement.setMoveSpeed(speed);
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, getMoveSpeed, F32, (), ,
|
||||
"@brief Gets the move speed of an AI object.\n\n"
|
||||
|
||||
"@return A speed multiplier between 0.0 and 1.0.\n\n"
|
||||
|
||||
"@see setMoveSpeed()\n")
|
||||
{
|
||||
return object->mMovement.getMoveSpeed();
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, stop, void, (), ,
|
||||
"@brief Tells the AIController controlled object to stop moving.\n\n")
|
||||
{
|
||||
object->mMovement.stopMove();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the state of a movement trigger.
|
||||
*
|
||||
* @param slot The trigger slot to set
|
||||
* @param isSet set/unset the trigger
|
||||
*/
|
||||
void AIController::TriggerState::setMoveTrigger(U32 slot, const bool isSet)
|
||||
{
|
||||
if (slot >= MaxTriggerKeys)
|
||||
{
|
||||
Con::errorf("Attempting to set an invalid trigger slot (%i)", slot);
|
||||
}
|
||||
else
|
||||
{
|
||||
mMoveTriggers[slot] = isSet; // set the trigger
|
||||
mControllerRef->getAIInfo()->mObj->setMaskBits(ShapeBase::NoWarpMask); // force the client to updateMove
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the state of a movement trigger.
|
||||
*
|
||||
* @param slot The trigger slot to query
|
||||
* @return True if the trigger is set, false if it is not set
|
||||
*/
|
||||
bool AIController::TriggerState::getMoveTrigger(U32 slot) const
|
||||
{
|
||||
if (slot >= MaxTriggerKeys)
|
||||
{
|
||||
Con::errorf("Attempting to get an invalid trigger slot (%i)", slot);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return mMoveTriggers[slot];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the trigger state for all movement triggers.
|
||||
*/
|
||||
void AIController::TriggerState::clearMoveTriggers()
|
||||
{
|
||||
for (U32 i = 0; i < MaxTriggerKeys; i++)
|
||||
setMoveTrigger(i, false);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
IMPLEMENT_CO_DATABLOCK_V1(AIControllerData);
|
||||
void AIControllerData::resolveYaw(AIController* obj, Point3F location, Move* move)
|
||||
{
|
||||
F32 xDiff = obj->mMovement.mAimLocation.x - location.x;
|
||||
F32 yDiff = obj->mMovement.mAimLocation.y - location.y;
|
||||
Point3F rotation = obj->getAIInfo()->mObj->getTransform().toEuler();
|
||||
|
||||
if (!mIsZero(xDiff) || !mIsZero(yDiff))
|
||||
{
|
||||
// First do Yaw
|
||||
// use the cur yaw between -Pi and Pi
|
||||
F32 curYaw = rotation.z;
|
||||
while (curYaw > M_2PI_F)
|
||||
curYaw -= M_2PI_F;
|
||||
while (curYaw < -M_2PI_F)
|
||||
curYaw += M_2PI_F;
|
||||
|
||||
// find the yaw offset
|
||||
F32 newYaw = mAtan2(xDiff, yDiff);
|
||||
F32 yawDiff = newYaw - curYaw;
|
||||
|
||||
// make it between 0 and 2PI
|
||||
if (yawDiff < 0.0f)
|
||||
yawDiff += M_2PI_F;
|
||||
else if (yawDiff >= M_2PI_F)
|
||||
yawDiff -= M_2PI_F;
|
||||
|
||||
// now make sure we take the short way around the circle
|
||||
if (yawDiff > M_PI_F)
|
||||
yawDiff -= M_2PI_F;
|
||||
else if (yawDiff < -M_PI_F)
|
||||
yawDiff += M_2PI_F;
|
||||
|
||||
move->yaw = yawDiff;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AIControllerData::resolveRoll(AIController* obj, Point3F location, Move* movePtr)
|
||||
{
|
||||
}
|
||||
|
||||
void AIControllerData::resolveSpeed(AIController* obj, Point3F location, Move* movePtr)
|
||||
{
|
||||
F32 xDiff = obj->getNav()->getMoveDestination().x - location.x;
|
||||
F32 yDiff = obj->getNav()->getMoveDestination().y - location.y;
|
||||
Point3F rotation = obj->getAIInfo()->mObj->getTransform().toEuler();
|
||||
|
||||
// Build move direction in world space
|
||||
if (mIsZero(xDiff))
|
||||
movePtr->y = (location.y > obj->getNav()->getMoveDestination().y) ? -1.0f : 1.0f;
|
||||
else
|
||||
{
|
||||
if (mIsZero(yDiff))
|
||||
movePtr->x = (location.x > obj->getNav()->getMoveDestination().x) ? -1.0f : 1.0f;
|
||||
else
|
||||
{
|
||||
if (mFabs(xDiff) > mFabs(yDiff))
|
||||
{
|
||||
F32 value = mFabs(yDiff / xDiff);
|
||||
movePtr->y = (location.y > obj->getNav()->getMoveDestination().y) ? -value : value;
|
||||
movePtr->x = (location.x > obj->getNav()->getMoveDestination().x) ? -1.0f : 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
F32 value = mFabs(xDiff / yDiff);
|
||||
movePtr->x = (location.x > obj->getNav()->getMoveDestination().x) ? -value : value;
|
||||
movePtr->y = (location.y > obj->getNav()->getMoveDestination().y) ? -1.0f : 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Rotate the move into object space (this really only needs
|
||||
// a 2D matrix)
|
||||
Point3F newMove;
|
||||
MatrixF moveMatrix;
|
||||
moveMatrix.set(EulerF(0.0f, 0.0f, -(rotation.z + movePtr->yaw)));
|
||||
moveMatrix.mulV(Point3F(movePtr->x, movePtr->y, 0.0f), &newMove);
|
||||
movePtr->x = newMove.x;
|
||||
movePtr->y = newMove.y;
|
||||
|
||||
// Set movement speed. We'll slow down once we get close
|
||||
// to try and stop on the spot...
|
||||
if (obj->mMovement.mMoveSlowdown)
|
||||
{
|
||||
F32 speed = obj->mMovement.mMoveSpeed;
|
||||
F32 dist = mSqrt(xDiff * xDiff + yDiff * yDiff);
|
||||
F32 maxDist = mMoveTolerance * 2;
|
||||
if (dist < maxDist)
|
||||
speed *= dist / maxDist;
|
||||
movePtr->x *= speed;
|
||||
movePtr->y *= speed;
|
||||
}
|
||||
else
|
||||
{
|
||||
movePtr->x *= obj->mMovement.mMoveSpeed;
|
||||
movePtr->y *= obj->mMovement.mMoveSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
void AIControllerData::resolveTriggerState(AIController* obj, Move* movePtr)
|
||||
{
|
||||
//check for scripted overides
|
||||
for (U32 slot = 0; slot < MaxTriggerKeys; slot++)
|
||||
{
|
||||
movePtr->trigger[slot] = obj->mTriggerState.mMoveTriggers[slot];
|
||||
}
|
||||
}
|
||||
|
||||
void AIControllerData::resolveStuck(AIController* obj)
|
||||
{
|
||||
if (obj->mMovement.mMoveState < AIController::ModeSlowing) return;
|
||||
if (!obj->getGoal()) return;
|
||||
ShapeBase* sbo = dynamic_cast<ShapeBase*>(obj->getAIInfo()->mObj.getPointer());
|
||||
// Don't check for ai stuckness if animation during
|
||||
// an anim-clip effect override.
|
||||
if (sbo->getDamageState() == ShapeBase::Enabled && !(sbo->anim_clip_flags & ShapeBase::ANIM_OVERRIDDEN) && !sbo->isAnimationLocked())
|
||||
{
|
||||
// We should check to see if we are stuck...
|
||||
F32 locationDelta = (obj->getAIInfo()->getPosition() - obj->getAIInfo()->mLastPos).len();
|
||||
if (locationDelta < mMoveStuckTolerance)
|
||||
{
|
||||
if (obj->mMovement.mMoveStuckTestCountdown > 0)
|
||||
--obj->mMovement.mMoveStuckTestCountdown;
|
||||
else
|
||||
{
|
||||
// If we are slowing down, then it's likely that our location delta will be less than
|
||||
// our move stuck tolerance. Because we can be both slowing and stuck
|
||||
// we should TRY to check if we've moved. This could use better detection.
|
||||
if (obj->mMovement.mMoveState != AIController::ModeSlowing || locationDelta == 0)
|
||||
{
|
||||
obj->mMovement.onStuck();
|
||||
obj->mMovement.mMoveStuckTestCountdown = obj->mControllerData->mMoveStuckTestDelay;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AIControllerData::AIControllerData()
|
||||
{
|
||||
mMoveTolerance = 0.25f;
|
||||
mAttackRadius = 2.0f;
|
||||
mMoveStuckTolerance = 0.01f;
|
||||
mMoveStuckTestDelay = 30;
|
||||
mHeightTolerance = 0.001f;
|
||||
mFollowTolerance = 1.0f;
|
||||
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
mLinkTypes = LinkData(AllFlags);
|
||||
mNavSize = AINavigation::Regular;
|
||||
mFlocking.mChance = 90;
|
||||
mFlocking.mMin = 1.0f;
|
||||
mFlocking.mMax = 3.0f;
|
||||
mFlocking.mSideStep = 0.01f;
|
||||
#endif
|
||||
resolveYawPtr.bind(this, &AIControllerData::resolveYaw);
|
||||
resolvePitchPtr.bind(this, &AIControllerData::resolvePitch);
|
||||
resolveRollPtr.bind(this, &AIControllerData::resolveRoll);
|
||||
resolveSpeedPtr.bind(this, &AIControllerData::resolveSpeed);
|
||||
resolveTriggerStatePtr.bind(this, &AIControllerData::resolveTriggerState);
|
||||
resolveStuckPtr.bind(this, &AIControllerData::resolveStuck);
|
||||
}
|
||||
|
||||
AIControllerData::AIControllerData(const AIControllerData& other, bool temp_clone) : SimDataBlock(other, temp_clone)
|
||||
{
|
||||
mMoveTolerance = other.mMoveTolerance;
|
||||
mAttackRadius = other.mAttackRadius;
|
||||
mMoveStuckTolerance = other.mMoveStuckTolerance;
|
||||
mMoveStuckTestDelay = other.mMoveStuckTestDelay;
|
||||
mHeightTolerance = other.mHeightTolerance;
|
||||
mFollowTolerance = other.mFollowTolerance;
|
||||
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
mLinkTypes = other.mLinkTypes;
|
||||
mNavSize = other.mNavSize;
|
||||
mFlocking.mChance = other.mFlocking.mChance;
|
||||
mFlocking.mMin = other.mFlocking.mMin;
|
||||
mFlocking.mMax = other.mFlocking.mMax;
|
||||
mFlocking.mSideStep = other.mFlocking.mSideStep;
|
||||
#endif
|
||||
|
||||
resolveYawPtr.bind(this, &AIControllerData::resolveYaw);
|
||||
resolvePitchPtr.bind(this, &AIControllerData::resolvePitch);
|
||||
resolveRollPtr.bind(this, &AIControllerData::resolveRoll);
|
||||
resolveSpeedPtr.bind(this, &AIControllerData::resolveSpeed);
|
||||
resolveTriggerStatePtr.bind(this, &AIControllerData::resolveTriggerState);
|
||||
resolveStuckPtr.bind(this, &AIControllerData::resolveStuck);
|
||||
}
|
||||
|
||||
void AIControllerData::initPersistFields()
|
||||
{
|
||||
docsURL;
|
||||
addGroup("AI");
|
||||
|
||||
addFieldV("moveTolerance", TypeRangedF32, Offset(mMoveTolerance, AIControllerData), &CommonValidators::PositiveFloat,
|
||||
"@brief Distance from destination before stopping.\n\n"
|
||||
"When the AIController controlled object is moving to a given destination it will move to within "
|
||||
"this distance of the destination and then stop. By providing this tolerance "
|
||||
"it helps the AIController controlled object from never reaching its destination due to minor obstacles, "
|
||||
"rounding errors on its position calculation, etc. By default it is set to 0.25.\n");
|
||||
|
||||
addFieldV("followTolerance", TypeRangedF32, Offset(mFollowTolerance, AIControllerData), &CommonValidators::PositiveFloat,
|
||||
"@brief Distance from destination before stopping.\n\n"
|
||||
"When the AIController controlled object is moving to a given destination it will move to within "
|
||||
"this distance of the destination and then stop. By providing this tolerance "
|
||||
"it helps the AIController controlled object from never reaching its destination due to minor obstacles, "
|
||||
"rounding errors on its position calculation, etc. By default it is set to 0.25.\n");
|
||||
|
||||
addFieldV("AttackRadius", TypeRangedF32, Offset(mAttackRadius, AIControllerData), &CommonValidators::PositiveFloat,
|
||||
"@brief Distance considered in firing range for callback purposes.");
|
||||
|
||||
addFieldV("moveStuckTolerance", TypeRangedF32, Offset(mMoveStuckTolerance, AIControllerData), &CommonValidators::PositiveFloat,
|
||||
"@brief Distance tolerance on stuck check.\n\n"
|
||||
"When the AIController controlled object controlled object is moving to a given destination, if it ever moves less than "
|
||||
"this tolerance during a single tick, the AIController controlled object is considered stuck. At this point "
|
||||
"the onMoveStuck() callback is called on the datablock.\n");
|
||||
|
||||
addFieldV("HeightTolerance", TypeRangedF32, Offset(mHeightTolerance, AIControllerData), &CommonValidators::PositiveFloat,
|
||||
"@brief Distance from destination before stopping.\n\n"
|
||||
"When the AIController controlled object is moving to a given destination it will move to within "
|
||||
"this distance of the destination and then stop. By providing this tolerance "
|
||||
"it helps the AIController controlled object from never reaching its destination due to minor obstacles, "
|
||||
"rounding errors on its position calculation, etc. By default it is set to 0.25.\n");
|
||||
|
||||
addFieldV("moveStuckTestDelay", TypeRangedS32, Offset(mMoveStuckTestDelay, AIControllerData), &CommonValidators::PositiveInt,
|
||||
"@brief The number of ticks to wait before testing if the AIController controlled object is stuck.\n\n"
|
||||
"When the AIController controlled object is asked to move, this property is the number of ticks to wait "
|
||||
"before the AIController controlled object starts to check if it is stuck. This delay allows the AIController controlled object "
|
||||
"to accelerate to full speed without its initial slow start being considered as stuck.\n"
|
||||
"@note Set to zero to have the stuck test start immediately.\n");
|
||||
endGroup("AI");
|
||||
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
addGroup("Pathfinding");
|
||||
addFieldV("FlockChance", TypeRangedS32, Offset(mFlocking.mChance, AIControllerData), &CommonValidators::S32Percent,
|
||||
"@brief chance of flocking.");
|
||||
addFieldV("FlockMin", TypeRangedF32, Offset(mFlocking.mMin, AIControllerData), &CommonValidators::PositiveFloat,
|
||||
"@brief min flocking separation distance.");
|
||||
addFieldV("FlockMax", TypeRangedF32, Offset(mFlocking.mMax, AIControllerData), &CommonValidators::PositiveFloat,
|
||||
"@brief max flocking clustering distance.");
|
||||
addFieldV("FlockSideStep", TypeRangedF32, Offset(mFlocking.mSideStep, AIControllerData), &CommonValidators::PositiveFloat,
|
||||
"@brief Distance from destination before we stop moving out of the way.");
|
||||
|
||||
addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, AIControllerData),
|
||||
"Allow the character to walk on dry land.");
|
||||
addField("allowJump", TypeBool, Offset(mLinkTypes.jump, AIControllerData),
|
||||
"Allow the character to use jump links.");
|
||||
addField("allowDrop", TypeBool, Offset(mLinkTypes.drop, AIControllerData),
|
||||
"Allow the character to use drop links.");
|
||||
addField("allowSwim", TypeBool, Offset(mLinkTypes.swim, AIControllerData),
|
||||
"Allow the character to move in water.");
|
||||
addField("allowLedge", TypeBool, Offset(mLinkTypes.ledge, AIControllerData),
|
||||
"Allow the character to jump ledges.");
|
||||
addField("allowClimb", TypeBool, Offset(mLinkTypes.climb, AIControllerData),
|
||||
"Allow the character to use climb links.");
|
||||
addField("allowTeleport", TypeBool, Offset(mLinkTypes.teleport, AIControllerData),
|
||||
"Allow the character to use teleporters.");
|
||||
|
||||
endGroup("Pathfinding");
|
||||
#endif // TORQUE_NAVIGATION_ENABLED
|
||||
|
||||
Parent::initPersistFields();
|
||||
}
|
||||
|
||||
void AIControllerData::packData(BitStream* stream)
|
||||
{
|
||||
Parent::packData(stream);
|
||||
stream->write(mMoveTolerance);
|
||||
stream->write(mAttackRadius);
|
||||
stream->write(mMoveStuckTolerance);
|
||||
stream->write(mMoveStuckTestDelay);
|
||||
stream->write(mHeightTolerance);
|
||||
stream->write(mFollowTolerance);
|
||||
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
//enums
|
||||
stream->write(mLinkTypes.getFlags());
|
||||
stream->write((U32)mNavSize);
|
||||
// end enums
|
||||
stream->write(mFlocking.mChance);
|
||||
stream->write(mFlocking.mMin);
|
||||
stream->write(mFlocking.mMax);
|
||||
stream->write(mFlocking.mSideStep);
|
||||
#endif // TORQUE_NAVIGATION_ENABLED
|
||||
};
|
||||
|
||||
void AIControllerData::unpackData(BitStream* stream)
|
||||
{
|
||||
Parent::unpackData(stream);
|
||||
|
||||
stream->read(&mMoveTolerance);
|
||||
stream->read(&mAttackRadius);
|
||||
stream->read(&mMoveStuckTolerance);
|
||||
stream->read(&mMoveStuckTestDelay);
|
||||
stream->read(&mHeightTolerance);
|
||||
stream->read(&mFollowTolerance);
|
||||
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
//enums
|
||||
U16 linkFlags;
|
||||
stream->read(&linkFlags);
|
||||
mLinkTypes = LinkData(linkFlags);
|
||||
U32 navSize;
|
||||
stream->read(&navSize);
|
||||
mNavSize = (AINavigation::NavSize)(navSize);
|
||||
// end enums
|
||||
stream->read(&(mFlocking.mChance));
|
||||
stream->read(&(mFlocking.mMin));
|
||||
stream->read(&(mFlocking.mMax));
|
||||
stream->read(&(mFlocking.mSideStep));
|
||||
#endif // TORQUE_NAVIGATION_ENABLED
|
||||
};
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
IMPLEMENT_CO_DATABLOCK_V1(AIPlayerControllerData);
|
||||
void AIPlayerControllerData::resolvePitch(AIController* obj, Point3F location, Move* movePtr)
|
||||
{
|
||||
Player* po = dynamic_cast<Player*>(obj->getAIInfo()->mObj.getPointer());
|
||||
if (!po) return;//not a player
|
||||
|
||||
if (obj->getAim() || obj->mMovement.mMoveState != AIController::ModeStop)
|
||||
{
|
||||
// Next do pitch.
|
||||
if (!obj->getAim())
|
||||
{
|
||||
// Level out if were just looking at our next way point.
|
||||
Point3F headRotation = po->getHeadRotation();
|
||||
movePtr->pitch = -headRotation.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
F32 xDiff = obj->mMovement.mAimLocation.x - location.x;
|
||||
F32 yDiff = obj->mMovement.mAimLocation.y - location.y;
|
||||
// This should be adjusted to run from the
|
||||
// eye point to the object's center position. Though this
|
||||
// works well enough for now.
|
||||
F32 vertDist = obj->mMovement.mAimLocation.z - location.z;
|
||||
F32 horzDist = mSqrt(xDiff * xDiff + yDiff * yDiff);
|
||||
F32 newPitch = mAtan2(horzDist, vertDist) - (M_PI_F / 2.0f);
|
||||
if (mFabs(newPitch) > 0.01f)
|
||||
{
|
||||
Point3F headRotation = po->getHeadRotation();
|
||||
movePtr->pitch = newPitch - headRotation.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Level out if we're not doing anything else
|
||||
Point3F headRotation = po->getHeadRotation();
|
||||
movePtr->pitch = -headRotation.x;
|
||||
}
|
||||
}
|
||||
|
||||
void AIPlayerControllerData::resolveTriggerState(AIController* obj, Move* movePtr)
|
||||
{
|
||||
Parent::resolveTriggerState(obj, movePtr);
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
if (obj->getNav()->mJump == AINavigation::Now)
|
||||
{
|
||||
movePtr->trigger[2] = true;
|
||||
obj->getNav()->mJump = AINavigation::None;
|
||||
}
|
||||
else if (obj->getNav()->mJump == AINavigation::Ledge)
|
||||
{
|
||||
// If we're not touching the ground, jump!
|
||||
RayInfo info;
|
||||
if (!obj->getAIInfo()->mObj->getContainer()->castRay(obj->getAIInfo()->getPosition(), obj->getAIInfo()->getPosition() - Point3F(0, 0, 0.4f), StaticShapeObjectType, &info))
|
||||
{
|
||||
movePtr->trigger[2] = true;
|
||||
obj->getNav()->mJump = AINavigation::None;
|
||||
}
|
||||
}
|
||||
#endif // TORQUE_NAVIGATION_ENABLED
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
IMPLEMENT_CO_DATABLOCK_V1(AIWheeledVehicleControllerData);
|
||||
|
||||
void AIWheeledVehicleControllerData::resolveYaw(AIController* obj, Point3F location, Move* movePtr)
|
||||
{
|
||||
if (obj->mMovement.mMoveState < AIController::ModeSlowing) return;
|
||||
WheeledVehicle* wvo = dynamic_cast<WheeledVehicle*>(obj->getAIInfo()->mObj.getPointer());
|
||||
if (!wvo)
|
||||
{
|
||||
//cover the case of a connection controling an object in turn controlling another
|
||||
if (obj->getAIInfo()->mObj->getObjectMount())
|
||||
wvo = dynamic_cast<WheeledVehicle*>(obj->getAIInfo()->mObj->getObjectMount());
|
||||
}
|
||||
if (!wvo) return;//not a WheeledVehicle
|
||||
|
||||
F32 lastYaw = wvo->getSteering().x;
|
||||
|
||||
Point3F right = wvo->getTransform().getRightVector();
|
||||
right.normalize();
|
||||
Point3F aimLoc = obj->mMovement.mAimLocation;
|
||||
|
||||
// Get the AI to Target vector and normalize it.
|
||||
Point3F toTarg = (location + wvo->getVelocity() * TickSec) - aimLoc;
|
||||
toTarg.normalize();
|
||||
|
||||
F32 dotYaw = -mDot(right, toTarg);
|
||||
movePtr->yaw = -lastYaw;
|
||||
|
||||
VehicleData* vd = (VehicleData*)(wvo->getDataBlock());
|
||||
F32 maxSteeringAngle = vd->maxSteeringAngle;
|
||||
|
||||
if (mFabs(dotYaw) > maxSteeringAngle*1.5f)
|
||||
dotYaw *= -1.0f;
|
||||
|
||||
if (dotYaw > maxSteeringAngle) dotYaw = maxSteeringAngle;
|
||||
if (dotYaw < -maxSteeringAngle) dotYaw = -maxSteeringAngle;
|
||||
|
||||
if (mFabs(dotYaw) > 0.05f)
|
||||
movePtr->yaw = dotYaw - lastYaw;
|
||||
};
|
||||
|
||||
void AIWheeledVehicleControllerData::resolveSpeed(AIController* obj, Point3F location, Move* movePtr)
|
||||
{
|
||||
if (obj->mMovement.mMoveState < AIController::ModeSlowing) return;
|
||||
WheeledVehicle* wvo = dynamic_cast<WheeledVehicle*>(obj->getAIInfo()->mObj.getPointer());
|
||||
if (!wvo)
|
||||
{
|
||||
//cover the case of a connection controling an object in turn controlling another
|
||||
if (obj->getAIInfo()->mObj->getObjectMount())
|
||||
wvo = dynamic_cast<WheeledVehicle*>(obj->getAIInfo()->mObj->getObjectMount());
|
||||
}
|
||||
if (!wvo) return;//not a WheeledVehicle
|
||||
|
||||
Parent::resolveSpeed(obj, location, movePtr);
|
||||
|
||||
VehicleData* db = static_cast<VehicleData *>(wvo->getDataBlock());
|
||||
movePtr->x = 0;
|
||||
movePtr->y *= mMax((db->maxSteeringAngle-mFabs(movePtr->yaw) / db->maxSteeringAngle),0.75f);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
IMPLEMENT_CO_DATABLOCK_V1(AIFlyingVehicleControllerData);
|
||||
|
||||
void AIFlyingVehicleControllerData::initPersistFields()
|
||||
{
|
||||
docsURL;
|
||||
addGroup("AI");
|
||||
|
||||
addFieldV("FlightFloor", TypeRangedF32, Offset(mFlightFloor, AIFlyingVehicleControllerData), &CommonValidators::F32Range,
|
||||
"@brief Min height we can target.");
|
||||
|
||||
addFieldV("FlightCeiling", TypeRangedF32, Offset(mFlightCeiling, AIFlyingVehicleControllerData), &CommonValidators::F32Range,
|
||||
"@brief Max height we can target.");
|
||||
|
||||
endGroup("AI");
|
||||
|
||||
Parent::initPersistFields();
|
||||
}
|
||||
|
||||
AIFlyingVehicleControllerData::AIFlyingVehicleControllerData(const AIFlyingVehicleControllerData& other, bool temp_clone) : AIControllerData(other, temp_clone)
|
||||
{
|
||||
mFlightCeiling = other.mFlightCeiling;
|
||||
mFlightFloor = other.mFlightFloor;
|
||||
resolveYawPtr.bind(this, &AIFlyingVehicleControllerData::resolveYaw);
|
||||
resolvePitchPtr.bind(this, &AIFlyingVehicleControllerData::resolvePitch);
|
||||
resolveSpeedPtr.bind(this, &AIFlyingVehicleControllerData::resolveSpeed);
|
||||
}
|
||||
|
||||
void AIFlyingVehicleControllerData::resolveYaw(AIController* obj, Point3F location, Move* movePtr)
|
||||
{
|
||||
if (obj->mMovement.mMoveState < AIController::ModeSlowing) return;
|
||||
FlyingVehicle* fvo = dynamic_cast<FlyingVehicle*>(obj->getAIInfo()->mObj.getPointer());
|
||||
if (!fvo)
|
||||
{
|
||||
//cover the case of a connection controling an object in turn controlling another
|
||||
if (obj->getAIInfo()->mObj->getObjectMount())
|
||||
fvo = dynamic_cast<FlyingVehicle*>(obj->getAIInfo()->mObj->getObjectMount());
|
||||
}
|
||||
if (!fvo) return;//not a FlyingVehicle
|
||||
|
||||
Point3F right = fvo->getTransform().getRightVector();
|
||||
right.normalize();
|
||||
|
||||
// Get the Target to AI vector and normalize it.
|
||||
Point3F aimLoc = obj->mMovement.mAimLocation;
|
||||
aimLoc.z = mClampF(aimLoc.z, mFlightFloor, mFlightCeiling);
|
||||
Point3F toTarg = (location + fvo->getVelocity() * TickSec) - aimLoc;
|
||||
toTarg.normalize();
|
||||
|
||||
movePtr->yaw = 0;
|
||||
F32 dotYaw = -mDot(right, toTarg);
|
||||
if (mFabs(dotYaw) > 0.05f)
|
||||
movePtr->yaw = dotYaw;
|
||||
};
|
||||
|
||||
void AIFlyingVehicleControllerData::resolvePitch(AIController* obj, Point3F location, Move* movePtr)
|
||||
{
|
||||
if (obj->mMovement.mMoveState < AIController::ModeSlowing) return;
|
||||
FlyingVehicle* fvo = dynamic_cast<FlyingVehicle*>(obj->getAIInfo()->mObj.getPointer());
|
||||
if (!fvo)
|
||||
{
|
||||
//cover the case of a connection controling an object in turn controlling another
|
||||
if (obj->getAIInfo()->mObj->getObjectMount())
|
||||
fvo = dynamic_cast<FlyingVehicle*>(obj->getAIInfo()->mObj->getObjectMount());
|
||||
}
|
||||
if (!fvo) return;//not a FlyingVehicle
|
||||
|
||||
Point3F up = fvo->getTransform().getUpVector();
|
||||
up.normalize();
|
||||
|
||||
|
||||
// Get the Target to AI vector and normalize it.
|
||||
Point3F aimLoc = obj->mMovement.mAimLocation;
|
||||
aimLoc.z = mClampF(aimLoc.z, mFlightFloor, mFlightCeiling);
|
||||
Point3F toTarg = (location + fvo->getVelocity() * TickSec) - aimLoc;
|
||||
toTarg.normalize();
|
||||
|
||||
movePtr->pitch = 0.0f;
|
||||
F32 dotPitch = mDot(up, toTarg);
|
||||
if (mFabs(dotPitch) > 0.05f)
|
||||
movePtr->pitch = dotPitch * M_2PI_F;
|
||||
|
||||
}
|
||||
|
||||
void AIFlyingVehicleControllerData::resolveSpeed(AIController* obj, Point3F location, Move* movePtr)
|
||||
{
|
||||
if (obj->mMovement.mMoveState < AIController::ModeSlowing) return;
|
||||
FlyingVehicle* fvo = dynamic_cast<FlyingVehicle*>(obj->getAIInfo()->mObj.getPointer());
|
||||
if (!fvo)
|
||||
{
|
||||
//cover the case of a connection controling an object in turn controlling another
|
||||
if (obj->getAIInfo()->mObj->getObjectMount())
|
||||
fvo = dynamic_cast<FlyingVehicle*>(obj->getAIInfo()->mObj->getObjectMount());
|
||||
}
|
||||
if (!fvo) return;//not a FlyingVehicle
|
||||
|
||||
movePtr->x = 0;
|
||||
movePtr->y = obj->mMovement.mMoveSpeed;
|
||||
}
|
||||
247
Engine/source/T3D/AI/AIController.h
Normal file
247
Engine/source/T3D/AI/AIController.h
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef _AICONTROLLER_H_
|
||||
#define _AICONTROLLER_H_
|
||||
#include "navigation/coverPoint.h"
|
||||
#include "AIInfo.h"
|
||||
#include "AIGoal.h"
|
||||
#include "AIAimTarget.h"
|
||||
#include "AICover.h"
|
||||
#include "AINavigation.h"
|
||||
class AIControllerData;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
class AIController : public SimObject {
|
||||
|
||||
typedef SimObject Parent;
|
||||
|
||||
public:
|
||||
AIControllerData* mControllerData;
|
||||
protected:
|
||||
static bool setControllerDataProperty(void* object, const char* index, const char* data);
|
||||
public:
|
||||
enum MoveState {
|
||||
ModeStop, // AI has stopped moving.
|
||||
ModeStuck, // AI is stuck, but wants to move.
|
||||
ModeSlowing, // AI is slowing down as it reaches it's destination.
|
||||
ModeMove, // AI is currently moving.
|
||||
ModeReverse // AI is reversing
|
||||
};
|
||||
|
||||
private:
|
||||
AIInfo*mAIInfo;
|
||||
public:
|
||||
void setAIInfo(SimObjectPtr<SceneObject> objIn, F32 rad = 0.0f) { delete(mAIInfo); mAIInfo = new AIInfo(this, objIn, rad); }
|
||||
AIInfo* getAIInfo() { return mAIInfo; }
|
||||
private:
|
||||
AIGoal* mGoal;
|
||||
public:
|
||||
void setGoal(AIInfo* targ);
|
||||
void setGoal(Point3F loc, F32 rad = 0.0f);
|
||||
void setGoal(SimObjectPtr<SceneObject> objIn, F32 rad = 0.0f);
|
||||
AIGoal* getGoal() { return mGoal; }
|
||||
void clearGoal() { SAFE_DELETE(mGoal); }
|
||||
private:
|
||||
AIAimTarget* mAimTarget;
|
||||
public:
|
||||
void setAim(Point3F loc, F32 rad = 0.0f, Point3F offset = Point3F(0.0f, 0.0f, 0.0f));
|
||||
void setAim(SimObjectPtr<SceneObject> objIn, F32 rad = 0.0f, Point3F offset = Point3F(0.0f, 0.0f, 0.0f));
|
||||
AIAimTarget* getAim() { return mAimTarget; }
|
||||
void clearAim() { SAFE_DELETE(mAimTarget); }
|
||||
private:
|
||||
AICover* mCover;
|
||||
public:
|
||||
void setCover(Point3F loc, F32 rad = 0.0f) { delete(mCover); mCover = new AICover(this, loc, rad); }
|
||||
void setCover(SimObjectPtr<SceneObject> objIn, F32 rad = 0.0f) { delete(mCover); mCover = new AICover(this, objIn, rad); }
|
||||
AICover* getCover() { return mCover; }
|
||||
bool findCover(const Point3F& from, F32 radius);
|
||||
void clearCover();
|
||||
|
||||
// Utility Methods
|
||||
void throwCallback(const char* name);
|
||||
AINavigation* mNav;
|
||||
AINavigation* getNav() { return mNav; };
|
||||
struct Movement
|
||||
{
|
||||
AIController* mControllerRef;
|
||||
AIController* getCtrl() { return mControllerRef; };
|
||||
MoveState mMoveState;
|
||||
F32 mMoveSpeed = 1.0;
|
||||
void setMoveSpeed(F32 speed) { mMoveSpeed = speed; };
|
||||
F32 getMoveSpeed() { return mMoveSpeed; };
|
||||
bool mMoveSlowdown; // Slowdown as we near the destination
|
||||
Point3F mLastLocation; // For stuck check
|
||||
S32 mMoveStuckTestCountdown; // The current countdown until at AI starts to check if it is stuck
|
||||
Point3F mAimLocation;
|
||||
// move triggers
|
||||
bool mMoveTriggers[MaxTriggerKeys];
|
||||
void stopMove();
|
||||
void onStuck();
|
||||
} mMovement;
|
||||
|
||||
struct TriggerState
|
||||
{
|
||||
AIController* mControllerRef;
|
||||
AIController* getCtrl() { return mControllerRef; };
|
||||
bool mMoveTriggers[MaxTriggerKeys];
|
||||
// Trigger sets/gets
|
||||
void setMoveTrigger(U32 slot, const bool isSet = true);
|
||||
bool getMoveTrigger(U32 slot) const;
|
||||
void clearMoveTriggers();
|
||||
} mTriggerState;
|
||||
bool getAIMove(Move* move);
|
||||
|
||||
static void initPersistFields();
|
||||
AIController()
|
||||
{
|
||||
for (S32 i = 0; i < MaxTriggerKeys; i++)
|
||||
mTriggerState.mMoveTriggers[i] = false;
|
||||
|
||||
mMovement.mControllerRef = this;
|
||||
mTriggerState.mControllerRef = this;
|
||||
mControllerData = NULL;
|
||||
mAIInfo = new AIInfo(this);
|
||||
mNav = new AINavigation(this);
|
||||
mGoal = NULL;
|
||||
mAimTarget = NULL;
|
||||
mCover = NULL;
|
||||
mMovement.mMoveState = ModeStop;
|
||||
};
|
||||
~AIController()
|
||||
{
|
||||
SAFE_DELETE(mAIInfo);
|
||||
SAFE_DELETE(mNav);
|
||||
clearGoal();
|
||||
clearAim();
|
||||
clearCover();
|
||||
}
|
||||
DECLARE_CONOBJECT(AIController);
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
class AIControllerData : public SimDataBlock {
|
||||
|
||||
typedef SimDataBlock Parent;
|
||||
|
||||
public:
|
||||
|
||||
AIControllerData();
|
||||
AIControllerData(const AIControllerData&, bool = false);
|
||||
~AIControllerData() {};
|
||||
void packData(BitStream* stream) override;
|
||||
void unpackData(BitStream* stream) override;
|
||||
static void initPersistFields();
|
||||
DECLARE_CONOBJECT(AIControllerData);
|
||||
|
||||
F32 mMoveTolerance; // Distance from destination point before we stop
|
||||
F32 mFollowTolerance; // Distance from destination object before we stop
|
||||
F32 mAttackRadius; // Distance to trigger weaponry calcs
|
||||
S32 mMoveStuckTestDelay; // The number of ticks to wait before checking if the AI is stuck
|
||||
F32 mMoveStuckTolerance; // Distance tolerance on stuck check
|
||||
F32 mHeightTolerance; // how high above the navmesh are we before we stop trying to repath
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
struct Flocking {
|
||||
U32 mChance; // chance of flocking
|
||||
F32 mMin; // min flocking separation distance
|
||||
F32 mMax; // max flocking clustering distance
|
||||
F32 mSideStep; // Distance from destination before we stop moving out of the way
|
||||
} mFlocking;
|
||||
|
||||
/// Types of link we can use.
|
||||
LinkData mLinkTypes;
|
||||
AINavigation::NavSize mNavSize;
|
||||
#endif
|
||||
Delegate<void(AIController* obj, Point3F location, Move* movePtr)> resolveYawPtr;
|
||||
void resolveYaw(AIController* obj, Point3F location, Move* movePtr);
|
||||
|
||||
Delegate<void(AIController* obj, Point3F location, Move* movePtr)> resolvePitchPtr;
|
||||
void resolvePitch(AIController* obj, Point3F location, Move* movePtr) {};
|
||||
|
||||
Delegate<void(AIController* obj, Point3F location, Move* movePtr)> resolveRollPtr;
|
||||
void resolveRoll(AIController* obj, Point3F location, Move* movePtr);
|
||||
|
||||
Delegate<void(AIController* obj, Point3F location, Move* movePtr)> resolveSpeedPtr;
|
||||
void resolveSpeed(AIController* obj, Point3F location, Move* movePtr);
|
||||
|
||||
Delegate<void(AIController* obj, Move* movePtr)> resolveTriggerStatePtr;
|
||||
void resolveTriggerState(AIController* obj, Move* movePtr);
|
||||
|
||||
Delegate<void(AIController* obj)> resolveStuckPtr;
|
||||
void resolveStuck(AIController* obj);
|
||||
};
|
||||
|
||||
class AIPlayerControllerData : public AIControllerData
|
||||
{
|
||||
typedef AIControllerData Parent;
|
||||
|
||||
public:
|
||||
AIPlayerControllerData()
|
||||
{
|
||||
resolvePitchPtr.bind(this, &AIPlayerControllerData::resolvePitch);
|
||||
resolveTriggerStatePtr.bind(this, &AIPlayerControllerData::resolveTriggerState);
|
||||
}
|
||||
void resolvePitch(AIController* obj, Point3F location, Move* movePtr);
|
||||
void resolveTriggerState(AIController* obj, Move* movePtr);
|
||||
DECLARE_CONOBJECT(AIPlayerControllerData);
|
||||
};
|
||||
|
||||
class AIWheeledVehicleControllerData : public AIControllerData
|
||||
{
|
||||
typedef AIControllerData Parent;
|
||||
|
||||
public:
|
||||
AIWheeledVehicleControllerData()
|
||||
{
|
||||
resolveYawPtr.bind(this, &AIWheeledVehicleControllerData::resolveYaw);
|
||||
resolveSpeedPtr.bind(this, &AIWheeledVehicleControllerData::resolveSpeed);
|
||||
mHeightTolerance = 2.0f;
|
||||
}
|
||||
void resolveYaw(AIController* obj, Point3F location, Move* movePtr);
|
||||
void resolveSpeed(AIController* obj, Point3F location, Move* movePtr);
|
||||
DECLARE_CONOBJECT(AIWheeledVehicleControllerData);
|
||||
};
|
||||
|
||||
class AIFlyingVehicleControllerData : public AIControllerData
|
||||
{
|
||||
typedef AIControllerData Parent;
|
||||
|
||||
F32 mFlightFloor;
|
||||
F32 mFlightCeiling;
|
||||
public:
|
||||
AIFlyingVehicleControllerData()
|
||||
{
|
||||
resolveYawPtr.bind(this, &AIFlyingVehicleControllerData::resolveYaw);
|
||||
resolvePitchPtr.bind(this, &AIFlyingVehicleControllerData::resolvePitch);
|
||||
resolveSpeedPtr.bind(this, &AIFlyingVehicleControllerData::resolveSpeed);
|
||||
mHeightTolerance = 200.0f;
|
||||
mFlightCeiling = 200.0f;
|
||||
mFlightFloor = 1.0;
|
||||
}
|
||||
AIFlyingVehicleControllerData(const AIFlyingVehicleControllerData&, bool = false);
|
||||
static void initPersistFields();
|
||||
void resolveYaw(AIController* obj, Point3F location, Move* movePtr);
|
||||
void resolveSpeed(AIController* obj, Point3F location, Move* movePtr);
|
||||
void resolvePitch(AIController* obj, Point3F location, Move* movePtr);
|
||||
|
||||
DECLARE_CONOBJECT(AIFlyingVehicleControllerData);
|
||||
};
|
||||
#endif //_AICONTROLLER_H_
|
||||
108
Engine/source/T3D/AI/AICover.cpp
Normal file
108
Engine/source/T3D/AI/AICover.cpp
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "AICover.h"
|
||||
#include "AIController.h"
|
||||
|
||||
struct CoverSearch
|
||||
{
|
||||
Point3F loc;
|
||||
Point3F from;
|
||||
F32 dist;
|
||||
F32 best;
|
||||
CoverPoint* point;
|
||||
CoverSearch() : loc(0, 0, 0), from(0, 0, 0)
|
||||
{
|
||||
best = -FLT_MAX;
|
||||
point = NULL;
|
||||
dist = FLT_MAX;
|
||||
}
|
||||
};
|
||||
|
||||
static void findCoverCallback(SceneObject* obj, void* key)
|
||||
{
|
||||
CoverPoint* p = dynamic_cast<CoverPoint*>(obj);
|
||||
if (!p || p->isOccupied())
|
||||
return;
|
||||
CoverSearch* s = static_cast<CoverSearch*>(key);
|
||||
Point3F dir = s->from - p->getPosition();
|
||||
dir.normalizeSafe();
|
||||
// Score first based on angle of cover point to enemy.
|
||||
F32 score = mDot(p->getNormal(), dir);
|
||||
// Score also based on distance from seeker.
|
||||
score -= (p->getPosition() - s->loc).len() / s->dist;
|
||||
// Finally, consider cover size.
|
||||
score += (p->getSize() + 1) / CoverPoint::NumSizes;
|
||||
score *= p->getQuality();
|
||||
if (score > s->best)
|
||||
{
|
||||
s->best = score;
|
||||
s->point = p;
|
||||
}
|
||||
}
|
||||
|
||||
bool AIController::findCover(const Point3F& from, F32 radius)
|
||||
{
|
||||
if (radius <= 0)
|
||||
return false;
|
||||
|
||||
// Create a search state.
|
||||
CoverSearch s;
|
||||
s.loc = getAIInfo()->getPosition();
|
||||
s.dist = radius;
|
||||
// Direction we seek cover FROM.
|
||||
s.from = from;
|
||||
|
||||
// Find cover points.
|
||||
Box3F box(radius * 2.0f);
|
||||
box.setCenter(getAIInfo()->getPosition());
|
||||
getAIInfo()->mObj->getContainer()->findObjects(box, MarkerObjectType, findCoverCallback, &s);
|
||||
|
||||
// Go to cover!
|
||||
if (s.point)
|
||||
{
|
||||
// Calling setPathDestination clears cover...
|
||||
bool foundPath = getNav()->setPathDestination(s.point->getPosition());
|
||||
setCover(s.point);
|
||||
s.point->setOccupied(true);
|
||||
return foundPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
DefineEngineMethod(AIController, findCover, S32, (Point3F from, F32 radius), ,
|
||||
"@brief Tells the AI to find cover nearby.\n\n"
|
||||
|
||||
"@param from Location to find cover from (i.e., enemy position).\n"
|
||||
"@param radius Distance to search for cover.\n"
|
||||
"@return Cover point ID if cover was found, -1 otherwise.\n\n")
|
||||
{
|
||||
if (object->findCover(from, radius))
|
||||
{
|
||||
CoverPoint* cover = object->getCover()->mCoverPoint.getObject();
|
||||
return cover ? cover->getId() : -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
41
Engine/source/T3D/AI/AICover.h
Normal file
41
Engine/source/T3D/AI/AICover.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef _AICOVER_H_
|
||||
#define _AICOVER_H_
|
||||
|
||||
#include "AIInfo.h"
|
||||
#include "navigation/coverPoint.h"
|
||||
|
||||
|
||||
|
||||
struct AICover : public AIInfo
|
||||
{
|
||||
typedef AIInfo Parent;
|
||||
/// Pointer to a cover point.
|
||||
SimObjectPtr<CoverPoint> mCoverPoint;
|
||||
AICover() = delete;
|
||||
AICover(AIController* controller) : Parent(controller) {};
|
||||
AICover(AIController* controller, SimObjectPtr<SceneObject> objIn, F32 radIn) : Parent(controller, objIn, radIn) { mCoverPoint = dynamic_cast<CoverPoint*>(objIn.getPointer());};
|
||||
AICover(AIController* controller, Point3F pointIn, F32 radIn) : Parent(controller, pointIn, radIn) {};
|
||||
};
|
||||
|
||||
#endif
|
||||
24
Engine/source/T3D/AI/AIGoal.cpp
Normal file
24
Engine/source/T3D/AI/AIGoal.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "AIGoal.h"
|
||||
#include "AIController.h"
|
||||
36
Engine/source/T3D/AI/AIGoal.h
Normal file
36
Engine/source/T3D/AI/AIGoal.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef _AIGOAL_H_
|
||||
#define _AIGOAL_H_
|
||||
|
||||
#include "AIInfo.h"
|
||||
|
||||
struct AIGoal : public AIInfo
|
||||
{
|
||||
typedef AIInfo Parent;
|
||||
bool mInRange, mInFiringRange;
|
||||
AIGoal() = delete;
|
||||
AIGoal(AIController* controller) : Parent(controller) { mInRange = mInFiringRange = false; };
|
||||
AIGoal(AIController* controller, SimObjectPtr<SceneObject> objIn, F32 radIn) : Parent(controller, objIn, radIn) { mInRange = mInFiringRange = false; };
|
||||
AIGoal(AIController* controller, Point3F pointIn, F32 radIn) : Parent(controller, pointIn, radIn) { mInRange = mInFiringRange = false; };
|
||||
};
|
||||
#endif
|
||||
81
Engine/source/T3D/AI/AIInfo.cpp
Normal file
81
Engine/source/T3D/AI/AIInfo.cpp
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "AIInfo.h"
|
||||
#include "AIController.h"
|
||||
|
||||
AIInfo::AIInfo(AIController* controller)
|
||||
{
|
||||
mControllerRef = controller;
|
||||
mObj = NULL;
|
||||
mPosition = mLastPos = Point3F(0.0f, 0.0f, 0.0f);
|
||||
mRadius = 0.0f;
|
||||
mPosSet = false;
|
||||
};
|
||||
|
||||
AIInfo::AIInfo(AIController* controller, SimObjectPtr<SceneObject> objIn, F32 radIn)
|
||||
{
|
||||
mControllerRef = controller;
|
||||
mObj = objIn;
|
||||
mPosition = mLastPos = objIn->getPosition();
|
||||
mRadius = radIn;
|
||||
mPosSet = false;
|
||||
if (radIn == 0.0f)
|
||||
mRadius = mMax(objIn->getObjBox().len_x(), objIn->getObjBox().len_y()) * 0.5;
|
||||
};
|
||||
|
||||
AIInfo::AIInfo(AIController* controller, Point3F pointIn, F32 radIn)
|
||||
{
|
||||
mControllerRef = controller;
|
||||
mObj = NULL;
|
||||
mPosition = mLastPos = pointIn;
|
||||
mRadius = radIn;
|
||||
mPosSet = true;
|
||||
};
|
||||
|
||||
Point3F AIInfo::getPosition(bool doCastray)
|
||||
{
|
||||
Point3F pos = (mObj.isValid()) ? mObj->getPosition() : mPosition;
|
||||
if (doCastray)
|
||||
{
|
||||
RayInfo info;
|
||||
if (gServerContainer.castRay(pos, pos - Point3F(0, 0, getCtrl()->mControllerData->mHeightTolerance), StaticShapeObjectType, &info))
|
||||
{
|
||||
pos = info.point;
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
F32 AIInfo::getDist()
|
||||
{
|
||||
AIInfo* controlObj = getCtrl()->getAIInfo();
|
||||
Point3F targPos = getPosition();
|
||||
|
||||
if (mFabs(targPos.z - controlObj->getPosition().z) < getCtrl()->mControllerData->mHeightTolerance)
|
||||
targPos.z = controlObj->getPosition().z;
|
||||
|
||||
F32 ret = VectorF(controlObj->getPosition() - targPos).len();
|
||||
ret -= controlObj->mRadius + mRadius;
|
||||
return ret;
|
||||
}
|
||||
46
Engine/source/T3D/AI/AIInfo.h
Normal file
46
Engine/source/T3D/AI/AIInfo.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef _AIINFO_H_
|
||||
#define _AIINFO_H_
|
||||
|
||||
#ifndef _SCENEOBJECT_H_
|
||||
#include "scene/sceneObject.h"
|
||||
#endif
|
||||
|
||||
class AIController;
|
||||
struct AIInfo
|
||||
{
|
||||
AIController* mControllerRef;
|
||||
AIController* getCtrl() { return mControllerRef; };
|
||||
void setCtrl(AIController* controller) { mControllerRef = controller; };
|
||||
SimObjectPtr<SceneObject> mObj;
|
||||
Point3F mPosition, mLastPos;
|
||||
bool mPosSet;
|
||||
F32 mRadius;
|
||||
Point3F getPosition(bool doCastray = false);
|
||||
F32 getDist();
|
||||
AIInfo() = delete;
|
||||
AIInfo(AIController* controller);
|
||||
AIInfo(AIController* controller, SimObjectPtr<SceneObject> objIn, F32 radIn = 0.0f);
|
||||
AIInfo(AIController* controller,Point3F pointIn, F32 radIn = 0.0f);
|
||||
};
|
||||
#endif
|
||||
574
Engine/source/T3D/AI/AINavigation.cpp
Normal file
574
Engine/source/T3D/AI/AINavigation.cpp
Normal file
|
|
@ -0,0 +1,574 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "AINavigation.h"
|
||||
#include "AIController.h"
|
||||
#include "T3D/shapeBase.h"
|
||||
|
||||
static U32 sAILoSMask = TerrainObjectType | StaticShapeObjectType | StaticObjectType | AIObjectType;
|
||||
|
||||
AINavigation::AINavigation(AIController* controller)
|
||||
{
|
||||
mControllerRef = controller;
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
mJump = None;
|
||||
mNavSize = Regular;
|
||||
#endif
|
||||
}
|
||||
|
||||
AINavigation::~AINavigation()
|
||||
{
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
clearPath();
|
||||
clearFollow();
|
||||
#endif
|
||||
}
|
||||
|
||||
void AINavigation::setMoveDestination(const Point3F& location, bool slowdown)
|
||||
{
|
||||
mMoveDestination = location;
|
||||
getCtrl()->mMovement.mMoveState = AIController::ModeMove;
|
||||
getCtrl()->mMovement.mMoveSlowdown = slowdown;
|
||||
getCtrl()->mMovement.mMoveStuckTestCountdown = getCtrl()->mControllerData->mMoveStuckTestDelay;
|
||||
}
|
||||
|
||||
bool AINavigation::setPathDestination(const Point3F& pos, bool replace)
|
||||
{
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
if (replace)
|
||||
getCtrl()->setGoal(pos, getCtrl()->mControllerData->mMoveTolerance);
|
||||
|
||||
if (!mNavMesh)
|
||||
updateNavMesh();
|
||||
|
||||
// If we can't find a mesh, just move regularly.
|
||||
if (!mNavMesh)
|
||||
{
|
||||
//setMoveDestination(pos);
|
||||
getCtrl()->throwCallback("onPathFailed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a new path.
|
||||
NavPath* path = new NavPath();
|
||||
|
||||
path->mMesh = mNavMesh;
|
||||
path->mFrom = getCtrl()->getAIInfo()->getPosition(true);
|
||||
path->mTo = getCtrl()->getGoal()->getPosition(true);
|
||||
path->mFromSet = path->mToSet = true;
|
||||
path->mAlwaysRender = true;
|
||||
path->mLinkTypes = getCtrl()->mControllerData->mLinkTypes;
|
||||
path->mXray = true;
|
||||
// Paths plan automatically upon being registered.
|
||||
if (!path->registerObject())
|
||||
{
|
||||
delete path;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path->success())
|
||||
{
|
||||
// Clear any current path we might have.
|
||||
clearPath();
|
||||
getCtrl()->clearCover();
|
||||
// Store new path.
|
||||
mPathData.path = path;
|
||||
mPathData.owned = true;
|
||||
// Skip node 0, which we are currently standing on.
|
||||
moveToNode(1);
|
||||
getCtrl()->throwCallback("onPathSuccess");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just move normally if we can't path.
|
||||
//setMoveDestination(pos, true);
|
||||
//return;
|
||||
getCtrl()->throwCallback("onPathFailed");
|
||||
path->deleteObject();
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
setMoveDestination(pos, false);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
Point3F AINavigation::getPathDestination() const
|
||||
{
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
if (!mPathData.path.isNull())
|
||||
return mPathData.path->mTo;
|
||||
return Point3F(0, 0, 0);
|
||||
#else
|
||||
return getMoveDestination();
|
||||
#endif
|
||||
}
|
||||
void AINavigation::onReachDestination()
|
||||
{
|
||||
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
if (!getPath().isNull())
|
||||
{
|
||||
if (mPathData.index == getPath()->size() - 1)
|
||||
{
|
||||
// Handle looping paths.
|
||||
if (getPath()->mIsLooping)
|
||||
moveToNode(0);
|
||||
// Otherwise end path.
|
||||
else
|
||||
{
|
||||
clearPath();
|
||||
getCtrl()->throwCallback("onReachDestination");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
moveToNode(mPathData.index + 1);
|
||||
// Throw callback every time if we're on a looping path.
|
||||
//if(mPathData.path->mIsLooping)
|
||||
//throwCallback("onReachDestination");
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
getCtrl()->throwCallback("onReachDestination");
|
||||
getCtrl()->mMovement.mMoveState = AIController::ModeStop;
|
||||
}
|
||||
}
|
||||
|
||||
void AINavigation::followObject()
|
||||
{
|
||||
if (getCtrl()->getGoal()->getDist() < getCtrl()->mControllerData->mMoveTolerance)
|
||||
return;
|
||||
|
||||
if (setPathDestination(getCtrl()->getGoal()->getPosition(true)))
|
||||
{
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
getCtrl()->clearCover();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void AINavigation::followObject(SceneObject* obj, F32 radius)
|
||||
{
|
||||
getCtrl()->setGoal(obj, radius);
|
||||
followObject();
|
||||
}
|
||||
|
||||
void AINavigation::clearFollow()
|
||||
{
|
||||
getCtrl()->clearGoal();
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, setMoveDestination, void, (Point3F goal, bool slowDown), (true),
|
||||
"@brief Tells the AI to move to the location provided\n\n"
|
||||
|
||||
"@param goal Coordinates in world space representing location to move to.\n"
|
||||
"@param slowDown A boolean value. If set to true, the bot will slow down "
|
||||
"when it gets within 5-meters of its move destination. If false, the bot "
|
||||
"will stop abruptly when it reaches the move destination. By default, this is true.\n\n"
|
||||
|
||||
"@note Upon reaching a move destination, the bot will clear its move destination and "
|
||||
"calls to getMoveDestination will return \"0 0 0\"."
|
||||
|
||||
"@see getMoveDestination()\n")
|
||||
{
|
||||
object->getNav()->setMoveDestination(goal, slowDown);
|
||||
}
|
||||
|
||||
|
||||
DefineEngineMethod(AIController, getMoveDestination, Point3F, (), ,
|
||||
"@brief Get the AIPlayer's current destination.\n\n"
|
||||
|
||||
"@return Returns a point containing the \"x y z\" position "
|
||||
"of the AIPlayer's current move destination. If no move destination "
|
||||
"has yet been set, this returns \"0 0 0\"."
|
||||
|
||||
"@see setMoveDestination()\n")
|
||||
{
|
||||
return object->getNav()->getMoveDestination();
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, setPathDestination, bool, (Point3F goal), ,
|
||||
"@brief Tells the AI to find a path to the location provided\n\n"
|
||||
|
||||
"@param goal Coordinates in world space representing location to move to.\n"
|
||||
"@return True if a path was found.\n\n"
|
||||
|
||||
"@see getPathDestination()\n"
|
||||
"@see setMoveDestination()\n")
|
||||
{
|
||||
return object->getNav()->setPathDestination(goal, true);
|
||||
}
|
||||
|
||||
|
||||
DefineEngineMethod(AIController, getPathDestination, Point3F, (), ,
|
||||
"@brief Get the AIPlayer's current pathfinding destination.\n\n"
|
||||
|
||||
"@return Returns a point containing the \"x y z\" position "
|
||||
"of the AIPlayer's current path destination. If no path destination "
|
||||
"has yet been set, this returns \"0 0 0\"."
|
||||
|
||||
"@see setPathDestination()\n")
|
||||
{
|
||||
return object->getNav()->getPathDestination();
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, followObject, void, (SimObjectId obj, F32 radius), ,
|
||||
"@brief Tell the AIPlayer to follow another object.\n\n"
|
||||
|
||||
"@param obj ID of the object to follow.\n"
|
||||
"@param radius Maximum distance we let the target escape to.")
|
||||
{
|
||||
SceneObject* follow;
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
object->getNav()->clearPath();
|
||||
object->clearCover();
|
||||
#endif
|
||||
object->getNav()->clearFollow();
|
||||
|
||||
if (Sim::findObject(obj, follow))
|
||||
object->getNav()->followObject(follow, radius);
|
||||
}
|
||||
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
NavMesh* AINavigation::findNavMesh() const
|
||||
{
|
||||
GameBase* gbo = dynamic_cast<GameBase*>(mControllerRef->getAIInfo()->mObj.getPointer());
|
||||
// Search for NavMeshes that contain us entirely with the smallest possible
|
||||
// volume.
|
||||
NavMesh* mesh = NULL;
|
||||
SimSet* set = NavMesh::getServerSet();
|
||||
for (U32 i = 0; i < set->size(); i++)
|
||||
{
|
||||
NavMesh* m = static_cast<NavMesh*>(set->at(i));
|
||||
if (m->getWorldBox().isContained(gbo->getWorldBox()))
|
||||
{
|
||||
// Check that mesh size is appropriate.
|
||||
if (gbo->isMounted())
|
||||
{
|
||||
if (!m->mVehicles)
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((getNavSize() == Small && !m->mSmallCharacters) ||
|
||||
(getNavSize() == Regular && !m->mRegularCharacters) ||
|
||||
(getNavSize() == Large && !m->mLargeCharacters))
|
||||
continue;
|
||||
}
|
||||
if (!mesh || m->getWorldBox().getVolume() < mesh->getWorldBox().getVolume())
|
||||
mesh = m;
|
||||
}
|
||||
}
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void AINavigation::updateNavMesh()
|
||||
{
|
||||
GameBase* gbo = dynamic_cast<GameBase*>(mControllerRef->getAIInfo()->mObj.getPointer());
|
||||
NavMesh* old = mNavMesh;
|
||||
if (mNavMesh.isNull())
|
||||
mNavMesh = findNavMesh();
|
||||
else
|
||||
{
|
||||
if (!mNavMesh->getWorldBox().isContained(gbo->getWorldBox()))
|
||||
mNavMesh = findNavMesh();
|
||||
}
|
||||
// See if we need to update our path.
|
||||
if (mNavMesh != old && !mPathData.path.isNull())
|
||||
{
|
||||
setPathDestination(mPathData.path->mTo);
|
||||
}
|
||||
}
|
||||
|
||||
void AINavigation::moveToNode(S32 node)
|
||||
{
|
||||
if (mPathData.path.isNull())
|
||||
return;
|
||||
|
||||
// -1 is shorthand for 'last path node'.
|
||||
if (node == -1)
|
||||
node = mPathData.path->size() - 1;
|
||||
|
||||
// Consider slowing down on the last path node.
|
||||
setMoveDestination(mPathData.path->getNode(node), false);
|
||||
|
||||
// Check flags for this segment.
|
||||
if (mPathData.index)
|
||||
{
|
||||
U16 flags = mPathData.path->getFlags(node - 1);
|
||||
// Jump if we must.
|
||||
if (flags & LedgeFlag)
|
||||
mJump = Ledge;
|
||||
else if (flags & JumpFlag)
|
||||
mJump = Now;
|
||||
else
|
||||
// Catch pathing errors.
|
||||
mJump = None;
|
||||
}
|
||||
|
||||
// Store current index.
|
||||
mPathData.index = node;
|
||||
}
|
||||
|
||||
void AINavigation::repath()
|
||||
{
|
||||
// Ineffectual if we don't have a path, or are using someone else's.
|
||||
if (mPathData.path.isNull() || !mPathData.owned)
|
||||
return;
|
||||
|
||||
if (mRandI(0, 100) < getCtrl()->mControllerData->mFlocking.mChance && flock())
|
||||
{
|
||||
mPathData.path->mTo = mMoveDestination;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're following, get their position.
|
||||
mPathData.path->mTo = getCtrl()->getGoal()->getPosition(true);
|
||||
}
|
||||
|
||||
// Update from position and replan.
|
||||
mPathData.path->mFrom = getCtrl()->getAIInfo()->getPosition(true);
|
||||
mPathData.path->plan();
|
||||
|
||||
// Move to first node (skip start pos).
|
||||
moveToNode(1);
|
||||
}
|
||||
|
||||
|
||||
void AINavigation::followNavPath(NavPath* path)
|
||||
{
|
||||
// Get rid of our current path.
|
||||
clearPath();
|
||||
getCtrl()->clearCover();
|
||||
|
||||
// Follow new path.
|
||||
mPathData.path = path;
|
||||
mPathData.owned = false;
|
||||
// Start from 0 since we might not already be there.
|
||||
moveToNode(0);
|
||||
}
|
||||
|
||||
void AINavigation::clearPath()
|
||||
{
|
||||
// Only delete if we own the path.
|
||||
if (!mPathData.path.isNull() && mPathData.owned)
|
||||
mPathData.path->deleteObject();
|
||||
// Reset path data.
|
||||
mPathData = PathData();
|
||||
}
|
||||
|
||||
bool AINavigation::flock()
|
||||
{
|
||||
AIControllerData::Flocking flockingData = getCtrl()->mControllerData->mFlocking;
|
||||
SimObjectPtr<SceneObject> obj = getCtrl()->getAIInfo()->mObj;
|
||||
|
||||
obj->disableCollision();
|
||||
Point3F pos = obj->getBoxCenter();
|
||||
Point3F searchArea = Point3F(flockingData.mMin / 2, flockingData.mMax / 2, getCtrl()->getAIInfo()->mObj->getObjBox().maxExtents.z / 2);
|
||||
|
||||
F32 maxFlocksq = flockingData.mMax * flockingData.mMax;
|
||||
bool flocking = false;
|
||||
U32 found = 0;
|
||||
if (getCtrl()->getGoal())
|
||||
{
|
||||
Point3F dest = mMoveDestination;
|
||||
|
||||
if (getCtrl()->mMovement.mMoveState == AIController::ModeStuck)
|
||||
{
|
||||
Point3F shuffle = Point3F(mRandF() - 0.5, mRandF() - 0.5, 0);
|
||||
shuffle.normalize();
|
||||
dest += shuffle * flockingData.mMin;
|
||||
}
|
||||
|
||||
dest.z = pos.z;
|
||||
if ((pos - dest).len() > flockingData.mSideStep)
|
||||
{
|
||||
//find closest object
|
||||
SimpleQueryList sql;
|
||||
Box3F queryBox = Box3F(pos - searchArea, pos + searchArea);
|
||||
obj->getContainer()->findObjects(queryBox, AIObjectType, SimpleQueryList::insertionCallback, &sql);
|
||||
sql.mList.remove(obj);
|
||||
|
||||
Point3F avoidanceOffset = Point3F::Zero;
|
||||
|
||||
//avoid objects in the way
|
||||
RayInfo info;
|
||||
if (obj->getContainer()->castRay(pos, dest + Point3F(0, 0, obj->getObjBox().len_z() / 2), sAILoSMask, &info))
|
||||
{
|
||||
Point3F blockerOffset = (info.point - dest);
|
||||
blockerOffset.z = 0;
|
||||
avoidanceOffset += blockerOffset;
|
||||
}
|
||||
|
||||
//avoid bots that are too close
|
||||
for (U32 i = 0; i < sql.mList.size(); i++)
|
||||
{
|
||||
ShapeBase* other = dynamic_cast<ShapeBase*>(sql.mList[i]);
|
||||
Point3F objectCenter = other->getBoxCenter();
|
||||
|
||||
F32 sumRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin;
|
||||
F32 separation = getCtrl()->getAIInfo()->mRadius + other->getAIController()->getAIInfo()->mRadius;
|
||||
sumRad += separation;
|
||||
|
||||
Point3F offset = (pos - objectCenter);
|
||||
F32 offsetLensq = offset.lenSquared(); //square roots are expensive, so use squared val compares
|
||||
if ((flockingData.mMin > 0) && (offsetLensq < (sumRad * sumRad)))
|
||||
{
|
||||
other->disableCollision();
|
||||
if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask, &info))
|
||||
{
|
||||
found++;
|
||||
offset.normalizeSafe();
|
||||
offset *= sumRad + separation;
|
||||
avoidanceOffset += offset; //accumulate total group, move away from that
|
||||
}
|
||||
other->enableCollision();
|
||||
}
|
||||
}
|
||||
//if we don't have to worry about bumping into one another (nothing found lower than minFLock), see about grouping up
|
||||
if (found == 0)
|
||||
{
|
||||
for (U32 i = 0; i < sql.mList.size(); i++)
|
||||
{
|
||||
ShapeBase* other = static_cast<ShapeBase*>(sql.mList[i]);
|
||||
Point3F objectCenter = other->getBoxCenter();
|
||||
|
||||
F32 sumRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin;
|
||||
F32 separation = getCtrl()->getAIInfo()->mRadius + other->getAIController()->getAIInfo()->mRadius;
|
||||
sumRad += separation;
|
||||
|
||||
Point3F offset = (pos - objectCenter);
|
||||
if ((flockingData.mMin > 0) && ((sumRad * sumRad) < (maxFlocksq)))
|
||||
{
|
||||
other->disableCollision();
|
||||
if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask, &info))
|
||||
{
|
||||
found++;
|
||||
offset.normalizeSafe();
|
||||
offset *= sumRad + separation;
|
||||
avoidanceOffset -= offset; // subtract total group, move toward it
|
||||
}
|
||||
other->enableCollision();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found > 0)
|
||||
{
|
||||
avoidanceOffset.z = 0;
|
||||
avoidanceOffset.x = (mRandF() * avoidanceOffset.x) * 0.5 + avoidanceOffset.x * 0.75;
|
||||
avoidanceOffset.y = (mRandF() * avoidanceOffset.y) * 0.5 + avoidanceOffset.y * 0.75;
|
||||
if (avoidanceOffset.lenSquared() < (maxFlocksq))
|
||||
{
|
||||
dest += avoidanceOffset;
|
||||
}
|
||||
|
||||
//if we're not jumping...
|
||||
if (mJump == None)
|
||||
{
|
||||
dest.z = obj->getPosition().z;
|
||||
//make sure we don't run off a cliff
|
||||
Point3F zlen(0, 0, getCtrl()->mControllerData->mHeightTolerance);
|
||||
if (obj->getContainer()->castRay(dest + zlen, dest - zlen, TerrainObjectType | StaticShapeObjectType | StaticObjectType, &info))
|
||||
{
|
||||
if ((mMoveDestination - dest).len() > getCtrl()->mControllerData->mMoveTolerance)
|
||||
{
|
||||
mMoveDestination = dest;
|
||||
flocking = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
obj->enableCollision();
|
||||
return flocking;
|
||||
}
|
||||
|
||||
|
||||
DefineEngineMethod(AIController, followNavPath, void, (SimObjectId obj), ,
|
||||
"@brief Tell the AIPlayer to follow a path.\n\n"
|
||||
|
||||
"@param obj ID of a NavPath object for the character to follow.")
|
||||
{
|
||||
NavPath* path;
|
||||
if (Sim::findObject(obj, path))
|
||||
object->getNav()->followNavPath(path);
|
||||
}
|
||||
|
||||
|
||||
DefineEngineMethod(AIController, repath, void, (), ,
|
||||
"@brief Tells the AI to re-plan its path. Does nothing if the character "
|
||||
"has no path, or if it is following a mission path.\n\n")
|
||||
{
|
||||
object->getNav()->repath();
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, findNavMesh, S32, (), ,
|
||||
"@brief Get the NavMesh object this AIPlayer is currently using.\n\n"
|
||||
|
||||
"@return The ID of the NavPath object this character is using for "
|
||||
"pathfinding. This is determined by the character's location, "
|
||||
"navigation type and other factors. Returns -1 if no NavMesh is "
|
||||
"found.")
|
||||
{
|
||||
NavMesh* mesh = object->getNav()->getNavMesh();
|
||||
return mesh ? mesh->getId() : -1;
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, getNavMesh, S32, (), ,
|
||||
"@brief Return the NavMesh this AIPlayer is using to navigate.\n\n")
|
||||
{
|
||||
NavMesh* m = object->getNav()->getNavMesh();
|
||||
return m ? m->getId() : 0;
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, setNavSize, void, (const char* size), ,
|
||||
"@brief Set the size of NavMesh this character uses. One of \"Small\", \"Regular\" or \"Large\".")
|
||||
{
|
||||
if (!String::compare(size, "Small"))
|
||||
object->getNav()->setNavSize(AINavigation::Small);
|
||||
else if (!String::compare(size, "Regular"))
|
||||
object->getNav()->setNavSize(AINavigation::Regular);
|
||||
else if (!String::compare(size, "Large"))
|
||||
object->getNav()->setNavSize(AINavigation::Large);
|
||||
else
|
||||
Con::errorf("AIPlayer::setNavSize: no such size '%s'.", size);
|
||||
}
|
||||
|
||||
DefineEngineMethod(AIController, getNavSize, const char*, (), ,
|
||||
"@brief Return the size of NavMesh this character uses for pathfinding.")
|
||||
{
|
||||
switch (object->getNav()->getNavSize())
|
||||
{
|
||||
case AINavigation::Small:
|
||||
return "Small";
|
||||
case AINavigation::Regular:
|
||||
return "Regular";
|
||||
case AINavigation::Large:
|
||||
return "Large";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
105
Engine/source/T3D/AI/AINavigation.h
Normal file
105
Engine/source/T3D/AI/AINavigation.h
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef _AINAVIGATION_H_
|
||||
#define _AINAVIGATION_H_
|
||||
|
||||
#include "AIInfo.h"
|
||||
|
||||
#include "navigation/navPath.h"
|
||||
#include "navigation/navMesh.h"
|
||||
|
||||
class AIController;
|
||||
struct AINavigation
|
||||
{
|
||||
AIController* mControllerRef;
|
||||
AIController* getCtrl() { return mControllerRef; };
|
||||
|
||||
AINavigation() = delete;
|
||||
AINavigation(AIController* controller);
|
||||
~AINavigation();
|
||||
Point3F mMoveDestination;
|
||||
void setMoveDestination(const Point3F& location, bool slowdown);
|
||||
Point3F getMoveDestination() const { return mMoveDestination; };
|
||||
bool setPathDestination(const Point3F& pos, bool replace = false);
|
||||
Point3F getPathDestination() const;
|
||||
|
||||
void onReachDestination();
|
||||
|
||||
void followObject();
|
||||
void followObject(SceneObject* obj, F32 radius);
|
||||
void clearFollow();
|
||||
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
/// Stores information about a path.
|
||||
struct PathData {
|
||||
/// Pointer to path object.
|
||||
SimObjectPtr<NavPath> path;
|
||||
/// Do we own our path? If so, we will delete it when finished.
|
||||
bool owned;
|
||||
/// Path node we're at.
|
||||
U32 index;
|
||||
/// Default constructor.
|
||||
PathData() : path(NULL)
|
||||
{
|
||||
owned = false;
|
||||
index = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/// Should we jump?
|
||||
enum JumpStates {
|
||||
None, ///< No, don't jump.
|
||||
Now, ///< Jump immediately.
|
||||
Ledge, ///< Jump when we walk off a ledge.
|
||||
};
|
||||
|
||||
enum NavSize {
|
||||
Small,
|
||||
Regular,
|
||||
Large
|
||||
} mNavSize;
|
||||
void setNavSize(NavSize size) { mNavSize = size; updateNavMesh(); }
|
||||
NavSize getNavSize() const { return mNavSize; }
|
||||
|
||||
/// NavMesh we pathfind on.
|
||||
SimObjectPtr<NavMesh> mNavMesh;
|
||||
NavMesh* findNavMesh() const;
|
||||
void updateNavMesh();
|
||||
NavMesh* getNavMesh() const { return mNavMesh; }
|
||||
PathData mPathData;
|
||||
JumpStates mJump;
|
||||
|
||||
/// Clear out the current path.
|
||||
void clearPath();
|
||||
void repath();
|
||||
|
||||
/// Get the current path we're following.
|
||||
SimObjectPtr<NavPath> getPath() { return mPathData.path; };
|
||||
void followNavPath(NavPath* path);
|
||||
|
||||
/// Move to the specified node in the current path.
|
||||
void moveToNode(S32 node);
|
||||
bool flock();
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -669,6 +669,7 @@ static void RegisterGameFunctions()
|
|||
Con::setIntVariable("$TypeMasks::PathShapeObjectType", PathShapeObjectType);
|
||||
// PATHSHAPE END
|
||||
Con::setIntVariable("$TypeMasks::TurretObjectType", TurretObjectType);
|
||||
Con::setIntVariable("$TypeMasks::AIObjectType", AIObjectType);
|
||||
|
||||
Con::addVariable("Ease::InOut", TypeS32, &gEaseInOut,
|
||||
"InOut ease for curve movement.\n"
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ enum SceneObjectTypes
|
|||
/// @see TurretShape
|
||||
TurretObjectType = BIT(29),
|
||||
N_A_31 = BIT(30),
|
||||
N_A_32 = BIT(31),
|
||||
AIObjectType = BIT(31),
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -461,7 +461,6 @@ PlayerData::PlayerData()
|
|||
|
||||
physicsPlayerType = StringTable->EmptyString();
|
||||
mControlMap = StringTable->EmptyString();
|
||||
|
||||
dMemset( actionList, 0, sizeof(actionList) );
|
||||
}
|
||||
|
||||
|
|
@ -740,7 +739,7 @@ void PlayerData::initPersistFields()
|
|||
endGroup( "Camera" );
|
||||
|
||||
addGroup( "Movement" );
|
||||
addField("controlMap", TypeString, Offset(mControlMap, PlayerData),
|
||||
addField("controlMap", TypeString, Offset(mControlMap, PlayerData),
|
||||
"@brief movemap used by these types of objects.\n\n");
|
||||
|
||||
addFieldV( "maxStepHeight", TypeRangedF32, Offset(maxStepHeight, PlayerData), &CommonValidators::PositiveFloat,
|
||||
|
|
@ -1643,7 +1642,6 @@ Player::Player()
|
|||
mLastAbsoluteYaw = 0.0f;
|
||||
mLastAbsolutePitch = 0.0f;
|
||||
mLastAbsoluteRoll = 0.0f;
|
||||
|
||||
afx_init();
|
||||
}
|
||||
|
||||
|
|
@ -1741,7 +1739,6 @@ bool Player::onAdd()
|
|||
world );
|
||||
mPhysicsRep->setTransform( getTransform() );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2258,12 +2255,6 @@ void Player::advanceTime(F32 dt)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Player::getAIMove(Move* move)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void Player::setState(ActionState state, U32 recoverTicks)
|
||||
{
|
||||
if (state != mState) {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,12 @@ class Player;
|
|||
class OpenVRTrackedObject;
|
||||
#endif
|
||||
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
#include "navigation/navPath.h"
|
||||
#include "navigation/navMesh.h"
|
||||
#include "navigation/coverPoint.h"
|
||||
#endif // TORQUE_NAVIGATION_ENABLED
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
struct PlayerData: public ShapeBaseData {
|
||||
|
|
@ -758,8 +764,6 @@ public:
|
|||
Point3F getMomentum() const override;
|
||||
void setMomentum(const Point3F &momentum) override;
|
||||
bool displaceObject(const Point3F& displaceVector) override;
|
||||
virtual bool getAIMove(Move*);
|
||||
|
||||
bool checkDismountPosition(const MatrixF& oldPos, const MatrixF& newPos); ///< Is it safe to dismount here?
|
||||
|
||||
//
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@
|
|||
#include "core/stream/fileStream.h"
|
||||
#include "T3D/accumulationVolume.h"
|
||||
#include "console/persistenceManager.h"
|
||||
#include "AI/AIController.h"
|
||||
|
||||
IMPLEMENT_CO_DATABLOCK_V1(ShapeBaseData);
|
||||
|
||||
|
|
@ -197,7 +198,8 @@ ShapeBaseData::ShapeBaseData()
|
|||
useEyePoint( false ),
|
||||
isInvincible( false ),
|
||||
renderWhenDestroyed( true ),
|
||||
inheritEnergyFromMount( false )
|
||||
inheritEnergyFromMount( false ),
|
||||
mAIControllData(NULL)
|
||||
{
|
||||
INIT_ASSET(Shape);
|
||||
INIT_ASSET(DebrisShape);
|
||||
|
|
@ -548,6 +550,10 @@ void ShapeBaseData::initPersistFields()
|
|||
addField("silentBBoxValidation", TypeBool, Offset(silent_bbox_check, ShapeBaseData));
|
||||
INITPERSISTFIELD_SHAPEASSET(DebrisShape, ShapeBaseData, "The shape asset to use for auto-generated breakups via blowup(). @note may not be functional.");
|
||||
endGroup( "Shapes" );
|
||||
addGroup("Movement");
|
||||
addField("aiControllerData", TYPEID< AIControllerData >(), Offset(mAIControllData, ShapeBaseData),
|
||||
"@brief ai controller used by these types of objects.\n\n");
|
||||
endGroup("Movement");
|
||||
|
||||
addGroup("Particle Effects");
|
||||
addField( "explosion", TYPEID< ExplosionData >(), Offset(explosion, ShapeBaseData),
|
||||
|
|
@ -999,7 +1005,8 @@ ShapeBase::ShapeBase()
|
|||
mCameraFov( 90.0f ),
|
||||
mIsControlled( false ),
|
||||
mLastRenderFrame( 0 ),
|
||||
mLastRenderDistance( 0.0f )
|
||||
mLastRenderDistance( 0.0f ),
|
||||
mAIController(NULL)
|
||||
{
|
||||
mTypeMask |= ShapeBaseObjectType | LightObjectType;
|
||||
|
||||
|
|
@ -1050,6 +1057,7 @@ ShapeBase::~ShapeBase()
|
|||
cur->next = sFreeTimeoutList;
|
||||
sFreeTimeoutList = cur;
|
||||
}
|
||||
if (mAIController) mAIController->deleteObject();
|
||||
}
|
||||
|
||||
void ShapeBase::initPersistFields()
|
||||
|
|
@ -5467,3 +5475,44 @@ DefineEngineMethod(ShapeBase, getNodePoint, Point3F, (const char* nodeName), ,
|
|||
|
||||
return pos;
|
||||
}
|
||||
|
||||
bool ShapeBase::setAIController(SimObjectId controller)
|
||||
{
|
||||
if (Sim::findObject(controller, mAIController) && mAIController->mControllerData)
|
||||
{
|
||||
mAIController->setAIInfo(this);
|
||||
mTypeMask |= AIObjectType;
|
||||
return true;
|
||||
}
|
||||
Con::errorf("unable to find AIController : %i", controller);
|
||||
mAIController = NULL;
|
||||
mTypeMask |= ~AIObjectType;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ShapeBase::getAIMove(Move* move)
|
||||
{
|
||||
if (!isServerObject()) return false;
|
||||
if (isControlled()) return false; //something else is steering us, so use that one's controller
|
||||
if (!(mTypeMask & VehicleObjectType || mTypeMask & PlayerObjectType)) return false; //only support players and vehicles for now
|
||||
if (mAIController)
|
||||
{
|
||||
mAIController->getAIMove(move); //actual result
|
||||
mTypeMask |= AIObjectType;
|
||||
return true;
|
||||
}
|
||||
mAIController = NULL;
|
||||
mTypeMask &= ~AIObjectType;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
DefineEngineMethod(ShapeBase, setAIController, bool, (S32 controller), , "")
|
||||
{
|
||||
return object->setAIController(controller);
|
||||
}
|
||||
|
||||
DefineEngineMethod(ShapeBase, getAIController, AIController*, (), , "")
|
||||
{
|
||||
return object->getAIController();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@ class ShapeBase;
|
|||
class SFXSource;
|
||||
class SFXTrack;
|
||||
class SFXProfile;
|
||||
struct AIController;
|
||||
struct AIControllerData;
|
||||
|
||||
typedef void* Light;
|
||||
|
||||
|
|
@ -560,6 +562,7 @@ public:
|
|||
U32 cubeDescId;
|
||||
ReflectorDesc *reflectorDesc;
|
||||
|
||||
AIControllerData* mAIControllData;
|
||||
/// @name Destruction
|
||||
///
|
||||
/// Everyone likes to blow things up!
|
||||
|
|
@ -1761,6 +1764,11 @@ public:
|
|||
/// Returns true if this object is controlling by something
|
||||
bool isControlled() { return(mIsControlled); }
|
||||
|
||||
AIController* mAIController;
|
||||
bool setAIController(SimObjectId controller);
|
||||
AIController* getAIController() { return mAIController; };
|
||||
virtual bool getAIMove(Move* move);
|
||||
|
||||
/// Returns true if this object is being used as a camera in first person
|
||||
bool isFirstPerson() const;
|
||||
|
||||
|
|
@ -1902,7 +1910,6 @@ public:
|
|||
void registerCollisionCallback(CollisionEventCallback*);
|
||||
void unregisterCollisionCallback(CollisionEventCallback*);
|
||||
|
||||
protected:
|
||||
enum {
|
||||
ANIM_OVERRIDDEN = BIT(0),
|
||||
BLOCK_USER_CONTROL = BIT(1),
|
||||
|
|
@ -1910,6 +1917,8 @@ protected:
|
|||
BAD_ANIM_ID = 999999999,
|
||||
BLENDED_CLIP = 0x80000000,
|
||||
};
|
||||
U8 anim_clip_flags;
|
||||
protected:
|
||||
struct BlendThread
|
||||
{
|
||||
TSThread* thread;
|
||||
|
|
@ -1917,7 +1926,6 @@ protected:
|
|||
};
|
||||
Vector<BlendThread> blend_clips;
|
||||
static U32 unique_anim_tag_counter;
|
||||
U8 anim_clip_flags;
|
||||
S32 last_anim_id;
|
||||
U32 last_anim_tag;
|
||||
U32 last_anim_lock_tag;
|
||||
|
|
|
|||
|
|
@ -148,6 +148,7 @@ VehicleData::VehicleData()
|
|||
collDamageThresholdVel = 20;
|
||||
collDamageMultiplier = 0.05f;
|
||||
enablePhysicsRep = true;
|
||||
mControlMap = StringTable->EmptyString();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -321,6 +322,11 @@ void VehicleData::initPersistFields()
|
|||
"velocity).\n\nCurrently unused." );
|
||||
endGroup("Collision");
|
||||
|
||||
addGroup("Movement");
|
||||
addField("controlMap", TypeString, Offset(mControlMap, VehicleData),
|
||||
"@brief movemap used by these types of objects.\n\n");
|
||||
endGroup("Movement");
|
||||
|
||||
addGroup("Steering");
|
||||
addField("controlMap", TypeString, Offset(mControlMap, VehicleData),
|
||||
"@brief movemap used by these types of objects.\n\n");
|
||||
|
|
@ -474,7 +480,6 @@ bool Vehicle::onAdd()
|
|||
void Vehicle::onRemove()
|
||||
{
|
||||
SAFE_DELETE(mPhysicsRep);
|
||||
|
||||
U32 i=0;
|
||||
|
||||
for( i=0; i<VehicleData::VC_NUM_DAMAGE_EMITTERS; i++ )
|
||||
|
|
@ -499,10 +504,17 @@ void Vehicle::processTick(const Move* move)
|
|||
{
|
||||
PROFILE_SCOPE( Vehicle_ProcessTick );
|
||||
|
||||
// If we're not being controlled by a client, let the
|
||||
// AI sub-module get a chance at producing a move.
|
||||
Move aiMove;
|
||||
if (!move && isServerObject() && getAIMove(&aiMove))
|
||||
move = &aiMove;
|
||||
|
||||
ShapeBase::processTick(move);
|
||||
if ( isMounted() )
|
||||
return;
|
||||
|
||||
|
||||
// Warp to catch up to server
|
||||
if (mDelta.warpCount < mDelta.warpTicks)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@
|
|||
#include "T3D/rigidShape.h"
|
||||
#endif
|
||||
|
||||
#include "T3D/AI/AIController.h"
|
||||
|
||||
class ParticleEmitter;
|
||||
class ParticleEmitterData;
|
||||
class ClippedPolyList;
|
||||
|
|
@ -69,8 +71,8 @@ struct VehicleData : public RigidShapeData
|
|||
F32 damageLevelTolerance[ VC_NUM_DAMAGE_LEVELS ];
|
||||
F32 numDmgEmitterAreas;
|
||||
|
||||
StringTableEntry mControlMap;
|
||||
bool enablePhysicsRep;
|
||||
StringTableEntry mControlMap;
|
||||
|
||||
//
|
||||
VehicleData();
|
||||
|
|
@ -99,7 +101,6 @@ class Vehicle : public RigidShape
|
|||
Point2F mSteering;
|
||||
F32 mThrottle;
|
||||
bool mJetting;
|
||||
|
||||
GFXStateBlockRef mSolidSB;
|
||||
|
||||
SimObjectPtr<ParticleEmitter> mDamageEmitterList[VehicleData::VC_NUM_DAMAGE_EMITTERS];
|
||||
|
|
@ -147,6 +148,9 @@ public:
|
|||
bool onAdd() override;
|
||||
void onRemove() override;
|
||||
|
||||
Point2F getSteering() { return mSteering; };
|
||||
F32 getThrottle() { return mThrottle;};
|
||||
|
||||
/// Interpolates between move ticks @see processTick
|
||||
/// @param dt Change in time between the last call and this call to the function
|
||||
void advanceTime(F32 dt) override;
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ ImplementBitfieldType(GameTypeMasksType,
|
|||
{ SceneObjectTypes::PathShapeObjectType, "$TypeMasks::PathShapeObjectType", "Path-following Objects.\n" },
|
||||
{ SceneObjectTypes::TurretObjectType, "$TypeMasks::TurretObjectType", "Turret Objects.\n" },
|
||||
{ SceneObjectTypes::N_A_31, "$TypeMasks::N_A_31", "unused 31st bit.\n" },
|
||||
{ SceneObjectTypes::N_A_32, "$TypeMasks::N_A_32", "unused 32nd bit.\n" },
|
||||
{ SceneObjectTypes::AIObjectType, "$TypeMasks::AIObjectType", "AIObjectType.\n" },
|
||||
|
||||
EndImplementBitfieldType;
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
#include "gui/buttons/guiButtonCtrl.h"
|
||||
#include "gui/worldEditor/undoActions.h"
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#include "T3D/AI/AIController.h"
|
||||
|
||||
IMPLEMENT_CONOBJECT(GuiNavEditorCtrl);
|
||||
|
||||
|
|
@ -225,8 +226,29 @@ void GuiNavEditorCtrl::spawnPlayer(const Point3F &pos)
|
|||
SimGroup* missionCleanup = dynamic_cast<SimGroup*>(cleanup);
|
||||
missionCleanup->addObject(obj);
|
||||
}
|
||||
mPlayer = static_cast<AIPlayer*>(obj);
|
||||
Con::executef(this, "onPlayerSelected", Con::getIntArg(mPlayer->mLinkTypes.getFlags()));
|
||||
mPlayer = obj;
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
AIPlayer* asAIPlayer = dynamic_cast<AIPlayer*>(obj);
|
||||
if (asAIPlayer) //try direct
|
||||
{
|
||||
Con::executef(this, "onPlayerSelected", Con::getIntArg(asAIPlayer->mLinkTypes.getFlags()));
|
||||
}
|
||||
else
|
||||
{
|
||||
ShapeBase* sbo = dynamic_cast<ShapeBase*>(obj);
|
||||
if (sbo->getAIController())
|
||||
{
|
||||
if (sbo->getAIController()->mControllerData)
|
||||
Con::executef(this, "onPlayerSelected", Con::getIntArg(sbo->getAIController()->mControllerData->mLinkTypes.getFlags()));
|
||||
}
|
||||
else
|
||||
{
|
||||
#endif
|
||||
Con::executef(this, "onPlayerSelected");
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -383,16 +405,56 @@ void GuiNavEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event)
|
|||
// Select/move character
|
||||
else
|
||||
{
|
||||
if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType, &ri))
|
||||
if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri))
|
||||
{
|
||||
if(dynamic_cast<AIPlayer*>(ri.object))
|
||||
if(ri.object)
|
||||
{
|
||||
mPlayer = dynamic_cast<AIPlayer*>(ri.object);
|
||||
Con::executef(this, "onPlayerSelected", Con::getIntArg(mPlayer->mLinkTypes.getFlags()));
|
||||
mPlayer = ri.object;
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
AIPlayer* asAIPlayer = dynamic_cast<AIPlayer*>(mPlayer.getPointer());
|
||||
if (asAIPlayer) //try direct
|
||||
{
|
||||
Con::executef(this, "onPlayerSelected", Con::getIntArg(asAIPlayer->mLinkTypes.getFlags()));
|
||||
}
|
||||
else
|
||||
{
|
||||
ShapeBase* sbo = dynamic_cast<ShapeBase*>(mPlayer.getPointer());
|
||||
if (sbo->getAIController())
|
||||
{
|
||||
if (sbo->getAIController()->mControllerData)
|
||||
Con::executef(this, "onPlayerSelected", Con::getIntArg(sbo->getAIController()->mControllerData->mLinkTypes.getFlags()));
|
||||
}
|
||||
else
|
||||
{
|
||||
#endif
|
||||
Con::executef(this, "onPlayerSelected");
|
||||
}
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (!mPlayer.isNull() && gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
|
||||
{
|
||||
AIPlayer* asAIPlayer = dynamic_cast<AIPlayer*>(mPlayer.getPointer());
|
||||
if (asAIPlayer) //try direct
|
||||
{
|
||||
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||
asAIPlayer->setPathDestination(ri.point);
|
||||
#else
|
||||
asAIPlayer->setMoveDestination(ri.point,false);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
ShapeBase* sbo = dynamic_cast<ShapeBase*>(mPlayer.getPointer());
|
||||
if (sbo->getAIController())
|
||||
{
|
||||
if (sbo->getAIController()->mControllerData)
|
||||
sbo->getAIController()->getNav()->setPathDestination(ri.point, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(!mPlayer.isNull() && gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
|
||||
mPlayer->setPathDestination(ri.point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -455,8 +517,8 @@ void GuiNavEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event)
|
|||
|
||||
if(mMode == mTestMode)
|
||||
{
|
||||
if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType, &ri))
|
||||
mCurPlayer = dynamic_cast<AIPlayer*>(ri.object);
|
||||
if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri))
|
||||
mCurPlayer = ri.object;
|
||||
else
|
||||
mCurPlayer = NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,8 +155,8 @@ protected:
|
|||
/// @name Test mode
|
||||
/// @{
|
||||
|
||||
SimObjectPtr<AIPlayer> mPlayer;
|
||||
SimObjectPtr<AIPlayer> mCurPlayer;
|
||||
SimObjectPtr<SceneObject> mPlayer;
|
||||
SimObjectPtr<SceneObject> mCurPlayer;
|
||||
|
||||
/// @}
|
||||
|
||||
|
|
|
|||
|
|
@ -169,3 +169,20 @@ datablock LightAnimData( SpinLightAnim )
|
|||
rotKeys[2] = "az";
|
||||
rotSmooth[2] = true;
|
||||
};
|
||||
|
||||
datablock AIPlayerControllerData( aiPlayerControl )
|
||||
{
|
||||
moveTolerance = 0.25; followTolerance = 1.0; mAttackRadius = 2;
|
||||
};
|
||||
|
||||
datablock AIWheeledVehicleControllerData( aiCarControl )
|
||||
{
|
||||
moveTolerance = 1.0; followTolerance = 2.0; mAttackRadius = 5.0;
|
||||
};
|
||||
|
||||
datablock AIFlyingVehicleControllerData( aiPlaneControl )
|
||||
{
|
||||
moveTolerance = 2.0; followTolerance = 5.0; mAttackRadius = 10.0;
|
||||
FlightFloor = 15; FlightCeiling = 150;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -485,7 +485,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) {
|
|||
VertSizing = "bottom";
|
||||
Extent = "90 18";
|
||||
text = "Stop";
|
||||
command = "NavEditorGui.getPlayer().stop();";
|
||||
command = "NavEditorGui.stop();";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ function NavEditorPlugin::onWorldEditorStartup(%this)
|
|||
|
||||
// Add ourselves to the Editor Settings window.
|
||||
exec("./NavEditorSettingsTab.gui");
|
||||
//ESettingsWindow.addTabPage(ENavEditorSettingsPage);
|
||||
ESettingsWindow.addTabPage(ENavEditorSettingsPage);
|
||||
ENavEditorSettingsPage.init();
|
||||
|
||||
// Add items to World Editor Creator
|
||||
|
|
@ -104,6 +104,7 @@ function ENavEditorSettingsPage::init(%this)
|
|||
{
|
||||
// Initialises the settings controls in the settings dialog box.
|
||||
%this-->SpawnClassOptions.clear();
|
||||
%this-->SpawnClassOptions.add("Player");
|
||||
%this-->SpawnClassOptions.add("AIPlayer");
|
||||
%this-->SpawnClassOptions.setFirstSelected();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -459,6 +459,14 @@ function NavEditorGui::onLinkSelected(%this, %flags)
|
|||
|
||||
function NavEditorGui::onPlayerSelected(%this, %flags)
|
||||
{
|
||||
if (!isObject(%this.getPlayer().aiController) && (!(%this.getPlayer().isMemberOfClass("AIPlayer"))))
|
||||
{
|
||||
%this.getPlayer().aiController = new AIController(){ ControllerData = %this.getPlayer().getDatablock().aiControllerData; };
|
||||
%this.getPlayer().setAIController(%this.getPlayer().aiController);
|
||||
}
|
||||
NavMeshIgnore(%this.getPlayer(), true);
|
||||
%this.getPlayer().setDamageState("Enabled");
|
||||
|
||||
updateLinkData(NavEditorOptionsWindow-->TestProperties, %flags);
|
||||
}
|
||||
|
||||
|
|
@ -526,7 +534,7 @@ function NavEditorGui::findCover(%this)
|
|||
%text = NavEditorOptionsWindow-->TestProperties->CoverPosition.getText();
|
||||
if(%text !$= "")
|
||||
%pos = eval("return " @ %text);
|
||||
%this.getPlayer().findCover(%pos, NavEditorOptionsWindow-->TestProperties->CoverRadius.getText());
|
||||
%this.getPlayer().getAIController().findCover(%pos, NavEditorOptionsWindow-->TestProperties->CoverRadius.getText());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -547,10 +555,19 @@ function NavEditorGui::followObject(%this)
|
|||
toolsMessageBoxOk("Error", "Cannot find object" SPC %text);
|
||||
}
|
||||
if(isObject(%obj))
|
||||
%this.getPlayer().followObject(%obj, NavEditorOptionsWindow-->TestProperties->FollowRadius.getText());
|
||||
%this.getPlayer().getAIController().followObject(%obj, NavEditorOptionsWindow-->TestProperties->FollowRadius.getText());
|
||||
}
|
||||
}
|
||||
|
||||
function NavEditorGui::stop(%this)
|
||||
{
|
||||
if (isObject(%this.getPlayer().aiController))
|
||||
%this.getPlayer().aiController.stop();
|
||||
else
|
||||
{
|
||||
NavEditorGui.getPlayer().stop();
|
||||
}
|
||||
}
|
||||
function NavInspector::inspect(%this, %obj)
|
||||
{
|
||||
%name = "";
|
||||
|
|
|
|||
Loading…
Reference in a new issue