소스 검색

generalized ai subsystem wipwork

AzaezelX 5 달 전
부모
커밋
8c663a19a5

+ 1 - 1
Engine/source/CMakeLists.txt

@@ -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"

+ 77 - 0
Engine/source/T3D/AI/AIAimTarget.cpp

@@ -0,0 +1,77 @@
+//-----------------------------------------------------------------------------
+// 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;
+
+bool AIAimTarget::checkInLos(GameBase* 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;
+}

+ 39 - 0
Engine/source/T3D/AI/AIAimTarget.h

@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// 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 : AIInfo
+{
+   typedef AIInfo Parent;
+   Point3F mAimOffset;
+   bool mTargetInLOS;                  // Is target object visible?
+   Point3F getPosition() { return ((mObj) ? mObj->getPosition() : mPosition) + mAimOffset; }
+   bool checkInLos(GameBase* target = NULL, bool _useMuzzle = false, bool _checkEnabled = false);
+   bool checkInFoV(GameBase* target = NULL, F32 camFov = 45.0f, bool _checkEnabled = false);
+   AIAimTarget(AIController* controller) : Parent(controller) {};
+   AIAimTarget(AIController* controller, SimObjectPtr<SceneObject> objIn, F32 radIn) : Parent(controller, objIn, radIn) {};
+   AIAimTarget(AIController* controller, Point3F pointIn, F32 radIn) : Parent(controller, pointIn, radIn) {};
+};
+
+#endif

+ 430 - 0
Engine/source/T3D/AI/AIController.cpp

