Browse Source

completed list of roughly ported over scripthooks.
todo: need to figure out why followobject is only hitting the first path node. likely amixup with goal handling

AzaezelX 5 months ago
parent
commit
4fb92f02a3

+ 1 - 1
Engine/source/T3D/AI/AIAimTarget.h

@@ -28,7 +28,7 @@ struct AIAimTarget : AIInfo
    typedef AIInfo Parent;
    Point3F mAimOffset;
    bool mTargetInLOS;                  // Is target object visible?
-   Point3F getPosition() { return ((mObj) ? mObj->getPosition() : mPosition) + mAimOffset; }
+   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);

+ 80 - 7
Engine/source/T3D/AI/AIController.cpp

@@ -82,25 +82,25 @@ bool AIController::getAIMove(Move* movePtr)
    {
       if (mMovement.mMoveState != ModeStop)
          getNav()->updateNavMesh();
-      if (!getGoal()->mObj.isNull())
+      if (getGoal() && !getGoal()->mObj.isNull())
       {
          if (getNav()->mPathData.path.isNull())
          {
-            if (getGoal()->getDist() > mControllerData->mMoveTolerance)
-               getNav()->followObject(getGoal());
+            if (getGoal()->getDist() > mControllerData->mFollowTolerance)
+               getNav()->followObject(getGoal()->mObj, mControllerData->mFollowTolerance);
          }
          else
          {
-            if (getGoal()->getDist() > mControllerData->mMoveTolerance)
+            if (getGoal()->getDist() > mControllerData->mFollowTolerance)
                getNav()->repath();
 
-            if (getAim()->getDist() < mControllerData->mMoveTolerance)
+            if (getGoal()->getDist() < mControllerData->mFollowTolerance)
             {
                getNav()->clearPath();
                mMovement.mMoveState = ModeStop;
                throwCallback("onTargetInRange");
             }
-            else if (getAim()->getDist() < mControllerData->mAttackRadius)
+            else if (getGoal()->getDist() < mControllerData->mAttackRadius)
             {
                throwCallback("onTargetInFiringRange");
             }
@@ -178,11 +178,29 @@ bool AIController::getAIMove(Move* movePtr)
 void AIController::clearCover()
 {
    // Notify cover that we are no longer on our way.
-   if (!getCover()->mCoverPoint.isNull())
+   if (getCover() && !getCover()->mCoverPoint.isNull())
       getCover()->mCoverPoint->setOccupied(false);
    SAFE_DELETE(mCover);
 }
 
+void AIController::Movement::stopMove()
+{
+   mMoveState = ModeStop;
+#ifdef TORQUE_NAVIGATION_ENABLED
+   mControllerRef->getNav()->clearPath();
+   mControllerRef->clearCover();
+   mControllerRef->getNav()->clearFollow();
+#endif
+}
+void AIController::Movement::onStuck()
+{
+   mControllerRef->throwCallback("onMoveStuck");
+#ifdef TORQUE_NAVIGATION_ENABLED
+   if (!mControllerRef->getNav()->getPath().isNull())
+      mControllerRef->getNav()->repath();
+#endif
+}
+
 DefineEngineMethod(AIController, setMoveSpeed, void, (F32 speed), ,
    "@brief Sets the move speed for an AI object.\n\n"
 
@@ -205,6 +223,60 @@ DefineEngineMethod(AIController, getMoveSpeed, F32, (), ,
    return object->mMovement.getMoveSpeed();
 }
 
+DefineEngineMethod(AIController, stop, void, (), ,
+   "@brief Tells the AIPlayer 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);
@@ -341,6 +413,7 @@ void AIControllerData::resolveStuck(AIController* obj)
             if (obj->mMovement.mMoveState != AIController::ModeSlowing || locationDelta == 0)
             {
                obj->mMovement.mMoveState = AIController::ModeStuck;
+               obj->mMovement.onStuck();
                obj->throwCallback("onStuck");
             }
          }

+ 20 - 3
Engine/source/T3D/AI/AIController.h

@@ -29,7 +29,6 @@
 #include "AICover.h"
 #include "AINavigation.h"
 class AIControllerData;
-class AIController;
 
 //-----------------------------------------------------------------------------
 class AIController : public SimObject {
@@ -83,6 +82,7 @@ public:
    AINavigation* getNav() { return mNav; };
    struct Movement
    {
+      AIController* mControllerRef;
       MoveState mMoveState;
       F32 mMoveSpeed = 1.0;
       void setMoveSpeed(F32 speed) { mMoveSpeed = speed; };
@@ -99,6 +99,8 @@ public:
 
    struct TriggerState
    {
+      AIController* mControllerRef;
+      bool mMoveTriggers[MaxTriggerKeys];
       // Trigger sets/gets
       void setMoveTrigger(U32 slot, const bool isSet = true);
       bool getMoveTrigger(U32 slot) const;
@@ -109,12 +111,18 @@ public:
    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);
       mGoal = new AIGoal(this);
       mAimTarget = new AIAimTarget(this);
       mCover = new AICover(this);
       mNav = new AINavigation(this);
+      mMovement.mMoveState = ModeStop;
    };
 
    DECLARE_CONOBJECT(AIController);
@@ -127,7 +135,16 @@ class AIControllerData : public SimDataBlock {
 
 public:
 
-   AIControllerData() { mMoveTolerance = 0.25; mFollowTolerance = 1.0; mAttackRadius = 2.0; mMoveStuckTolerance = 0.01f; mMoveStuckTestDelay = 30;};
+   AIControllerData()
+   {
+      mMoveTolerance = 0.25;
+      mFollowTolerance = 1.0;
+      mAttackRadius = 2.0;
+      mMoveStuckTolerance = 0.01f;
+      mMoveStuckTestDelay = 30;
+      mLinkTypes = LinkData(AllFlags);
+      mNavSize = AINavigation::Regular;
+   };
    ~AIControllerData() {};
 
    static void initPersistFields();
@@ -140,7 +157,7 @@ public:
    S32 mMoveStuckTestDelay;            // The number of ticks to wait before checking if the AI is stuck
    /// Types of link we can use.
    LinkData mLinkTypes;
-
+   AINavigation::NavSize mNavSize;
    void resolveYaw(AIController* obj, Point3F location, Move* movePtr);
    void resolvePitch(AIController* obj, Point3F location, Move* movePtr) {};
    void resolveRoll(AIController* obj, Point3F location, Move* movePtr);

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

@@ -19,3 +19,90 @@
 // 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;
+   }
+}

+ 4 - 1
Engine/source/T3D/AI/AICover.h

@@ -23,6 +23,9 @@
 #define _AICOVER_H_
 
 #include "AIInfo.h"
+#include "navigation/coverPoint.h"
+
+
 
 struct AICover : AIInfo
 {
@@ -30,7 +33,7 @@ struct AICover : AIInfo
    /// 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, 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) {};
 };
 

+ 1 - 1
Engine/source/T3D/AI/AIInfo.cpp

@@ -53,7 +53,7 @@ AIInfo::AIInfo(AIController* controller, Point3F pointIn, F32 radIn)
 F32 AIInfo::getDist()
 {
    AIInfo* controlObj = getCtrl()->getAIInfo();
-   F32 ret = VectorF(controlObj->mObj->getPosition() - getPosition()).len();
+   F32 ret = VectorF(controlObj->getPosition() - getPosition()).len();
    ret -= controlObj->mRadius + mRadius;
    return ret;
 }

+ 1 - 1
Engine/source/T3D/AI/AIInfo.h

@@ -36,7 +36,7 @@ struct AIInfo
    Point3F mPosition, mLastPos;
    bool mPosSet;
    F32 mRadius;
-   Point3F getPosition() { return (mObj) ? mObj->getPosition() : mPosition; }
+   Point3F getPosition() { return (mObj.isValid()) ? mObj->getPosition() : mPosition; }
    F32 getDist();
    AIInfo() = delete;
    AIInfo(AIController* controller);

+ 124 - 7
Engine/source/T3D/AI/AINavigation.cpp

@@ -26,6 +26,7 @@ AINavigation::AINavigation(AIController* controller)
 {
    mControllerRef = controller;
    mJump = None;
+   mNavSize = Regular;
 }
 
 NavMesh* AINavigation::findNavMesh() const
@@ -40,6 +41,19 @@ NavMesh* AINavigation::findNavMesh() const
       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;
       }
@@ -101,6 +115,8 @@ void AINavigation::repath()
    if (mPathData.path.isNull() || !mPathData.owned)
       return;
 
+   if (!mControllerRef->getGoal()) return;
+
    // If we're following, get their position.
    mPathData.path->mTo = mControllerRef->getGoal()->getPosition();
    // Update from position and replan.
@@ -212,24 +228,21 @@ bool AINavigation::setPathDestination(const Point3F& pos)
    }
 }
 
-void AINavigation::followObject(AIInfo* targ)
+void AINavigation::followObject()
 {
-   if (!targ) return;
-
-   if (targ->getDist() < mControllerRef->mControllerData->mMoveTolerance)
+   if ((mControllerRef->getGoal()->mLastPos - mControllerRef->getAIInfo()->getPosition()).len() < mControllerRef->mControllerData->mMoveTolerance)
       return;
 
-   if (setPathDestination(targ->getPosition()))
+   if (setPathDestination(mControllerRef->getGoal()->getPosition()))
    {
       mControllerRef->clearCover();
-      mControllerRef->setGoal(targ);
    }
 }
 
 void AINavigation::followObject(SceneObject* obj, F32 radius)
 {
    mControllerRef->setGoal(obj, radius);
-   followObject(mControllerRef->getGoal());
+   followObject();
 }
 
 void AINavigation::clearFollow()
@@ -289,3 +302,107 @@ DefineEngineMethod(AIController, getMoveDestination, Point3F, (), ,
    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);
+}
+
+
+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, 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, 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;
+   object->getNav()->clearPath();
+   object->clearCover();
+   object->getNav()->clearFollow();
+
+   if (Sim::findObject(obj, follow))
+      object->getNav()->followObject(follow, radius);
+}
+
+
+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 "";
+}