@@ -0,0 +1,430 @@
+//-----------------------------------------------------------------------------
+// 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"
+
+
+IMPLEMENT_CONOBJECT(AIController);
+
+//-----------------------------------------------------------------------------
+void AIController::throwCallback(const char* 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;
+}
+
+#ifdef TORQUE_NAVIGATION_ENABLED
+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().getForwardVector();
+
+#ifdef TORQUE_NAVIGATION_ENABLED
+   if (sbo->getDamageState() == ShapeBase::Enabled)
+   {
+      if (mMovement.mMoveState != ModeStop)
+         getNav()->updateNavMesh();
+      if (!getGoal()->mObj.isNull())
+      {
+         if (getNav()->mPathData.path.isNull())
+         {
+            if (getGoal()->getDist() > mControllerData->mMoveTolerance)
+               getNav()->followObject(getGoal());
+         }
+         else
+         {
+            if (getGoal()->getDist() > mControllerData->mMoveTolerance)
+               getNav()->repath();
+
+            if (getAim()->getDist() < mControllerData->mMoveTolerance)
+            {
+               getNav()->clearPath();
+               mMovement.mMoveState = ModeStop;
+               throwCallback("onTargetInRange");
+            }
+            else if (getAim()->getDist() < mControllerData->mAttackRadius)
+            {
+               throwCallback("onTargetInFiringRange");
+            }
+         }
+      }
+   }
+#endif // TORQUE_NAVIGATION_ENABLED
+
+   // Orient towards the aim point, aim object, or towards
+   // our destination.
+   if (getAim()->mObj || getAim()->mPosSet || mMovement.mMoveState != ModeStop)
+   {
+      // Update the aim position if we're aiming for an object or explicit position
+      if (getAim()->mObj || getAim()->mPosSet)
+         mMovement.mAimLocation = getAim()->getPosition();
+      else
+         mMovement.mAimLocation = mMovement.mMoveDestination;
+
+      mControllerData->resolveYaw(this, location, movePtr);
+      mControllerData->resolvePitch(this, location, movePtr);
+      mControllerData->resolveRoll(this, location, movePtr);
+      mControllerData->resolveSpeed(this, location, movePtr);
+      mControllerData->resolveStuck(this);
+   }
+
+   // 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()->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;
+      }
+   }
+
+   /*
+   // Replicate the trigger state into the move so that
+   // triggers can be controlled from scripts.
+   for (U32 i = 0; i < MaxTriggerKeys; i++)
+      movePtr->trigger[i] = getImageTriggerState(i);
+   */
+
+#ifdef TORQUE_NAVIGATION_ENABLED
+   if (getNav()->mJump == AINavigation::Now)
+   {
+      movePtr->trigger[2] = true;
+      getNav()->mJump = AINavigation::None;
+   }
+   else if (getNav()->mJump == AINavigation::Ledge)
+   {
+      // If we're not touching the ground, jump!
+      RayInfo info;
+      if (!getAIInfo()->mObj->getContainer()->castRay(getAIInfo()->getPosition(), getAIInfo()->getPosition() - Point3F(0, 0, 0.4f), StaticShapeObjectType, &info))
+      {
+         movePtr->trigger[2] = true;
+         getNav()->mJump = AINavigation::None;
+      }
+   }
+#endif // TORQUE_NAVIGATION_ENABLED
+
+   return true;
+}
+
+void AIController::clearCover()
+{
+   // Notify cover that we are no longer on our way.
+   if (!getCover()->mCoverPoint.isNull())
+      getCover()->mCoverPoint->setOccupied(false);
+   SAFE_DELETE(mCover);
+}
+
+//-----------------------------------------------------------------------------
+
+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().getForwardVector();
+
+   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)
+{
+   // Move towards the destination
+   if (obj->mMovement.mMoveState != AIController::ModeStop)
+   {
+      F32 xDiff = obj->mMovement.mMoveDestination.x - location.x;
+      F32 yDiff = obj->mMovement.mMoveDestination.y - location.y;
+      Point3F rotation = obj->getAIInfo()->mObj->getTransform().getForwardVector();
+
+      // Check if we should mMove, or if we are 'close enough'
+      if (mFabs(xDiff) < mMoveTolerance && mFabs(yDiff) < mMoveTolerance)
+      {
+         obj->mMovement.mMoveState = AIController::ModeStop;
+         obj->getNav()->onReachDestination();
+      }
+      else
+      {
+         // Build move direction in world space
+         if (mIsZero(xDiff))
+            movePtr->y = (location.y > obj->mMovement.mMoveDestination.y) ? -1.0f : 1.0f;
+         else
+            if (mIsZero(yDiff))
+               movePtr->x = (location.x > obj->mMovement.mMoveDestination.x) ? -1.0f : 1.0f;
+            else
+               if (mFabs(xDiff) > mFabs(yDiff))
+               {
+                  F32 value = mFabs(yDiff / xDiff);
+                  movePtr->y = (location.y > obj->mMovement.mMoveDestination.y) ? -value : value;
+                  movePtr->x = (location.x > obj->mMovement.mMoveDestination.x) ? -1.0f : 1.0f;
+               }
+               else
+               {
+                  F32 value = mFabs(xDiff / yDiff);
+                  movePtr->x = (location.x > obj->mMovement.mMoveDestination.x) ? -value : value;
+                  movePtr->y = (location.y > obj->mMovement.mMoveDestination.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;
+
+            obj->mMovement.mMoveState = AIController::ModeSlowing;
+         }
+         else
+         {
+            movePtr->x *= obj->mMovement.mMoveSpeed;
+            movePtr->y *= obj->mMovement.mMoveSpeed;
+
+            obj->mMovement.mMoveState = AIController::ModeMove;
+         }
+      }
+   }
+}
+
+void AIControllerData::resolveStuck(AIController* obj)
+{
+   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()) {
+      if (obj->mMovement.mMoveStuckTestCountdown > 0)
+         --obj->mMovement.mMoveStuckTestCountdown;
+      else
+      {
+         // We should check to see if we are stuck...
+         F32 locationDelta = (obj->getAIInfo()->getPosition() - obj->getAIInfo()->mLastPos).len();
+         if (locationDelta < mMoveStuckTolerance && (sbo->getDamageState() == ShapeBase::Enabled))
+         {
+            // 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.mMoveState = AIController::ModeStuck;
+               obj->throwCallback("onStuck");
+            }
+         }
+      }
+      obj->getAIInfo()->mLastPos = obj->getAIInfo()->getPosition();
+   }
+}
+
+void AIControllerData::initPersistFields()
+{
+   docsURL;
+   addGroup("AI");
+
+   addFieldV("moveTolerance", TypeRangedF32, Offset(mMoveTolerance, AIControllerData), &CommonValidators::PositiveFloat,
+      "@brief Distance from destination before stopping.\n\n"
+      "When the AIPlayer 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 AIPlayer 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 AIPlayer 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 AIPlayer 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("moveStuckTolerance", TypeRangedF32, Offset(mMoveStuckTolerance, AIControllerData), &CommonValidators::PositiveFloat,
+      "@brief Distance tolerance on stuck check.\n\n"
+      "When the AIPlayer is moving to a given destination, if it ever moves less than "
+      "this tolerance during a single tick, the AIPlayer is considered stuck.  At this point "
+      "the onMoveStuck() callback is called on the datablock.\n");
+
+   addFieldV("moveStuckTestDelay", TypeRangedS32, Offset(mMoveStuckTestDelay, AIControllerData), &CommonValidators::PositiveInt,
+      "@brief The number of ticks to wait before testing if the AIPlayer is stuck.\n\n"
+      "When the AIPlayer is asked to move, this property is the number of ticks to wait "
+      "before the AIPlayer starts to check if it is stuck.  This delay allows the AIPlayer "
+      "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");
+
+   addFieldV("AttackRadius", TypeRangedF32, Offset(mAttackRadius, AIControllerData), &CommonValidators::PositiveFloat,
+      "@brief Distance considered in firing range for callback purposes.");
+
+   endGroup("AI");
+
+#ifdef TORQUE_NAVIGATION_ENABLED
+   addGroup("Pathfinding");
+
+   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 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()->mObj || obj->getAim()->mPosSet || obj->mMovement.mMoveState != AIController::ModeStop)
+   {
+      // Next do pitch.
+      if (!obj->getAim()->mObj && !obj->getAim()->mPosSet)
+      {
+         // 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;
+   }
+}
+#endif //_AICONTROLLER_H_