+ 10 - 1
Engine/source/T3D/AI/AINavigation.h

@@ -59,6 +59,14 @@ struct AINavigation
       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; }
+
    Point3F mMoveDestination;
    void setMoveDestination(const Point3F& location, bool slowdown);
    Point3F getMoveDestination() { return mMoveDestination; };
@@ -68,6 +76,7 @@ struct AINavigation
    SimObjectPtr<NavMesh> mNavMesh;
    NavMesh* findNavMesh() const;
    void updateNavMesh();
+   NavMesh* getNavMesh() const { return mNavMesh; }
    PathData mPathData;
    JumpStates mJump;
 
@@ -81,7 +90,7 @@ struct AINavigation
    SimObjectPtr<NavPath> getPath() { return mPathData.path; };
    void followNavPath(NavPath* path);
 
-   void followObject(AIInfo* targ);
+   void followObject();
    void followObject(SceneObject* obj, F32 radius);
    void clearFollow();
    /// Move to the specified node in the current path.

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

@@ -2259,14 +2259,14 @@ void Player::advanceTime(F32 dt)
    }
 }
 
-bool Player::setAIController(S32 controller)
+bool Player::setAIController(SimObjectId controller)
 {
    if (Sim::findObject(controller, mAIController))
    {
       mAIController->setAIInfo(this);
       return true;
    }
-
+   Con::errorf("unable to find AIController : %i", controller);
    mAIController = NULL;
    return false;
 }

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

@@ -763,7 +763,7 @@ public:
    void    setMomentum(const Point3F &momentum) override;
    bool    displaceObject(const Point3F& displaceVector) override;
    virtual bool    getAIMove(Move*);
-   bool setAIController(S32 controller);
+   bool setAIController(SimObjectId controller);
    AIController* getAIController() { return mAIController; };
 
    bool checkDismountPosition(const MatrixF& oldPos, const MatrixF& newPos);  ///< Is it safe to dismount here?

+ 38 - 10
Engine/source/navigation/guiNavEditorCtrl.cpp

@@ -225,8 +225,18 @@ 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;
+      Player* po = dynamic_cast<Player*>(obj);
+      if (!po) return; //todo, more types
+      if (po->getAIController())
+      {
+         if (po->getAIController()->mControllerData)
+            Con::executef(this, "onPlayerSelected", Con::getIntArg(po->getAIController()->mControllerData->mLinkTypes.getFlags()));
+      }
+      else
+      {
+         Con::executef(this, "onPlayerSelected");
+      }
    }
 }
 
@@ -383,16 +393,34 @@ 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(ri.object)
+            {
+               mPlayer = ri.object;
+               Player* po = dynamic_cast<Player*>(ri.object);
+               if (!po) return; //todo, more types
+               if (po->getAIController())
+               {
+                  if (po->getAIController()->mControllerData)
+                     Con::executef(this, "onPlayerSelected", Con::getIntArg(po->getAIController()->mControllerData->mLinkTypes.getFlags()));
+               }
+               else
+               {
+                  Con::executef(this, "onPlayerSelected");
+               }
+            }
+         }
+         else if (!mPlayer.isNull() && gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
          {
-            if(dynamic_cast<AIPlayer*>(ri.object))
+            Player* po = dynamic_cast<Player*>(mPlayer.getPointer());
+            if (!po) return; //todo, more types
+            if (po->getAIController())
             {
-               mPlayer = dynamic_cast<AIPlayer*>(ri.object);
-               Con::executef(this, "onPlayerSelected", Con::getIntArg(mPlayer->mLinkTypes.getFlags()));
+               if (po->getAIController()->mControllerData)
+                  po->getAIController()->getNav()->setPathDestination(ri.point);
             }
          }
-         else if(!mPlayer.isNull() && gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
-            mPlayer->setPathDestination(ri.point);
       }
    }
 }