+ 155 - 0
Engine/source/T3D/AI/AIController.h

@@ -0,0 +1,155 @@
+//-----------------------------------------------------------------------------
+// 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_
+#ifdef TORQUE_NAVIGATION_ENABLED
+#include "navigation/coverPoint.h"
+#include "AIInfo.h"
+#include "AIGoal.h"
+#include "AIAimTarget.h"
+#include "AICover.h"
+#include "AINavigation.h"
+class AIControllerData;
+class AIController;
+
+//-----------------------------------------------------------------------------
+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.
+      ModeMove,                       // AI is currently moving.
+      ModeStuck,                      // AI is stuck, but wants to move.
+      ModeSlowing,                    // AI is slowing down as it reaches it's destination.
+   };
+
+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) { mGoal = (targ) ? new AIGoal(this, targ->getPosition(), targ->mRadius) : NULL; }
+   void setGoal(Point3F loc, F32 rad = 0.0f) { delete(mGoal); mGoal = new AIGoal(this, loc, rad); }
+   void setGoal(SimObjectPtr<SceneObject> objIn, F32 rad = 0.0f) { delete(mGoal); mGoal = new AIGoal(this, objIn, rad); }
+   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)) { delete(mAimTarget);  mAimTarget = new AIAimTarget(this, loc, rad); mAimTarget->mAimOffset = offset; }
+   void setAim(SimObjectPtr<SceneObject> objIn, F32 rad = 0.0f, Point3F offset = Point3F(0.0f, 0.0f, 0.0f)) { delete(mAimTarget); mAimTarget = new AIAimTarget(this, objIn, rad); mAimTarget->mAimOffset = offset; }
+   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
+   {
+      MoveState mMoveState;
+      F32 mMoveSpeed = 1.0;
+      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;
+      Point3F mMoveDestination;
+      // move triggers
+      bool mMoveTriggers[MaxTriggerKeys];
+      void stopMove();
+      void onStuck();
+   } mMovement;
+
+   struct TriggerState
+   {
+      // 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()
+   {
+      mControllerData = NULL;
+      mAIInfo = new AIInfo(this);
+      mGoal = new AIGoal(this);
+      mAimTarget = new AIAimTarget(this);
+      mCover = new AICover(this);
+      mNav = new AINavigation(this);
+   };
+
+   DECLARE_CONOBJECT(AIController);
+};
+
+//-----------------------------------------------------------------------------
+class AIControllerData : public SimDataBlock {
+
+   typedef SimDataBlock Parent;
+
+public:
+
+   AIControllerData() {};
+   ~AIControllerData() {};
+
+   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
+   F32 mMoveStuckTolerance;            // Distance tolerance on stuck check
+   S32 mMoveStuckTestDelay;            // The number of ticks to wait before checking if the AI is stuck
+   /// Types of link we can use.
+   LinkData mLinkTypes;
+
+   void resolveYaw(AIController* obj, Point3F location, Move* movePtr);
+   void resolvePitch(AIController* obj, Point3F location, Move* movePtr) {};
+   void resolveRoll(AIController* obj, Point3F location, Move* movePtr);
+   void resolveSpeed(AIController* obj, Point3F location, Move* movePtr);
+   void resolveStuck(AIController* obj);
+};
+
+class AIPlayerControllerData : AIControllerData
+{
+   void resolvePitch(AIController* obj, Point3F location, Move* movePtr);
+};
+#endif // TORQUE_NAVIGATION_ENABLED
+#endif //_AICONTROLLER_H_