@@ -455,8 +483,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;
    }

+ 2 - 2
Engine/source/navigation/guiNavEditorCtrl.h

@@ -155,8 +155,8 @@ protected:
    /// @name Test mode
    /// @{
 
-   SimObjectPtr<AIPlayer> mPlayer;
-   SimObjectPtr<AIPlayer> mCurPlayer;
+   SimObjectPtr<SceneObject> mPlayer;
+   SimObjectPtr<SceneObject> mCurPlayer;
 
    /// @}
 

+ 5 - 0
Templates/BaseGame/game/core/gameObjects/datablocks/defaultDatablocks.tscript

@@ -169,3 +169,8 @@ datablock LightAnimData( SpinLightAnim )
    rotKeys[2] = "az";
    rotSmooth[2] = true;
 };
+
+datablock AIPlayerControllerData( aiPlayerControl )
+{
+
+};

+ 2 - 1
Templates/BaseGame/game/tools/navEditor/main.tscript

@@ -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();
 }
@@ -240,7 +241,7 @@ function NavEditorPlugin::initSettings(%this)
 {
    EditorSettings.beginGroup("NavEditor", true);
 
-   EditorSettings.setDefaultValue("SpawnClass",     "AIPlayer");
+   EditorSettings.setDefaultValue("SpawnClass",     "Player");
    EditorSettings.setDefaultValue("SpawnDatablock", "DefaultPlayerData");
 
    EditorSettings.endGroup();

+ 8 - 2
Templates/BaseGame/game/tools/navEditor/navEditor.tscript

@@ -459,6 +459,12 @@ function NavEditorGui::onLinkSelected(%this, %flags)
 
 function NavEditorGui::onPlayerSelected(%this, %flags)
 {
+   if (!isObject(%this.getPlayer().aiController))
+   {
+      %this.getPlayer().aiController = new AIController(){ ControllerData = aiPlayerControl; };
+      %this.getPlayer().setAIController(%this.getPlayer().aiController);
+   }
+   NavMeshIgnore(%this.getPlayer(), true);
    updateLinkData(NavEditorOptionsWindow-->TestProperties, %flags);
 }
 
@@ -526,7 +532,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,7 +553,7 @@ 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());
    }
 }