+ 21 - 0
Engine/source/T3D/AI/AICover.cpp

@@ -0,0 +1,21 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------

+ 37 - 0
Engine/source/T3D/AI/AICover.h

@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------------
+// 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"
+
+struct AICover : AIInfo
+{
+   typedef AIInfo Parent;
+   /// Pointer to a cover point.
+   SimObjectPtr<CoverPoint> mCoverPoint;
+   AICover(AIController* controller) : Parent(controller) {};
+   AICover(AIController* controller, SimObjectPtr<SceneObject> objIn, F32 radIn) : Parent(controller, objIn, radIn) {};
+   AICover(AIController* controller, Point3F pointIn, F32 radIn) : Parent(controller, pointIn, radIn) {};
+};
+
+#endif

+ 24 - 0
Engine/source/T3D/AI/AIGoal.cpp

@@ -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"

+ 34 - 0
Engine/source/T3D/AI/AIGoal.h

@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------------
+// 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 : AIInfo
+{
+   typedef AIInfo Parent;
+   AIGoal(AIController* controller): Parent(controller) {};
+   AIGoal(AIController* controller, SimObjectPtr<SceneObject> objIn, F32 radIn) : Parent(controller, objIn, radIn) {};
+   AIGoal(AIController* controller, Point3F pointIn, F32 radIn) : Parent(controller, pointIn, radIn) {};
+};
+#endif

+ 59 - 0
Engine/source/T3D/AI/AIInfo.cpp

@@ -0,0 +1,59 @@
+//-----------------------------------------------------------------------------
+// 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;
+};
+
+AIInfo::AIInfo(AIController* controller, Point3F pointIn, F32 radIn)
+{
+   mControllerRef = controller;
+   mObj = NULL;
+   mPosition = mLastPos = pointIn;
+   mRadius = radIn;
+   mPosSet = true;
+};
+
+F32 AIInfo::getDist()
+{
+   AIInfo* controlObj = getCtrl()->getAIInfo();
+   F32 ret = VectorF(controlObj->mObj->getPosition() - getPosition()).len();
+   ret -= controlObj->mRadius + mRadius;
+   return ret;
+}

+ 46 - 0
Engine/source/T3D/AI/AIInfo.h

@@ -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 _SHAPEBASE_H_
+#include "T3D/shapeBase.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() { return (mObj) ? mObj->getPosition() : mPosition; }
+   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

+ 259 - 0
Engine/source/T3D/AI/AINavigation.cpp

@@ -0,0 +1,259 @@
+//-----------------------------------------------------------------------------
+// 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"
+
+AINavigation::AINavigation(AIController* controller)
+{
+   mControllerRef = controller;
+}
+
+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()))
+      {
+         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 we're following, get their position.
+   mPathData.path->mTo = mControllerRef->getGoal()->getPosition();
+   // Update from position and replan.
+   mPathData.path->mFrom = mControllerRef->getAIInfo()->getPosition();
+   mPathData.path->plan();
+   // Move to first node (skip start pos).
+   moveToNode(1);
+}
+
+Point3F AINavigation::getPathDestination() const
+{
+   if (!mPathData.path.isNull())
+      return mPathData.path->mTo;
+   return Point3F(0, 0, 0);
+
+}
+
+void AINavigation::setMoveDestination(const Point3F& location, bool slowdown)
+{
+   mMoveDestination = location;
+   mControllerRef->mMovement.mMoveState = AIController::ModeMove;
+   mControllerRef->mMovement.mMoveSlowdown = slowdown;
+   mControllerRef->mMovement.mMoveStuckTestCountdown = mControllerRef->mControllerData->mMoveStuckTestDelay;
+}
+
+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");
+}
+
+bool AINavigation::setPathDestination(const Point3F& pos)
+{
+   if (!mNavMesh)
+      updateNavMesh();
+   // If we can't find a mesh, just move regularly.
+   if (!mNavMesh)
+   {
+      //setMoveDestination(pos);
+      mControllerRef->throwCallback("onPathFailed");
+      return false;
+   }
+
+   // Create a new path.
+   NavPath* path = new NavPath();
+
+   path->mMesh = mNavMesh;
+   path->mFrom = mControllerRef->getAIInfo()->getPosition();
+   path->mTo = pos;
+   path->mFromSet = path->mToSet = true;
+   path->mAlwaysRender = true;
+   path->mLinkTypes = mControllerRef->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();
+      mControllerRef->clearCover();
+      clearFollow();
+      // Store new path.
+      mPathData.path = path;
+      mPathData.owned = true;
+      // Skip node 0, which we are currently standing on.
+      moveToNode(1);
+      mControllerRef->throwCallback("onPathSuccess");
+      return true;
+   }
+   else
+   {
+      // Just move normally if we can't path.
+      //setMoveDestination(pos, true);
+      //return;
+      mControllerRef->throwCallback("onPathFailed");
+      path->deleteObject();
+      return false;
+   }
+}
+
+void AINavigation::followObject(AIInfo* targ)
+{
+   if (!targ) return;
+
+   if (targ->getDist() < mControllerRef->mControllerData->mMoveTolerance)
+      return;
+
+   if (setPathDestination(targ->getPosition()))
+   {
+      mControllerRef->clearCover();
+      mControllerRef->setGoal(targ);
+   }
+}
+
+void AINavigation::followObject(SceneObject* obj, F32 radius)
+{
+   mControllerRef->setGoal(obj, radius);
+   followObject(mControllerRef->getGoal());
+}
+
+void AINavigation::clearFollow()
+{
+   mControllerRef->clearGoal();
+}
+
+void AINavigation::followNavPath(NavPath* path)
+{
+   // Get rid of our current path.
+   clearPath();
+   mControllerRef->clearCover();
+   clearFollow();
+
+   // 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();
+}

+ 91 - 0
Engine/source/T3D/AI/AINavigation.h

@@ -0,0 +1,91 @@
+//-----------------------------------------------------------------------------
+// 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);
+
+   /// 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.
+   };
+
+   Point3F mMoveDestination;
+   void setMoveDestination(const Point3F& location, bool slowdown);
+   void onReachDestination();
+
+   /// NavMesh we pathfind on.
+   SimObjectPtr<NavMesh> mNavMesh;
+   NavMesh* findNavMesh() const;
+   void updateNavMesh();
+   PathData mPathData;
+   JumpStates mJump;
+
+   /// Clear out the current path.
+   void clearPath();
+   bool setPathDestination(const Point3F& pos);
+   Point3F getPathDestination() const;
+   void repath();
+
+   /// Get the current path we're following.
+   SimObjectPtr<NavPath> getPath() { return mPathData.path; };
+   void followNavPath(NavPath* path);
+
+   void followObject(AIInfo* targ);
+   void followObject(SceneObject* obj, F32 radius);
+   void clearFollow();
+   /// Move to the specified node in the current path.
+   void moveToNode(S32 node);
+
+};
+
+#endif

+ 33 - 2
Engine/source/T3D/player.cpp

@@ -460,6 +460,7 @@ PlayerData::PlayerData()
    jumpTowardsNormal = true;
 
    physicsPlayerType = StringTable->EmptyString();
+   mControlMap = StringTable->EmptyString();
 
    dMemset( actionList, 0, sizeof(actionList) );
 }
@@ -739,7 +740,9 @@ void PlayerData::initPersistFields()
    endGroup( "Camera" );
 
    addGroup( "Movement" );
-
+   addField("controlMap", TypeString, Offset(mControlMap, PlayerData),
+      "@brief movemap used by these types of objects.\n\n");
+   
       addFieldV( "maxStepHeight", TypeRangedF32, Offset(maxStepHeight, PlayerData), &CommonValidators::PositiveFloat,
          "@brief Maximum height the player can step up.\n\n"
          "The player will automatically step onto changes in ground height less "
@@ -1738,7 +1741,7 @@ bool Player::onAdd()
                            world );
       mPhysicsRep->setTransform( getTransform() );
    }
-
+   mAIController = NULL;
    return true;
 }
 
@@ -2256,8 +2259,36 @@ void Player::advanceTime(F32 dt)
    }
 }
 
+bool Player::setAIController(const char* controller)
+{
+   if (Sim::findObject(controller, mAIController))
+   {
+      mAIController->setAIInfo(this);
+      return true;
+   }
+
+   mAIController = NULL;
+   return false;
+}
+
+DefineEngineMethod(Player, setAIController, bool, (const char* controller), , "")
+{
+   return object->setAIController(controller);
+}
+
+DefineEngineMethod(Player, getAIController, AIController*, (), , "")
+{
+   return object->getAIController();
+}
+
+
 bool Player::getAIMove(Move* move)
 {
+   if (mAIController)
+   {
+      mAIController->getAIMove(move); //actual result
+   }
+
    return false;
 }
 

+ 12 - 1
Engine/source/T3D/player.h

@@ -50,6 +50,13 @@ 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
+#include "AI/AIController.h"
+
 //----------------------------------------------------------------------------
 
 struct PlayerData: public ShapeBaseData {
@@ -346,7 +353,8 @@ struct PlayerData: public ShapeBaseData {
 
    // Jump off surfaces at their normal rather than straight up
    bool jumpTowardsNormal;
-
+   StringTableEntry mControlMap;
+   AIControllerData* mAIControllData;
    // For use if/when mPhysicsPlayer is created
    StringTableEntry physicsPlayerType;
 
@@ -488,6 +496,7 @@ protected:
 
    SimObjectPtr<ShapeBase> mControlObject; ///< Controlling object
 
+   AIController* mAIController;
    /// @name Animation threads & data
    /// @{
 
@@ -754,6 +763,8 @@ public:
    void    setMomentum(const Point3F &momentum) override;
    bool    displaceObject(const Point3F& displaceVector) override;
    virtual bool    getAIMove(Move*);
+   bool setAIController(const char* controller);
+   AIController* getAIController() { return mAIController; };
 
    bool checkDismountPosition(const MatrixF& oldPos, const MatrixF& newPos);  ///< Is it safe to dismount here?
 

+ 2 - 2
Engine/source/T3D/shapeBase.h

@@ -1895,7 +1895,6 @@ public:
    void   registerCollisionCallback(CollisionEventCallback*);
    void   unregisterCollisionCallback(CollisionEventCallback*);
 
-protected:
    enum { 
       ANIM_OVERRIDDEN     = BIT(0),
       BLOCK_USER_CONTROL  = BIT(1),
@@ -1903,6 +1902,8 @@ protected:
       BAD_ANIM_ID         = 999999999,
       BLENDED_CLIP        = 0x80000000,
    };
+   U8 anim_clip_flags;
+protected:
    struct BlendThread
    {
       TSThread* thread;
@@ -1910,7 +1911,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;