2
0
Эх сурвалжийг харах

Merge remote-tracking branch 'upstream/development' into imageAsset_refactor_rev3

marauder2k7 7 сар өмнө
parent
commit
e1c01cd49a
100 өөрчлөгдсөн 3970 нэмэгдсэн , 400 устгасан
  1. 1 1
      Engine/source/CMakeLists.txt
  2. 226 0
      Engine/source/T3D/AI/AIAimTarget.cpp
  3. 41 0
      Engine/source/T3D/AI/AIAimTarget.h
  4. 916 0
      Engine/source/T3D/AI/AIController.cpp
  5. 247 0
      Engine/source/T3D/AI/AIController.h
  6. 108 0
      Engine/source/T3D/AI/AICover.cpp
  7. 41 0
      Engine/source/T3D/AI/AICover.h
  8. 24 0
      Engine/source/T3D/AI/AIGoal.cpp
  9. 36 0
      Engine/source/T3D/AI/AIGoal.h
  10. 81 0
      Engine/source/T3D/AI/AIInfo.cpp
  11. 46 0
      Engine/source/T3D/AI/AIInfo.h
  12. 574 0
      Engine/source/T3D/AI/AINavigation.cpp
  13. 105 0
      Engine/source/T3D/AI/AINavigation.h
  14. 16 16
      Engine/source/T3D/SubScene.cpp
  15. 6 6
      Engine/source/T3D/SubScene.h
  16. 14 0
      Engine/source/T3D/assets/CppAsset.cpp
  17. 3 3
      Engine/source/T3D/assets/CppAsset.h
  18. 3 12
      Engine/source/T3D/assets/LevelAsset.cpp
  19. 0 3
      Engine/source/T3D/assets/LevelAsset.h
  20. 7 1
      Engine/source/T3D/assets/ShapeAsset.h
  21. 166 0
      Engine/source/T3D/assets/SubSceneAsset.cpp
  22. 113 0
      Engine/source/T3D/assets/SubSceneAsset.h
  23. 1 1
      Engine/source/T3D/camera.cpp
  24. 1 1
      Engine/source/T3D/debris.cpp
  25. 4 1
      Engine/source/T3D/debris.h
  26. 5 0
      Engine/source/T3D/fps/guiHealthBarHud.cpp
  27. 6 1
      Engine/source/T3D/fps/guiHealthTextHud.cpp
  28. 1 1
      Engine/source/T3D/fx/explosion.cpp
  29. 4 1
      Engine/source/T3D/fx/explosion.h
  30. 4 1
      Engine/source/T3D/fx/groundCover.cpp
  31. 7 1
      Engine/source/T3D/fx/groundCover.h
  32. 1 1
      Engine/source/T3D/fx/lightning.cpp
  33. 1 1
      Engine/source/T3D/fx/particleEmitter.cpp
  34. 1 1
      Engine/source/T3D/fx/particleEmitterNode.cpp
  35. 1 1
      Engine/source/T3D/fx/precipitation.cpp
  36. 1 1
      Engine/source/T3D/fx/ribbonNode.cpp
  37. 1 1
      Engine/source/T3D/fx/splash.cpp
  38. 3 3
      Engine/source/T3D/gameBase/gameBase.cpp
  39. 2 2
      Engine/source/T3D/gameBase/gameBase.h
  40. 1 0
      Engine/source/T3D/gameFunctions.cpp
  41. 1 1
      Engine/source/T3D/item.cpp
  42. 1 1
      Engine/source/T3D/missionMarker.cpp
  43. 1 1
      Engine/source/T3D/objectTypes.h
  44. 1 1
      Engine/source/T3D/pathCamera.cpp
  45. 1 1
      Engine/source/T3D/pathShape.cpp
  46. 4 1
      Engine/source/T3D/physics/physicsDebris.h
  47. 4 1
      Engine/source/T3D/physics/physicsShape.h
  48. 5 11
      Engine/source/T3D/player.cpp
  49. 13 4
      Engine/source/T3D/player.h
  50. 31 6
      Engine/source/T3D/projectile.cpp
  51. 6 3
      Engine/source/T3D/projectile.h
  52. 1 1
      Engine/source/T3D/proximityMine.cpp
  53. 1 1
      Engine/source/T3D/rigidShape.cpp
  54. 70 3
      Engine/source/T3D/shapeBase.cpp
  55. 20 5
      Engine/source/T3D/shapeBase.h
  56. 1 1
      Engine/source/T3D/staticShape.cpp
  57. 1 1
      Engine/source/T3D/trigger.cpp
  58. 1 1
      Engine/source/T3D/turret/aiTurretShape.cpp
  59. 4 1
      Engine/source/T3D/turret/turretShape.cpp
  60. 1 0
      Engine/source/T3D/turret/turretShape.h
  61. 1 1
      Engine/source/T3D/vehicles/flyingVehicle.cpp
  62. 1 1
      Engine/source/T3D/vehicles/hoverVehicle.cpp
  63. 19 1
      Engine/source/T3D/vehicles/vehicle.cpp
  64. 6 1
      Engine/source/T3D/vehicles/vehicle.h
  65. 1 1
      Engine/source/T3D/vehicles/wheeledVehicle.cpp
  66. 4 1
      Engine/source/T3D/vehicles/wheeledVehicle.h
  67. 4 1
      Engine/source/afx/afxMagicMissile.h
  68. 1 1
      Engine/source/afx/afxSpellBook.cpp
  69. 4 1
      Engine/source/afx/ce/afxModel.h
  70. 4 4
      Engine/source/afx/ce/afxParticleEmitter.cpp
  71. 47 0
      Engine/source/assets/assetManager.cpp
  72. 3 0
      Engine/source/assets/assetManager.h
  73. 14 0
      Engine/source/assets/assetManager_ScriptBinding.h
  74. 63 0
      Engine/source/console/propertyParsing.h
  75. 9 1
      Engine/source/console/sim.cpp
  76. 16 13
      Engine/source/console/simDatablock.cpp
  77. 2 0
      Engine/source/console/simDatablock.h
  78. 4 1
      Engine/source/console/simObject.cpp
  79. 4 1
      Engine/source/forest/forestItem.h
  80. 1 1
      Engine/source/gui/editor/popupMenu.cpp
  81. 304 246
      Engine/source/math/mathTypes.cpp
  82. 72 10
      Engine/source/navigation/guiNavEditorCtrl.cpp
  83. 2 2
      Engine/source/navigation/guiNavEditorCtrl.h
  84. 18 1
      Templates/BaseGame/game/core/clientServer/scripts/client/connectionToServer.tscript
  85. 3 1
      Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript
  86. 17 0
      Templates/BaseGame/game/core/gameObjects/datablocks/defaultDatablocks.tscript
  87. 2 4
      Templates/BaseGame/game/core/utility/scripts/gameObjectManagement.tscript
  88. 11 0
      Templates/BaseGame/game/data/DamageModel/DamageModel.module
  89. 48 0
      Templates/BaseGame/game/data/DamageModel/DamageModel.tscript
  90. 1 0
      Templates/BaseGame/game/data/DamageModel/guis/damageGuiOverlay.asset.taml
  91. 285 0
      Templates/BaseGame/game/data/DamageModel/guis/damageGuiOverlay.gui
  92. BIN
      Templates/BaseGame/game/data/DamageModel/images/crosshair.png
  93. BIN
      Templates/BaseGame/game/data/DamageModel/images/crosshair_blue.png
  94. 3 0
      Templates/BaseGame/game/data/DamageModel/images/crosshair_blue_image.asset.taml
  95. BIN
      Templates/BaseGame/game/data/DamageModel/images/damageBottom.png
  96. 3 0
      Templates/BaseGame/game/data/DamageModel/images/damageBottom_image.asset.taml
  97. BIN
      Templates/BaseGame/game/data/DamageModel/images/damageFront.png
  98. 3 0
      Templates/BaseGame/game/data/DamageModel/images/damageFront_image.asset.taml
  99. BIN
      Templates/BaseGame/game/data/DamageModel/images/damageLeft.png
  100. 3 0
      Templates/BaseGame/game/data/DamageModel/images/damageLeft_image.asset.taml

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

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

@@ -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 - 0
Engine/source/T3D/AI/AIAimTarget.h

@@ -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 - 0
Engine/source/T3D/AI/AIController.cpp

@@ -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 - 0
Engine/source/T3D/AI/AIController.h

@@ -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 - 0
Engine/source/T3D/AI/AICover.cpp

@@ -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 - 0
Engine/source/T3D/AI/AICover.h

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

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

@@ -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 - 0
Engine/source/T3D/AI/AIInfo.cpp

@@ -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 - 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 _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 - 0
Engine/source/T3D/AI/AINavigation.cpp

@@ -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 - 0
Engine/source/T3D/AI/AINavigation.h

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

+ 16 - 16
Engine/source/T3D/SubScene.cpp

@@ -24,7 +24,7 @@ IMPLEMENT_CALLBACK(SubScene, onUnloaded, void, (), (),
    "@brief Called when a subScene has been unloaded and has game mode implications.\n\n");
 
 SubScene::SubScene() :
-   mLevelAssetId(StringTable->EmptyString()),
+   mSubSceneAssetId(StringTable->EmptyString()),
    mGameModesNames(StringTable->EmptyString()),
    mScopeDistance(-1),
    mLoaded(false),
@@ -67,7 +67,7 @@ void SubScene::initPersistFields()
 {
    addGroup("SubScene");
    addField("isGlobalLayer", TypeBool, Offset(mGlobalLayer, SubScene), "");
-   INITPERSISTFIELD_LEVELASSET(Level, SubScene, "The level asset to load.");
+   INITPERSISTFIELD_SUBSCENEASSET(SubScene, SubScene, "The subscene asset to load.");
    addField("tickPeriodMS", TypeS32, Offset(mTickPeriodMS, SubScene), "evaluation rate (ms)");
    addField("gameModes", TypeGameModeList, Offset(mGameModesNames, SubScene), "The game modes that this subscene is associated with.");
    endGroup("SubScene");
@@ -265,13 +265,13 @@ void SubScene::processTick(const Move* move)
 
 void SubScene::_onFileChanged(const Torque::Path& path)
 {
-   if(mLevelAsset.isNull() || Torque::Path(mLevelAsset->getLevelPath()) != path)
+   if(mSubSceneAsset.isNull() || Torque::Path(mSubSceneAsset->getLevelPath()) != path)
       return;
 
    if (mSaving)
       return;
 
-   AssertFatal(path == mLevelAsset->getLevelPath(), "Prefab::_onFileChanged - path does not match filename.");
+   AssertFatal(path == mSubSceneAsset->getLevelPath(), "SubScene::_onFileChanged - path does not match filename.");
 
    _closeFile(false);
    _loadFile(false);
@@ -307,9 +307,9 @@ void SubScene::_closeFile(bool removeFileNotify)
 
    _removeContents(SimGroupIterator(this));
 
-   if (removeFileNotify && mLevelAsset.notNull() && mLevelAsset->getLevelPath() != StringTable->EmptyString())
+   if (removeFileNotify && mSubSceneAsset.notNull() && mSubSceneAsset->getLevelPath() != StringTable->EmptyString())
    {
-      Torque::FS::RemoveChangeNotification(mLevelAsset->getLevelPath(), this, &SubScene::_onFileChanged);
+      Torque::FS::RemoveChangeNotification(mSubSceneAsset->getLevelPath(), this, &SubScene::_onFileChanged);
    }
 
    mGameModesList.clear();
@@ -319,18 +319,18 @@ void SubScene::_loadFile(bool addFileNotify)
 {
    AssertFatal(isServerObject(), "Trying to load a SubScene file on the client is bad!");
 
-   if(mLevelAsset.isNull() || mLevelAsset->getLevelPath() == StringTable->EmptyString())
+   if(mSubSceneAsset.isNull() || mSubSceneAsset->getLevelPath() == StringTable->EmptyString())
       return;
 
-   String evalCmd = String::ToString("exec(\"%s\");", mLevelAsset->getLevelPath());
+   String evalCmd = String::ToString("exec(\"%s\");", mSubSceneAsset->getLevelPath());
 
    String instantGroup = Con::getVariable("InstantGroup");
    Con::setIntVariable("InstantGroup", this->getId());
-   Con::evaluate((const char*)evalCmd.c_str(), false, mLevelAsset->getLevelPath());
+   Con::evaluate((const char*)evalCmd.c_str(), false, mSubSceneAsset->getLevelPath());
    Con::setVariable("InstantGroup", instantGroup.c_str());
 
    if (addFileNotify)
-      Torque::FS::AddChangeNotification(mLevelAsset->getLevelPath(), this, &SubScene::_onFileChanged);
+      Torque::FS::AddChangeNotification(mSubSceneAsset->getLevelPath(), this, &SubScene::_onFileChanged);
 }
 
 void SubScene::load()
@@ -432,7 +432,7 @@ bool SubScene::save()
    if (size() == 0 || !isLoaded())
       return false;
 
-   if (mLevelAsset.isNull())
+   if (mSubSceneAsset.isNull())
       return false;
 
    if (mSaving)
@@ -446,7 +446,7 @@ bool SubScene::save()
 
    PersistenceManager prMger;
 
-   StringTableEntry levelPath = mLevelAsset->getLevelPath();
+   StringTableEntry levelPath = mSubSceneAsset->getLevelPath();
 
    FileStream fs;
    fs.open(levelPath, Torque::FS::File::Write);
@@ -460,7 +460,7 @@ bool SubScene::save()
       {
          if ((*itr)->isMethod("onSaving"))
          {
-            Con::executef((*itr), "onSaving", mLevelAssetId);
+            Con::executef((*itr), "onSaving", mSubSceneAssetId);
          }
 
          if (childObj->getGroup() == this)
@@ -481,14 +481,14 @@ bool SubScene::save()
    bool saveSuccess = false;
 
    //Get the level asset
-   if (mLevelAsset.isNull())
+   if (mSubSceneAsset.isNull())
       return saveSuccess;
 
    //update the gamemode list as well
-   mLevelAsset->setDataField(StringTable->insert("gameModesNames"), NULL, StringTable->insert(mGameModesNames));
+   mSubSceneAsset->setDataField(StringTable->insert("gameModesNames"), NULL, StringTable->insert(mGameModesNames));
 
    //Finally, save
-   saveSuccess = mLevelAsset->saveAsset();
+   saveSuccess = mSubSceneAsset->saveAsset();
 
    mSaving = false;
 

+ 6 - 6
Engine/source/T3D/SubScene.h

@@ -5,8 +5,8 @@
 #ifndef SCENE_GROUP_H
 #include "SceneGroup.h"
 #endif
-#ifndef LEVEL_ASSET_H
-#include "assets/LevelAsset.h"
+#ifndef SUBSCENE_ASSET_H
+#include "assets/SubSceneAsset.h"
 #endif
 
 class GameMode;
@@ -21,13 +21,13 @@ public:
       NextFreeMask = Parent::NextFreeMask << 0
    };
 
-   void onLevelChanged() {}
+   void onSubSceneChanged() {}
 
 protected:
    static bool smTransformChildren;
 
 private:
-   DECLARE_LEVELASSET(SubScene, Level, onLevelChanged);
+   DECLARE_SUBSCENEASSET(SubScene, SubScene, onSubSceneChanged);
 
    StringTableEntry mGameModesNames;
    Vector<GameMode*> mGameModesList;
@@ -61,7 +61,7 @@ public:
 
    static void initPersistFields();
    static void consoleInit();
-   StringTableEntry getTypeHint() const override { return (getLevelAsset()) ? getLevelAsset()->getAssetName() : StringTable->EmptyString(); }
+   StringTableEntry getTypeHint() const override { return (getSubSceneAsset()) ? getSubSceneAsset()->getAssetName() : StringTable->EmptyString(); }
 
    // SimObject
    bool onAdd() override;
@@ -122,6 +122,6 @@ public:
 
    DECLARE_CALLBACK(void, onLoaded, ());
    DECLARE_CALLBACK(void, onUnloaded, ());
-   DECLARE_ASSET_SETGET(SubScene, Level);
+   DECLARE_ASSET_SETGET(SubScene, SubScene);
 };
 #endif

+ 14 - 0
Engine/source/T3D/assets/CppAsset.cpp

@@ -178,3 +178,17 @@ void CppAsset::onAssetRefresh(void)
 
    mHeaderPath = getOwned() ? expandAssetFilePath(mHeaderFile) : mHeaderPath;
 }
+
+DefineEngineMethod(CppAsset, getCodePath, const char*, (), ,
+   "Gets the code file filepath of this asset.\n"
+   "@return File path of the code file.")
+{
+   return object->getCppFilePath();
+}
+
+DefineEngineMethod(CppAsset, getHeaderPath, const char*, (), ,
+   "Gets the header file filepath of this asset.\n"
+   "@return File path of the header file.")
+{
+   return object->getHeaderFilePath();
+}

+ 3 - 3
Engine/source/T3D/assets/CppAsset.h

@@ -66,6 +66,9 @@ public:
    void                    setHeaderFile(const char* pHeaderFile);
    inline StringTableEntry getHeaderFile(void) const { return mHeaderFile; };
 
+   inline StringTableEntry getCppFilePath(void) const { return mCodePath; };
+   inline StringTableEntry getHeaderFilePath(void) const { return mHeaderPath; };
+
 protected:
 	void            initializeAsset(void) override;
 	void            onAssetRefresh(void) override;
@@ -73,9 +76,6 @@ protected:
    static bool setCppFile(void *obj, const char *index, const char *data) { static_cast<CppAsset*>(obj)->setCppFile(data); return false; }
    static const char* getCppFile(void* obj, const char* data) { return static_cast<CppAsset*>(obj)->getCppFile(); }
 
-   inline StringTableEntry getCppFilePath(void) const { return mCodePath; };
-   inline StringTableEntry getHeaderFilePath(void) const { return mHeaderPath; };
-
    static bool setHeaderFile(void *obj, const char *index, const char *data) { static_cast<CppAsset*>(obj)->setHeaderFile(data); return false; }
    static const char* getHeaderFile(void* obj, const char* data) { return static_cast<CppAsset*>(obj)->getHeaderFile(); }
 };

+ 3 - 12
Engine/source/T3D/assets/LevelAsset.cpp

@@ -101,7 +101,7 @@ ConsoleSetType(TypeLevelAssetId)
 }
 //-----------------------------------------------------------------------------
 
-LevelAsset::LevelAsset() : AssetBase(), mIsSubLevel(false)
+LevelAsset::LevelAsset() : AssetBase()
 {
    mLevelName = StringTable->EmptyString();
    mLevelFile = StringTable->EmptyString();
@@ -117,7 +117,6 @@ LevelAsset::LevelAsset() : AssetBase(), mIsSubLevel(false)
    mNavmeshPath = StringTable->EmptyString();
 
    mGameModesNames = StringTable->EmptyString();
-   mMainLevelAsset = StringTable->EmptyString();
 
    mEditorFile = StringTable->EmptyString();
    mBakedSceneFile = StringTable->EmptyString();
@@ -158,7 +157,6 @@ void LevelAsset::initPersistFields()
    addProtectedField("BakedSceneFile", TypeAssetLooseFilePath, Offset(mBakedSceneFile, LevelAsset),
       &setBakedSceneFile, &getBakedSceneFile, "Path to the level file with the objects generated as part of the baking process");
 
-   addField("isSubScene", TypeBool, Offset(mIsSubLevel, LevelAsset), "Is this a sublevel to another Scene");
    addField("gameModesNames", TypeString, Offset(mGameModesNames, LevelAsset), "Name of the Game Mode to be used with this level");
 }
 
@@ -481,15 +479,8 @@ GuiControl* GuiInspectorTypeLevelAssetPtr::constructEditControl()
    // Create "Open in Editor" button
    mEditButton = new GuiBitmapButtonCtrl();
 
-   String setSubSceneValue = "$createLevelAssetIsSubScene = \"\";";
-   if(dynamic_cast<SubScene*>(mInspector->getInspectObject()) != NULL)
-   {
-      setSubSceneValue = "$createLevelAssetIsSubScene = true;";
-   }
-
-   dSprintf(szBuffer, sizeof(szBuffer), "$createAndAssignField = %s; %s AssetBrowser.setupCreateNewAsset(\"LevelAsset\", AssetBrowser.selectedModule, \"createAndAssignLevelAsset\");",
-      getIdString(),
-      setSubSceneValue.c_str());
+   dSprintf(szBuffer, sizeof(szBuffer), "$createAndAssignField = %s; AssetBrowser.setupCreateNewAsset(\"LevelAsset\", AssetBrowser.selectedModule, \"createAndAssignLevelAsset\");",
+      getIdString());
    mEditButton->setField("Command", szBuffer);
 
    char bitmapName[512] = "ToolsModule:iconAdd_image";

+ 0 - 3
Engine/source/T3D/assets/LevelAsset.h

@@ -66,9 +66,6 @@ class LevelAsset : public AssetBase
    StringTableEntry        mEditorFile;
    StringTableEntry        mBakedSceneFile;
 
-   bool                    mIsSubLevel;
-   StringTableEntry        mMainLevelAsset;
-
    StringTableEntry        mGameModesNames;
 
    Vector<AssetBase*>      mAssetDependencies;

+ 7 - 1
Engine/source/T3D/assets/ShapeAsset.h

@@ -370,7 +370,7 @@ public: \
 
 #pragma region Arrayed Asset Macros
 
-#define DECLARE_SHAPEASSET_ARRAY(className,name,max) public: \
+#define DECLARE_SHAPEASSET_ARRAY(className,name,max,changeFunc) public: \
    static const U32 sm##name##Count = max;\
    Resource<TSShape>m##name[max];\
    StringTableEntry m##name##Name[max]; \
@@ -384,6 +384,10 @@ public: \
    \
    bool _set##name(StringTableEntry _in, const U32& index)\
    {\
+      if (m##name##Asset[index].notNull())\
+      {\
+            m##name##Asset[index]->getChangedSignal().remove(this, &className::changeFunc);\
+      }\
       if(m##name##AssetId[index] != _in || m##name##Name[index] != _in)\
       {\
          if(index >= sm##name##Count || index < 0)\
@@ -430,6 +434,8 @@ public: \
       if (get##name(index) != StringTable->EmptyString() && m##name##Asset[index].notNull())\
       {\
          m##name[index] = m##name##Asset[index]->getShapeResource();\
+         \
+         m##name##Asset[index]->getChangedSignal().notify(this, &className::changeFunc);\
       }\
       else\
       {\

+ 166 - 0
Engine/source/T3D/assets/SubSceneAsset.cpp

@@ -0,0 +1,166 @@
+#include "SubSceneAsset.h"
+
+#include "T3D/SubScene.h"
+IMPLEMENT_CONOBJECT(SubSceneAsset);
+
+
+ConsoleType(SubSceneAssetPtr, TypeSubSceneAssetPtr, const char*, "")
+
+//-----------------------------------------------------------------------------
+
+ConsoleGetType(TypeSubSceneAssetPtr)
+{
+   // Fetch asset Id.
+   return *((const char**)(dptr));
+}
+
+//-----------------------------------------------------------------------------
+
+ConsoleSetType(TypeSubSceneAssetPtr)
+{
+   // Was a single argument specified?
+   if (argc == 1)
+   {
+      // Yes, so fetch field value.
+      *((const char**)dptr) = StringTable->insert(argv[0]);
+
+      return;
+   }
+
+   // Warn.
+   Con::warnf("(TypeSubSceneAssetPtr) - Cannot set multiple args to a single asset.");
+}
+
+//-----------------------------------------------------------------------------
+ConsoleType(assetIdString, TypeSubSceneAssetId, const char*, "")
+
+ConsoleGetType(TypeSubSceneAssetId)
+{
+   // Fetch asset Id.
+   return *((const char**)(dptr));
+}
+
+ConsoleSetType(TypeSubSceneAssetId)
+{
+   // Was a single argument specified?
+   if (argc == 1)
+   {
+      *((const char**)dptr) = StringTable->insert(argv[0]);
+
+      return;
+   }
+
+   // Warn.
+   Con::warnf("(TypeSubSceneAssetId) - Cannot set multiple args to a single asset.");
+}
+
+SubSceneAsset::SubSceneAsset() : LevelAsset()
+{
+}
+
+SubSceneAsset::~SubSceneAsset()
+{
+}
+
+void SubSceneAsset::initPersistFields()
+{
+   docsURL;
+   Parent::initPersistFields();
+}
+
+
+//-----------------------------------------------------------------------------
+// GuiInspectorTypeAssetId
+//-----------------------------------------------------------------------------
+
+IMPLEMENT_CONOBJECT(GuiInspectorTypeSubSceneAssetPtr);
+
+ConsoleDocClass(GuiInspectorTypeSubSceneAssetPtr,
+   "@brief Inspector field type for Shapes\n\n"
+   "Editor use only.\n\n"
+   "@internal"
+);
+
+void GuiInspectorTypeSubSceneAssetPtr::consoleInit()
+{
+   Parent::consoleInit();
+
+   ConsoleBaseType::getType(TypeSubSceneAssetPtr)->setInspectorFieldType("GuiInspectorTypeSubSceneAssetPtr");
+}
+
+GuiControl* GuiInspectorTypeSubSceneAssetPtr::constructEditControl()
+{
+   // Create base filename edit controls
+   GuiControl* retCtrl = Parent::constructEditControl();
+   if (retCtrl == NULL)
+      return retCtrl;
+
+   // Change filespec
+   char szBuffer[512];
+   dSprintf(szBuffer, sizeof(szBuffer), "AssetBrowser.showDialog(\"SubSceneAsset\", \"AssetBrowser.changeAsset\", %s, \"\");",
+      getIdString());
+   mBrowseButton->setField("Command", szBuffer);
+
+   setDataField(StringTable->insert("targetObject"), NULL, mInspector->getInspectObject()->getIdString());
+
+   // Create "Open in Editor" button
+   mEditButton = new GuiBitmapButtonCtrl();
+
+   dSprintf(szBuffer, sizeof(szBuffer), "$createAndAssignField = %s; AssetBrowser.setupCreateNewAsset(\"SubSceneAsset\", AssetBrowser.selectedModule);",
+      getIdString());
+   mEditButton->setField("Command", szBuffer);
+
+   char bitmapName[512] = "ToolsModule:iconAdd_image";
+   mEditButton->setBitmap(StringTable->insert(bitmapName));
+
+   mEditButton->setDataField(StringTable->insert("Profile"), NULL, "GuiButtonProfile");
+   mEditButton->setDataField(StringTable->insert("tooltipprofile"), NULL, "GuiToolTipProfile");
+   mEditButton->setDataField(StringTable->insert("hovertime"), NULL, "1000");
+   mEditButton->setDataField(StringTable->insert("tooltip"), NULL, "Test play this sound");
+
+   mEditButton->registerObject();
+   addObject(mEditButton);
+
+   return retCtrl;
+}
+
+bool GuiInspectorTypeSubSceneAssetPtr::updateRects()
+{
+   S32 dividerPos, dividerMargin;
+   mInspector->getDivider(dividerPos, dividerMargin);
+   Point2I fieldExtent = getExtent();
+   Point2I fieldPos = getPosition();
+
+   mCaptionRect.set(0, 0, fieldExtent.x - dividerPos - dividerMargin, fieldExtent.y);
+   mEditCtrlRect.set(fieldExtent.x - dividerPos + dividerMargin, 1, dividerPos - dividerMargin - 34, fieldExtent.y);
+
+   bool resized = mEdit->resize(mEditCtrlRect.point, mEditCtrlRect.extent);
+   if (mBrowseButton != NULL)
+   {
+      mBrowseRect.set(fieldExtent.x - 32, 2, 14, fieldExtent.y - 4);
+      resized |= mBrowseButton->resize(mBrowseRect.point, mBrowseRect.extent);
+   }
+
+   if (mEditButton != NULL)
+   {
+      RectI shapeEdRect(fieldExtent.x - 16, 2, 14, fieldExtent.y - 4);
+      resized |= mEditButton->resize(shapeEdRect.point, shapeEdRect.extent);
+   }
+
+   return resized;
+}
+
+IMPLEMENT_CONOBJECT(GuiInspectorTypeSubSceneAssetId);
+
+ConsoleDocClass(GuiInspectorTypeSubSceneAssetId,
+   "@brief Inspector field type for SubScene\n\n"
+   "Editor use only.\n\n"
+   "@internal"
+);
+
+void GuiInspectorTypeSubSceneAssetId::consoleInit()
+{
+   Parent::consoleInit();
+
+   ConsoleBaseType::getType(TypeSubSceneAssetId)->setInspectorFieldType("GuiInspectorTypeSubSceneAssetId");
+}

+ 113 - 0
Engine/source/T3D/assets/SubSceneAsset.h

@@ -0,0 +1,113 @@
+#pragma once
+#ifndef SUBSCENE_ASSET_H
+#define SUBSCENE_ASSET_H
+
+#ifndef LEVEL_ASSET_H
+#include "LevelAsset.h"
+#endif
+
+#ifndef _ASSET_DEFINITION_H_
+#include "assets/assetDefinition.h"
+#endif
+
+#ifndef _GUI_INSPECTOR_TYPES_H_
+#include "gui/editor/guiInspectorTypes.h"
+#endif
+
+class SubSceneAsset : public LevelAsset
+{
+   typedef LevelAsset Parent;
+
+public:
+   SubSceneAsset();
+   virtual ~SubSceneAsset();
+
+   /// Engine.
+   static void initPersistFields();
+
+   /// Declare Console Object.
+   DECLARE_CONOBJECT(SubSceneAsset);
+};
+
+#ifdef TORQUE_TOOLS
+class GuiInspectorTypeSubSceneAssetPtr : public GuiInspectorTypeFileName
+{
+   typedef GuiInspectorTypeFileName Parent;
+public:
+
+   GuiBitmapButtonCtrl* mEditButton;
+
+   DECLARE_CONOBJECT(GuiInspectorTypeSubSceneAssetPtr);
+   static void consoleInit();
+
+   GuiControl* constructEditControl() override;
+   bool updateRects() override;
+};
+
+class GuiInspectorTypeSubSceneAssetId : public GuiInspectorTypeSubSceneAssetPtr
+{
+   typedef GuiInspectorTypeSubSceneAssetPtr Parent;
+public:
+
+   DECLARE_CONOBJECT(GuiInspectorTypeSubSceneAssetId);
+   static void consoleInit();
+};
+#endif
+
+DefineConsoleType(TypeSubSceneAssetPtr, SubSceneAsset)
+DefineConsoleType(TypeSubSceneAssetId, String)
+
+#pragma region Singular Asset Macros
+
+//Singular assets
+/// <Summary>
+/// Declares an SubScene asset
+/// This establishes the assetId, asset and legacy filepath fields, along with supplemental getter and setter functions
+/// </Summary>
+#define DECLARE_SUBSCENEASSET(className, name, changeFunc) public: \
+   StringTableEntry m##name##AssetId;\
+   AssetPtr<SubSceneAsset>  m##name##Asset;\
+public: \
+   const AssetPtr<SubSceneAsset> & get##name##Asset() const { return m##name##Asset; }\
+   void set##name##Asset(const AssetPtr<SubSceneAsset> &_in) { m##name##Asset = _in;}\
+   \
+   bool _set##name(StringTableEntry _in)\
+   {\
+      if(m##name##AssetId != _in)\
+      {\
+         if (m##name##Asset.notNull())\
+         {\
+            m##name##Asset->getChangedSignal().remove(this, &className::changeFunc);\
+         }\
+         if (_in == NULL || _in == StringTable->EmptyString())\
+         {\
+            m##name##AssetId = StringTable->EmptyString();\
+            m##name##Asset = NULL;\
+            return true;\
+         }\
+         if (AssetDatabase.isDeclaredAsset(_in))\
+         {\
+            m##name##AssetId = _in;\
+            m##name##Asset = _in;\
+            return true;\
+         }\
+      }\
+      \
+      if(get##name() == StringTable->EmptyString())\
+         return true;\
+      \
+      return false;\
+   }\
+   \
+   const StringTableEntry get##name() const\
+   {\
+      return m##name##AssetId;\
+   }\
+   bool name##Valid() {return (get##name() != StringTable->EmptyString() && m##name##Asset->getStatus() == AssetBase::Ok); }
+
+#define INITPERSISTFIELD_SUBSCENEASSET(name, consoleClass, docs) \
+   addProtectedField(assetText(name, Asset), TypeSubSceneAssetId, Offset(m##name##AssetId, consoleClass), _set##name##Data, &defaultProtectedGetFn, assetDoc(name, asset docs.));
+
+#pragma endregion
+
+#endif // SUBSCENE_ASSET_H

+ 1 - 1
Engine/source/T3D/camera.cpp

@@ -345,7 +345,7 @@ bool Camera::onNewDataBlock( GameBaseData *dptr, bool reload )
    if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
       return false;
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
 
    return true;
 }

+ 1 - 1
Engine/source/T3D/debris.cpp

@@ -601,7 +601,7 @@ bool Debris::onNewDataBlock( GameBaseData *dptr, bool reload )
 
    if (mDataBlock->isTempClone())
       return true;
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 
 }

+ 4 - 1
Engine/source/T3D/debris.h

@@ -111,7 +111,10 @@ public:
    void onPerformSubstitutions() override;
    bool allowSubstitutions() const override { return true; }
 
-   void onShapeChanged() {}
+   void onShapeChanged()
+   {
+      reloadOnLocalClient();
+   }
 };
 
 //**************************************************************************

+ 5 - 0
Engine/source/T3D/fps/guiHealthBarHud.cpp

@@ -147,6 +147,11 @@ void GuiHealthBarHud::onRender(Point2I offset, const RectI &updateRect)
    if (!conn)
       return;
    ShapeBase* control = dynamic_cast<ShapeBase*>(conn->getControlObject());
+
+   //cover the case of a connection controling an object in turn controlling another
+   if (control && control->getControlObject())
+      control = control->getControlObject();
+
    if (!control || !(control->getTypeMask() & (PlayerObjectType | VehicleObjectType)))
       return;
 

+ 6 - 1
Engine/source/T3D/fps/guiHealthTextHud.cpp

@@ -148,7 +148,12 @@ void GuiHealthTextHud::onRender(Point2I offset, const RectI &updateRect)
    GameConnection* conn = GameConnection::getConnectionToServer();  
    if (!conn)  
       return;  
-   ShapeBase* control = dynamic_cast<ShapeBase*>(conn->getControlObject());  
+   ShapeBase* control = dynamic_cast<ShapeBase*>(conn->getControlObject());
+
+   //cover the case of a connection controling an object in turn controlling another
+   if (control && control->getControlObject())
+      control = control->getControlObject();
+
    if (!control || !(control->getTypeMask() & (PlayerObjectType | VehicleObjectType)))
       return;  
   

+ 1 - 1
Engine/source/T3D/fx/explosion.cpp

@@ -1127,7 +1127,7 @@ bool Explosion::onNewDataBlock( GameBaseData *dptr, bool reload )
 
    if (mDataBlock->isTempClone())
       return true;
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 4 - 1
Engine/source/T3D/fx/explosion.h

@@ -143,7 +143,10 @@ public:
    ExplosionData* cloneAndPerformSubstitutions(const SimObject*, S32 index=0);
    bool   allowSubstitutions() const override { return true; }
 
-   void onShapeChanged() {}
+   void onShapeChanged()
+   {
+      reloadOnLocalClient();
+   }
 };
 
 

+ 4 - 1
Engine/source/T3D/fx/groundCover.cpp

@@ -852,8 +852,11 @@ void GroundCover::unpackUpdate( NetConnection *connection, BitStream *stream )
       // It's sloppy, but it works for now.
       _freeCells();
 
-      if ( isProperlyAdded() )
+      if (isProperlyAdded())
+      {
          _initMaterial();
+         _initShapes();
+      }
    }
 }
 

+ 7 - 1
Engine/source/T3D/fx/groundCover.h

@@ -341,7 +341,7 @@ protected:
    RectF mBillboardRects[MAX_COVERTYPES];
 
    /// The cover shape filenames.
-   DECLARE_SHAPEASSET_ARRAY(GroundCover, Shape, MAX_COVERTYPES);
+   DECLARE_SHAPEASSET_ARRAY(GroundCover, Shape, MAX_COVERTYPES, onShapeChanged);
    DECLARE_ASSET_ARRAY_NET_SETGET(GroundCover, Shape, -1);
 
    /// The cover shape instances.
@@ -409,6 +409,12 @@ protected:
                                     S32 randSeed );
 
    void _debugRender( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat );
+
+   void onShapeChanged()
+   {
+      _initShapes();
+      setMaskBits(U32(-1));
+   }
 };
 
 #endif // _GROUNDCOVER_H_

+ 1 - 1
Engine/source/T3D/fx/lightning.cpp

@@ -474,7 +474,7 @@ bool Lightning::onNewDataBlock( GameBaseData *dptr, bool reload )
    if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
       return false;
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 1 - 1
Engine/source/T3D/fx/particleEmitter.cpp

@@ -1134,7 +1134,7 @@ bool ParticleEmitter::onNewDataBlock( GameBaseData *dptr, bool reload )
      return true;
    }
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 1 - 1
Engine/source/T3D/fx/particleEmitterNode.cpp

@@ -254,7 +254,7 @@ bool ParticleEmitterNode::onNewDataBlock( GameBaseData *dptr, bool reload )
       return false;
 
    // Todo: Uncomment if this is a "leaf" class
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 1 - 1
Engine/source/T3D/fx/precipitation.cpp

@@ -598,7 +598,7 @@ bool Precipitation::onNewDataBlock( GameBaseData *dptr, bool reload )
       initMaterials();
    }
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 1 - 1
Engine/source/T3D/fx/ribbonNode.cpp

@@ -159,7 +159,7 @@ bool RibbonNode::onNewDataBlock( GameBaseData *dptr, bool reload )
       return false;
 
    // Todo: Uncomment if this is a "leaf" class
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 1 - 1
Engine/source/T3D/fx/splash.cpp

@@ -440,7 +440,7 @@ bool Splash::onNewDataBlock( GameBaseData *dptr, bool reload )
    if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
       return false;
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 3 - 3
Engine/source/T3D/gameBase/gameBase.cpp

@@ -95,7 +95,7 @@ IMPLEMENT_CALLBACK( GameBaseData, onAdd, void, ( GameBase* obj ), ( obj ),
       "}\n\n"
    "@endtsexample\n" );
 
-IMPLEMENT_CALLBACK( GameBaseData, onNewDataBlock, void, ( GameBase* obj ), ( obj ),
+IMPLEMENT_CALLBACK( GameBaseData, onNewDataBlock, void, ( GameBase* obj, bool reload), ( obj, reload),
    "@brief Called when the object has a new datablock assigned.\n\n"
    "@param obj the GameBase object\n\n"
    "@see onAdd for an example\n" );
@@ -512,12 +512,12 @@ void GameBase::scriptOnAdd()
       mDataBlock->onAdd_callback( this );
 }
 
-void GameBase::scriptOnNewDataBlock()
+void GameBase::scriptOnNewDataBlock(bool reload)
 {
    // Script onNewDataBlock() must be called by the leaf class
    // after everything is loaded.
    if (mDataBlock && !isGhost())
-      mDataBlock->onNewDataBlock_callback( this );
+      mDataBlock->onNewDataBlock_callback( this, reload);
 }
 
 void GameBase::scriptOnRemove()

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

@@ -115,7 +115,7 @@ public:
    /// @{
    DECLARE_CALLBACK( void, onAdd, ( GameBase* obj ) );
    DECLARE_CALLBACK( void, onRemove, ( GameBase* obj ) );
-   DECLARE_CALLBACK( void, onNewDataBlock, ( GameBase* obj ) );
+   DECLARE_CALLBACK( void, onNewDataBlock, ( GameBase* obj, bool reload) );
    DECLARE_CALLBACK( void, onMount, ( SceneObject* obj, SceneObject* mountObj, S32 node ) );
    DECLARE_CALLBACK( void, onUnmount, ( SceneObject* obj, SceneObject* mountObj, S32 node ) );
    /// @}
@@ -299,7 +299,7 @@ public:
    /// Executes the 'onNewDataBlock' script function for this object.
    ///
    /// @note This must be called after everything is loaded.
-   void scriptOnNewDataBlock();
+   void scriptOnNewDataBlock(bool reload = false);
 
    /// Executes the 'onRemove' script function for this object.
    /// @note This must be called while the object is still valid

+ 1 - 0
Engine/source/T3D/gameFunctions.cpp

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

+ 1 - 1
Engine/source/T3D/item.cpp

@@ -422,7 +422,7 @@ bool Item::onNewDataBlock( GameBaseData *dptr, bool reload )
       return false;
 
    if (!mSubclassItemHandlesScene)
-      scriptOnNewDataBlock();
+      scriptOnNewDataBlock(reload);
 
    if ( isProperlyAdded() )
       _updatePhysics();

+ 1 - 1
Engine/source/T3D/missionMarker.cpp

@@ -142,7 +142,7 @@ bool MissionMarker::onNewDataBlock( GameBaseData *dptr, bool reload )
    mDataBlock = dynamic_cast<MissionMarkerData*>( dptr );
    if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
       return(false);
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return(true);
 }
 

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

@@ -174,7 +174,7 @@ enum SceneObjectTypes
    /// @see TurretShape
    TurretObjectType = BIT(29),
    N_A_31 = BIT(30),
-   N_A_32 = BIT(31),
+   AIObjectType = BIT(31),
 
    /// @}
 };

+ 1 - 1
Engine/source/T3D/pathCamera.cpp

@@ -181,7 +181,7 @@ bool PathCamera::onNewDataBlock( GameBaseData *dptr, bool reload )
    if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
       return false;
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 1 - 1
Engine/source/T3D/pathShape.cpp

@@ -134,7 +134,7 @@ bool PathShape::onNewDataBlock(GameBaseData* dptr, bool reload)
    if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
       return false;
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 4 - 1
Engine/source/T3D/physics/physicsDebris.h

@@ -97,7 +97,10 @@ public:
    void        packData( BitStream *stream ) override;
    void        unpackData( BitStream *stream ) override;
 
-   void onShapeChanged() {}
+   void onShapeChanged()
+   {
+      reloadOnLocalClient();
+   }
 
    DECLARE_CONOBJECT( PhysicsDebrisData );
 

+ 4 - 1
Engine/source/T3D/physics/physicsShape.h

@@ -135,7 +135,10 @@ public:
    SimObjectRef< ExplosionData > explosion;   
    SimObjectRef< PhysicsShapeData > destroyedShape;
 
-   void onShapeChanged() {}
+   void onShapeChanged()
+   {
+      reloadOnLocalClient();
+   }
 };
 
 typedef PhysicsShapeData::SimType PhysicsSimType;

+ 5 - 11
Engine/source/T3D/player.cpp

@@ -460,7 +460,7 @@ PlayerData::PlayerData()
    jumpTowardsNormal = true;
 
    physicsPlayerType = StringTable->EmptyString();
-
+   mControlMap = StringTable->EmptyString();
    dMemset( actionList, 0, sizeof(actionList) );
 }
 
@@ -739,7 +739,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 "
@@ -1640,7 +1642,6 @@ Player::Player()
    mLastAbsoluteYaw = 0.0f;
    mLastAbsolutePitch = 0.0f;
    mLastAbsoluteRoll = 0.0f;
-   
    afx_init();
 }
 
@@ -1738,7 +1739,6 @@ bool Player::onAdd()
                            world );
       mPhysicsRep->setTransform( getTransform() );
    }
-
    return true;
 }
 
@@ -1923,7 +1923,7 @@ bool Player::onNewDataBlock( GameBaseData *dptr, bool reload )
    onScaleChanged();
    resetWorldBox();
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 
@@ -2255,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) {

+ 13 - 4
Engine/source/T3D/player.h

@@ -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 {
@@ -76,7 +82,7 @@ struct PlayerData: public ShapeBaseData {
                                                                   ///  that we don't create a TSThread on the player if we don't
                                                                   ///  need to.
 
-   DECLARE_SHAPEASSET_ARRAY(PlayerData, ShapeFP, ShapeBase::MaxMountedImages); ///< Used to render with mounted images in first person [optional]
+   DECLARE_SHAPEASSET_ARRAY(PlayerData, ShapeFP, ShapeBase::MaxMountedImages, onShapeChanged); ///< Used to render with mounted images in first person [optional]
    DECLARE_ASSET_ARRAY_SETGET(PlayerData, ShapeFP);
 
    StringTableEntry  imageAnimPrefixFP;                           ///< Passed along to mounted images to modify
@@ -346,7 +352,7 @@ struct PlayerData: public ShapeBaseData {
 
    // Jump off surfaces at their normal rather than straight up
    bool jumpTowardsNormal;
-
+   StringTableEntry mControlMap;
    // For use if/when mPhysicsPlayer is created
    StringTableEntry physicsPlayerType;
 
@@ -366,6 +372,11 @@ struct PlayerData: public ShapeBaseData {
    void packData(BitStream* stream) override;
    void unpackData(BitStream* stream) override;
 
+   void onShapeChanged()
+   {
+      reloadOnLocalClient();
+   }
+
    /// @name Callbacks
    /// @{
    DECLARE_CALLBACK( void, onPoseChange, ( Player* obj, const char* oldPose, const char* newPose ) );
@@ -753,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?
 
    //

+ 31 - 6
Engine/source/T3D/projectile.cpp

@@ -56,6 +56,7 @@
 #include "T3D/decal/decalData.h"
 #include "T3D/lightDescription.h"
 #include "console/engineAPI.h"
+#include "T3D/rigidShape.h"
 
 
 IMPLEMENT_CO_DATABLOCK_V1(ProjectileData);
@@ -163,6 +164,7 @@ ProjectileData::ProjectileData()
    scale.set( 1.0f, 1.0f, 1.0f );
 
    isBallistic = false;
+   mExplodeOnTmeout = false;
 
 	velInheritFactor = 1.0f;
 	muzzleVelocity = 50;
@@ -203,6 +205,7 @@ ProjectileData::ProjectileData(const ProjectileData& other, bool temp_clone) : G
    muzzleVelocity = other.muzzleVelocity;
    impactForce = other.impactForce;
    isBallistic = other.isBallistic;
+   mExplodeOnTmeout = other.mExplodeOnTmeout;
    bounceElasticity = other.bounceElasticity;
    bounceFriction = other.bounceFriction;
    gravityMod = other.gravityMod;
@@ -285,6 +288,8 @@ void ProjectileData::initPersistFields()
       addProtectedFieldV("fadeDelay", TypeRangedS32, Offset(fadeDelay, ProjectileData), &setFadeDelay, &getScaledValue, &CommonValidators::NaturalNumber,
          "@brief Amount of time, in milliseconds, before the projectile begins to fade out.\n\n"
          "This value must be smaller than the projectile's lifetime to have an affect.");
+      addField("explodeOnTmeout", TypeBool, Offset(mExplodeOnTmeout, ProjectileData),
+         "@brief Detetmines if the projectile should explode on timeout");      
       addField("isBallistic", TypeBool, Offset(isBallistic, ProjectileData),
          "@brief Detetmines if the projectile should be affected by gravity and whether or not "
          "it bounces before exploding.\n\n");
@@ -455,13 +460,14 @@ void ProjectileData::packData(BitStream* stream)
    stream->write(armingDelay);
    stream->write(fadeDelay);
 
+   stream->writeFlag(mExplodeOnTmeout);
    if(stream->writeFlag(isBallistic))
    {
       stream->write(gravityMod);
       stream->write(bounceElasticity);
       stream->write(bounceFriction);
    }
-
+   
 }
 
 void ProjectileData::unpackData(BitStream* stream)
@@ -514,6 +520,7 @@ void ProjectileData::unpackData(BitStream* stream)
    stream->read(&armingDelay);
    stream->read(&fadeDelay);
 
+   mExplodeOnTmeout = stream->readFlag();
    isBallistic = stream->readFlag();
    if(isBallistic)
    {
@@ -611,6 +618,7 @@ Projectile::Projectile()
    mProjectileShape( NULL ),
    mActivateThread( NULL ),
    mMaintainThread( NULL ),
+   mHasHit(false),
    mHasExploded( false ),
    mFadeValue( 1.0f )
 {
@@ -1128,10 +1136,18 @@ void Projectile::processTick( const Move *move )
 
 void Projectile::simulate( F32 dt )
 {         
-   if ( isServerObject() && mCurrTick >= mDataBlock->lifetime )
+   if ( isServerObject()  )
    {
-      deleteObject();
-      return;
+      if (mCurrTick >= (mDataBlock->lifetime - TickMs))
+      {
+         if (mDataBlock->mExplodeOnTmeout)
+            explode(mCurrPosition, Point3F::UnitZ, VehicleObjectType);
+      }
+      if (mCurrTick >= mDataBlock->lifetime || (mHasHit && mCurrTick < mDataBlock->armingDelay))
+      {
+         deleteObject();
+         return;
+      }
    }
    
    if ( mHasExploded )
@@ -1167,9 +1183,16 @@ void Projectile::simulate( F32 dt )
 
    if ( mPhysicsWorld )
       hit = mPhysicsWorld->castRay( oldPosition, newPosition, &rInfo, Point3F( newPosition - oldPosition) * mDataBlock->impactForce );            
-   else 
+   else
+   {
       hit = getContainer()->castRay(oldPosition, newPosition, dynamicCollisionMask | staticCollisionMask, &rInfo);
-
+      if (hit && rInfo.object->getTypeMask() & VehicleObjectType)
+      {
+         RigidShape* aRigid = dynamic_cast<RigidShape*>(rInfo.object);
+         if (aRigid)
+            aRigid->applyImpulse(rInfo.point, Point3F(newPosition - oldPosition) * mDataBlock->impactForce);
+      }
+   }
    if ( hit )
    {
       // make sure the client knows to bounce
@@ -1237,6 +1260,8 @@ void Projectile::simulate( F32 dt )
       else
       {
          mCurrVelocity    = Point3F::Zero;
+         newPosition = oldPosition = rInfo.point + rInfo.normal * 0.05f;
+         mHasHit = true;
       }
    }
 

+ 6 - 3
Engine/source/T3D/projectile.h

@@ -87,9 +87,9 @@ public:
    /// Force imparted on a hit object.
    F32 impactForce;
 
+   bool mExplodeOnTmeout;
    /// Should it arc?
    bool isBallistic;
-
    /// How HIGH should it bounce (parallel to normal), [0,1]
    F32 bounceElasticity;
    /// How much momentum should be lost when it bounces (perpendicular to normal), [0,1]
@@ -154,7 +154,10 @@ public:
    ProjectileData(const ProjectileData&, bool = false);
    bool allowSubstitutions() const override { return true; }
 
-   void onShapeChanged() {}
+   void onShapeChanged()
+   {
+      reloadOnLocalClient();
+   }
 };
 
 
@@ -274,7 +277,7 @@ protected:
    
    LightInfo *mLight;
    LightState mLightState;   
-
+   bool             mHasHit;
    bool             mHasExploded;   ///< Prevent rendering, lighting, and duplicate explosions.
    F32              mFadeValue;     ///< set in processTick, interpolation between fadeDelay and lifetime
                                     ///< in data block

+ 1 - 1
Engine/source/T3D/proximityMine.cpp

@@ -350,7 +350,7 @@ bool ProximityMine::onNewDataBlock( GameBaseData* dptr, bool reload )
    if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
       return false;
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 1 - 1
Engine/source/T3D/rigidShape.cpp

@@ -906,7 +906,7 @@ bool RigidShape::onNewDataBlock(GameBaseData* dptr, bool reload)
    else
       mRigid.setObjectInertia(mObjBox.maxExtents - mObjBox.minExtents);
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
 
    return true;
 }

+ 70 - 3
Engine/source/T3D/shapeBase.cpp

@@ -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);
 
@@ -172,6 +173,8 @@ ShapeBaseData::ShapeBaseData()
    density( 1.0f ),
    maxEnergy( 0.0f ),
    maxDamage( 1.0f ),
+   mCollisionMul(0.0f),
+   mImpactMul(0.0f),
    repairRate( 0.0033f ),
    disabledLevel( 1.0f ),
    destroyedLevel( 1.0f ),
@@ -195,7 +198,8 @@ ShapeBaseData::ShapeBaseData()
    useEyePoint( false ),
    isInvincible( false ),
    renderWhenDestroyed( true ),
-   inheritEnergyFromMount( false )
+   inheritEnergyFromMount( false ),
+   mAIControllData(NULL)
 {
    INIT_ASSET(Shape);
    INIT_ASSET(DebrisShape);
@@ -229,6 +233,8 @@ ShapeBaseData::ShapeBaseData(const ShapeBaseData& other, bool temp_clone) : Game
    density = other.density;
    maxEnergy = other.maxEnergy;
    maxDamage = other.maxDamage;
+   mCollisionMul = other.mCollisionMul;
+   mImpactMul = other.mImpactMul;
    repairRate = other.repairRate;
    disabledLevel = other.disabledLevel;
    destroyedLevel = other.destroyedLevel;
@@ -345,7 +351,7 @@ bool ShapeBaseData::preload(bool server, String &errorStr)
 
    S32 i;
    U32 assetStatus = ShapeAsset::getAssetErrCode(mShapeAsset);
-   if (assetStatus == AssetBase::Ok|| assetStatus == AssetBase::UsingFallback)
+   if (assetStatus == AssetBase::Ok || assetStatus == AssetBase::UsingFallback)
    {
       if (!server && !mShape->preloadMaterialList(mShape.getPath()) && NetConnection::filesWereDownloaded())
          shapeError = true;
@@ -544,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),
@@ -585,6 +595,10 @@ void ShapeBaseData::initPersistFields()
       addField( "isInvincible", TypeBool, Offset(isInvincible, ShapeBaseData),
          "Invincible flag; when invincible, the object cannot be damaged or "
          "repaired." );
+      addFieldV("collisionMul", TypeRangedF32, Offset(mCollisionMul, ShapeBaseData), &CommonValidators::PositiveFloat,
+         "collision damage multiplier");
+      addFieldV("impactMul", TypeRangedF32, Offset(mImpactMul, ShapeBaseData), &CommonValidators::PositiveFloat,
+         "impact damage multiplier");
    endGroup( "Damage/Energy" );
 
    addGroup( "Camera", "The settings used by the shape when it is the camera." );
@@ -904,7 +918,17 @@ void ShapeBaseData::unpackData(BitStream* stream)
    silent_bbox_check = stream->readFlag();
 }
 
+//
+//
+void ShapeBaseData::onShapeChanged()
+{
+   reloadOnLocalClient();
+}
 
+void ShapeBaseData::onDebrisChanged()
+{
+   reloadOnLocalClient();
+}
 //----------------------------------------------------------------------------
 //----------------------------------------------------------------------------
 
@@ -981,7 +1005,8 @@ ShapeBase::ShapeBase()
    mCameraFov( 90.0f ),
    mIsControlled( false ),
    mLastRenderFrame( 0 ),
-   mLastRenderDistance( 0.0f )
+   mLastRenderDistance( 0.0f ),
+   mAIController(NULL)
 {
    mTypeMask |= ShapeBaseObjectType | LightObjectType;   
 
@@ -1032,6 +1057,7 @@ ShapeBase::~ShapeBase()
       cur->next = sFreeTimeoutList;
       sFreeTimeoutList = cur;
    }
+   if (mAIController) mAIController->deleteObject();
 }
 
 void ShapeBase::initPersistFields()
@@ -5449,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();
+}

+ 20 - 5
Engine/source/T3D/shapeBase.h

@@ -88,6 +88,8 @@ class ShapeBase;
 class SFXSource;
 class SFXTrack;
 class SFXProfile;
+struct AIController;
+struct AIControllerData;
 
 typedef void* Light;
 
@@ -378,7 +380,7 @@ struct ShapeBaseImageData: public GameBaseData {
    F32 scriptAnimTransitionTime;    ///< The amount of time to transition between the previous sequence and new sequence
                                     ///< when the script prefix has changed.
 
-   DECLARE_SHAPEASSET_ARRAY(ShapeBaseImageData, Shape, MaxShapes);  ///< Name of shape to render.
+   DECLARE_SHAPEASSET_ARRAY(ShapeBaseImageData, Shape, MaxShapes, onShapeChanged);  ///< Name of shape to render.
    DECLARE_ASSET_ARRAY_SETGET(ShapeBaseImageData, Shape);
 
    //DECLARE_SHAPEASSET(ShapeBaseImageData, ShapeFP);  ///< Name of shape to render in first person (optional).
@@ -505,6 +507,11 @@ struct ShapeBaseImageData: public GameBaseData {
 
    void handleStateSoundTrack(const U32& stateId);
 
+   void onShapeChanged()
+   {
+      reloadOnLocalClient();
+   }
+
    /// @}
 
    /// @name Callbacks
@@ -555,6 +562,7 @@ public:
    U32 cubeDescId;
    ReflectorDesc *reflectorDesc;
 
+   AIControllerData* mAIControllData;
    /// @name Destruction
    ///
    /// Everyone likes to blow things up!
@@ -579,6 +587,8 @@ public:
    F32 density;
    F32 maxEnergy;
    F32 maxDamage;
+   F32 mCollisionMul;
+   F32 mImpactMul;
    F32 repairRate;                  ///< Rate per tick.
 
    F32 disabledLevel;
@@ -681,8 +691,8 @@ public:
    Vector<TextureTagRemapping> txr_tag_remappings;
    bool silent_bbox_check;
 
-   void onShapeChanged() {}
-   void onDebrisChanged() {}
+   void onShapeChanged();
+   void onDebrisChanged();
 public:
    ShapeBaseData(const ShapeBaseData&, bool = false);
 };
@@ -1754,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;
 
@@ -1895,7 +1910,6 @@ public:
    void   registerCollisionCallback(CollisionEventCallback*);
    void   unregisterCollisionCallback(CollisionEventCallback*);
 
-protected:
    enum { 
       ANIM_OVERRIDDEN     = BIT(0),
       BLOCK_USER_CONTROL  = BIT(1),
@@ -1903,6 +1917,8 @@ protected:
       BAD_ANIM_ID         = 999999999,
       BLENDED_CLIP        = 0x80000000,
    };
+   U8 anim_clip_flags;
+protected:
    struct BlendThread
    {
       TSThread* thread;
@@ -1910,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;

+ 1 - 1
Engine/source/T3D/staticShape.cpp

@@ -219,7 +219,7 @@ bool StaticShape::onNewDataBlock(GameBaseData* dptr, bool reload)
    if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
       return false;
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 1 - 1
Engine/source/T3D/trigger.cpp

@@ -465,7 +465,7 @@ bool Trigger::onNewDataBlock( GameBaseData *dptr, bool reload )
    if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
       return false;
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 1 - 1
Engine/source/T3D/turret/aiTurretShape.cpp

@@ -545,7 +545,7 @@ bool AITurretShape::onNewDataBlock(GameBaseData* dptr, bool reload)
       mShapeInstance->setTimeScale(mStateAnimThread,0);
    }
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 4 - 1
Engine/source/T3D/turret/turretShape.cpp

@@ -127,6 +127,7 @@ TurretShapeData::TurretShapeData()
       recoilSequence[i] = -1;
    pitchSequence = -1;
    headingSequence = -1;
+   mControlMap = StringTable->EmptyString();
 }
 
 void TurretShapeData::initPersistFields()
@@ -134,6 +135,8 @@ void TurretShapeData::initPersistFields()
    docsURL;
    Parent::initPersistFields();
    addGroup("Steering");
+   addField("controlMap", TypeString, Offset(mControlMap, TurretShapeData),
+      "@brief movemap used by these types of objects.\n\n");
       addField("zRotOnly",       TypeBool,         Offset(zRotOnly,       TurretShapeData),
          "@brief Should the turret allow only z rotations.\n\n"
          "True indicates that the turret may only be rotated on its z axis, just like the Item class.  "
@@ -440,7 +443,7 @@ bool TurretShape::onNewDataBlock(GameBaseData* dptr, bool reload)
 
    if (!mSubclassTurretShapeHandlesScene)
    {
-      scriptOnNewDataBlock();
+      scriptOnNewDataBlock(reload);
    }
 
    return true;

+ 1 - 0
Engine/source/T3D/turret/turretShape.h

@@ -80,6 +80,7 @@ public:
    bool startLoaded;          ///< Should the turret's mounted weapon(s) start in a loaded state?
 
    bool zRotOnly;             ///< Should the turret allow only z rotations (like an item)?
+   StringTableEntry mControlMap;
 
 public:
    TurretShapeData();

+ 1 - 1
Engine/source/T3D/vehicles/flyingVehicle.cpp

@@ -407,7 +407,7 @@ bool FlyingVehicle::onNewDataBlock(GameBaseData* dptr, bool reload)
          mJetThread[i] = 0;
    }
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 1 - 1
Engine/source/T3D/vehicles/hoverVehicle.cpp

@@ -549,7 +549,7 @@ bool HoverVehicle::onNewDataBlock(GameBaseData* dptr, bool reload)
    }
 
    // Todo: Uncomment if this is a "leaf" class
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
 
    return true;
 }

+ 19 - 1
Engine/source/T3D/vehicles/vehicle.cpp

@@ -141,12 +141,14 @@ VehicleData::VehicleData()
    dMemset( damageEmitterOffset, 0, sizeof( damageEmitterOffset ) );
    dMemset( damageEmitterIDList, 0, sizeof( damageEmitterIDList ) );
    dMemset( damageLevelTolerance, 0, sizeof( damageLevelTolerance ) );
+   mControlMap = StringTable->EmptyString();
 
    numDmgEmitterAreas = 0;
 
    collDamageThresholdVel = 20;
    collDamageMultiplier = 0.05f;
    enablePhysicsRep = true;
+   mControlMap = StringTable->EmptyString();
 }
 
 
@@ -320,7 +322,14 @@ 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");
       addFieldV( "jetForce", TypeRangedF32, Offset(jetForce, VehicleData), &CommonValidators::PositiveFloat,
          "@brief Additional force applied to the vehicle when it is jetting.\n\n"
          "For WheeledVehicles, the force is applied in the forward direction. For "
@@ -471,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++ )
@@ -496,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)
    {
@@ -726,6 +741,9 @@ void Vehicle::updateMove(const Move* move)
    if (mDamageState == Enabled) {
       setImageTriggerState(0,move->trigger[0]);
       setImageTriggerState(1,move->trigger[1]);
+      //legacy code has trigger 2 and 3 reserved
+      setImageTriggerState(2, move->trigger[4]);
+      setImageTriggerState(3, move->trigger[5]);
    }
 
    // Throttle

+ 6 - 1
Engine/source/T3D/vehicles/vehicle.h

@@ -27,6 +27,8 @@
 #include "T3D/rigidShape.h"
 #endif
 
+#include "T3D/AI/AIController.h"
+
 class ParticleEmitter;
 class ParticleEmitterData;
 class ClippedPolyList;
@@ -70,6 +72,7 @@ struct VehicleData : public RigidShapeData
    F32 numDmgEmitterAreas;
 
    bool enablePhysicsRep;
+   StringTableEntry mControlMap;
 
    //
    VehicleData();
@@ -98,7 +101,6 @@ class Vehicle : public RigidShape
    Point2F mSteering;
    F32 mThrottle;
    bool mJetting;
-
    GFXStateBlockRef  mSolidSB;
 
    SimObjectPtr<ParticleEmitter> mDamageEmitterList[VehicleData::VC_NUM_DAMAGE_EMITTERS];
@@ -146,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;

+ 1 - 1
Engine/source/T3D/vehicles/wheeledVehicle.cpp

@@ -711,7 +711,7 @@ bool WheeledVehicle::onNewDataBlock(GameBaseData* dptr, bool reload)
          mJetSound = SFX->createSource( mDataBlock->getWheeledVehicleSoundsProfile(WheeledVehicleData::JetSound), &getTransform() );
    }
 
-   scriptOnNewDataBlock();
+   scriptOnNewDataBlock(reload);
    return true;
 }
 

+ 4 - 1
Engine/source/T3D/vehicles/wheeledVehicle.h

@@ -74,7 +74,10 @@ struct WheeledVehicleTire: public SimDataBlock
    void packData(BitStream* stream) override;
    void unpackData(BitStream* stream) override;
 
-   void onShapeChanged() {}
+   void onShapeChanged()
+   {
+      reloadOnLocalClient();
+   }
 };
 
 

+ 4 - 1
Engine/source/afx/afxMagicMissile.h

@@ -66,7 +66,10 @@ protected:
 public:
   enum { MaxLifetimeTicks = 4095 };
 
-  void onShapeChanged() {}
+  void onShapeChanged()
+  {
+     reloadOnLocalClient();
+  }
   
 public:
    // variables set in datablock definition:

+ 1 - 1
Engine/source/afx/afxSpellBook.cpp

@@ -206,7 +206,7 @@ bool afxSpellBook::onNewDataBlock(GameBaseData* dptr, bool reload)
   if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
     return false;
 
-  scriptOnNewDataBlock();
+  scriptOnNewDataBlock(reload);
 
   return true;
 }

+ 4 - 1
Engine/source/afx/ce/afxModel.h

@@ -94,7 +94,10 @@ public:
 
   static void           initPersistFields();
 
-  void onShapeChanged() {}
+  void onShapeChanged()
+  {
+     reloadOnLocalClient();
+  }
   void onSequenceChanged() {}
 
   DECLARE_CONOBJECT(afxModelData);

+ 4 - 4
Engine/source/afx/ce/afxParticleEmitter.cpp

@@ -1064,7 +1064,7 @@ bool afxParticleEmitter::onNewDataBlock(GameBaseData* dptr, bool reload)
   if (mDataBlock->isTempClone())
     return true;
 
-  scriptOnNewDataBlock();
+  scriptOnNewDataBlock(reload);
   return true;
 }
 
@@ -1108,7 +1108,7 @@ bool afxParticleEmitterVector::onNewDataBlock(GameBaseData* dptr, bool reload)
   if (mDataBlock->isTempClone())
     return true;
 
-  scriptOnNewDataBlock();
+  scriptOnNewDataBlock(reload);
   return true;
 }
 
@@ -1177,7 +1177,7 @@ bool afxParticleEmitterCone::onNewDataBlock(GameBaseData* dptr, bool reload)
   if (mDataBlock->isTempClone())
     return true;
 
-  scriptOnNewDataBlock();
+  scriptOnNewDataBlock(reload);
   return true;
 }
 
@@ -1294,7 +1294,7 @@ bool afxParticleEmitterPath::onNewDataBlock(GameBaseData* dptr, bool reload)
   if (mDataBlock->isTempClone())
     return true;
 
-  scriptOnNewDataBlock();
+  scriptOnNewDataBlock(reload);
   return true;
 }
 

+ 47 - 0
Engine/source/assets/assetManager.cpp

@@ -1563,6 +1563,53 @@ bool AssetManager::restoreAssetTags( void )
 
 //-----------------------------------------------------------------------------
 
+const char* AssetManager::getAssetLooseFiles(const char* pAssetId)
+{
+   // Debug Profiling.
+   PROFILE_SCOPE(AssetManager_getAssetLooseFIles);
+
+   // Sanity!
+   AssertFatal(pAssetId != NULL, "Cannot look up NULL asset Id.");
+
+   // Find asset.
+   AssetDefinition* pAssetDefinition = findAsset(pAssetId);
+
+   // Did we find the asset?
+   if (pAssetDefinition == NULL)
+   {
+      // No, so warn.
+      Con::warnf("Asset Manager: Failed to find asset Id '%s' as it does not exist.", pAssetId);
+      return String::EmptyString;
+   }
+
+   // Info.
+   if (mEchoInfo)
+   {
+      Con::printSeparator();
+      Con::printf("Asset Manager: Started getting loose files of Asset Id '%s'...", pAssetId);
+   }
+
+   String looseFileList = "";
+   Vector<StringTableEntry>& assetLooseFiles = pAssetDefinition->mAssetLooseFiles;
+   for (Vector<StringTableEntry>::iterator looseFileItr = assetLooseFiles.begin(); looseFileItr != assetLooseFiles.end(); ++looseFileItr)
+   {
+      // Fetch loose file.
+      StringTableEntry looseFile = *looseFileItr;
+
+      looseFileList += looseFile;
+
+      if (looseFileItr != assetLooseFiles.end())
+         looseFileList += "\t";
+   }
+
+   char* ret = Con::getReturnBuffer(1024);
+   dSprintf(ret, 1024, "%s", looseFileList.c_str());
+
+   return ret;
+}
+
+//-----------------------------------------------------------------------------
+
 S32 QSORT_CALLBACK descendingAssetDefinitionLoadCount(const void* a, const void* b)
 {
     // Debug Profiling.

+ 3 - 0
Engine/source/assets/assetManager.h

@@ -341,6 +341,9 @@ public:
     bool restoreAssetTags( void );
     inline AssetTagsManifest* getAssetTags( void ) const { return mAssetTagsManifest; }
 
+    /// Loose File management
+    const char* getAssetLooseFiles(const char* pAssetId);
+
     /// Info.
     inline U32 getDeclaredAssetCount( void ) const { return (U32)mDeclaredAssets.size(); }
     inline U32 getReferencedAssetCount( void ) const { return (U32)mReferencedAssets.size(); }

+ 14 - 0
Engine/source/assets/assetManager_ScriptBinding.h

@@ -432,6 +432,20 @@ DefineEngineMethod(AssetManager, getAssetTags, S32, (), ,
 
 //-----------------------------------------------------------------------------
 
+DefineEngineMethod(AssetManager, getAssetLooseFiles, const char*, (const char* assetId), (""),
+   "Finds the specified asset Id and gets a list of its loose files.\n"
+   "@param assetId The selected asset Id.\n"
+   "@return A tab-delinated list of loose files associated to the assetId.\n")
+{
+   // Fetch asset Id.
+   const char* pAssetId = assetId;
+
+   // Delete asset.
+   return object->getAssetLooseFiles(pAssetId);
+}
+
+//-----------------------------------------------------------------------------
+
 DefineEngineMethod(AssetManager, findAllAssets, S32, (const char* assetQuery, bool ignoreInternal, bool ignorePrivate), ("", true, true),
    "Performs an asset query searching for all assets optionally ignoring internal assets.\n"
    "@param assetQuery The asset query object that will be populated with the results.\n"

+ 63 - 0
Engine/source/console/propertyParsing.h

@@ -162,6 +162,69 @@ namespace PropertyInfo
    bool hex_print(String & string, const S8 & hex);
 
    bool default_print(String & result, SimObjectType * const & data);
+
+   template<typename T, size_t count>
+   char* FormatPropertyBuffer(const void* dataPtr, char* buffer, U32 bufSize)
+   {
+      const T* values = reinterpret_cast<const T*>(dataPtr);
+      char* ptr = buffer;
+
+      for (size_t i = 0; i < count; ++i)
+      {
+         S32 written = 0;
+
+         if constexpr (std::is_same_v<T, int> || std::is_same_v<T, S32>)
+            written = dSprintf(ptr, bufSize - (ptr - buffer), "%d", values[i]);
+         else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, F32>)
+            written = dSprintf(ptr, bufSize - (ptr - buffer), "%g", values[i]);
+         else
+            AssertFatal(sizeof(T) == 0, "Unsupported type in FormatProperty");
+
+         ptr += written;
+         if (i < count - 1)
+            *ptr++ = ' ';
+      }
+
+      return ptr; // return end of written string for chaining
+   }
+
+   template<typename T, size_t count>
+   const char* FormatProperty(const void* dataPtr)
+   {
+      static const U32 bufSize = 256;
+      char* buffer = Con::getReturnBuffer(bufSize);
+      FormatPropertyBuffer<T,count>(dataPtr, buffer, bufSize);
+      return buffer;
+   }
+
+   template<typename T, size_t count>
+   inline bool ParseProperty(char* str, T(&out)[count])
+   {
+
+      size_t index = 0;
+      char* tok = dStrtok(str, " \t");
+
+      while (tok && index < count)
+      {
+         if constexpr (std::is_same_v<T, int> || std::is_same_v<T, S32>)
+         {
+            out[index++] = mRound(dAtof(tok));
+         }
+         else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, F32>)
+         {
+            out[index++] = dAtof(tok);
+         }
+         else
+         {
+            AssertFatal(sizeof(T) == 0, "Unsupported type in PropertyInfo::ParseProperty");
+         }
+
+         tok = dStrtok(nullptr, " \t");
+      }
+
+      return index == count;
+   }
+
 }
 
 // Default Scan/print definition

+ 9 - 1
Engine/source/console/sim.cpp

@@ -102,7 +102,15 @@ DefineEngineFunction( isObject, bool, (const char * objectName), ,"isObject(obje
    if (!String::compare(objectName, "0") || !String::compare(objectName, ""))
       return false;
    else
-      return (Sim::findObject(objectName) != NULL);
+   {
+      SimObject* obj= Sim::findObject(objectName);
+      if (obj)
+      {
+         if (!obj->isProperlyAdded() || obj->isRemoved())
+            obj = NULL;
+      }
+      return obj != NULL;
+   }
 }
 
 ConsoleDocFragment _spawnObject1(

+ 16 - 13
Engine/source/console/simDatablock.cpp

@@ -425,39 +425,42 @@ void SimDataBlock::write(Stream &stream, U32 tabStop, U32 flags)
 // MARK: ---- API ----
 
 //-----------------------------------------------------------------------------
-
-DefineEngineMethod( SimDataBlock, reloadOnLocalClient, void, (),,
-   "Reload the datablock.  This can only be used with a local client configuration." )
+void SimDataBlock::reloadOnLocalClient()
 {
    // Make sure we're running a local client.
 
    GameConnection* localClient = GameConnection::getLocalClientConnection();
-   if( !localClient )
+   if (!localClient)
       return;
 
    // Do an in-place pack/unpack/preload.
 
-   if( !object->preload( true, NetConnection::getErrorBuffer() ) )
+   if (!preload(true, NetConnection::getErrorBuffer()))
    {
-      Con::errorf( NetConnection::getErrorBuffer() );
+      Con::errorf(NetConnection::getErrorBuffer());
       return;
    }
 
-   U8 buffer[ 16384 ];
-   BitStream stream( buffer, 16384 );
+   U8 buffer[16384];
+   BitStream stream(buffer, 16384);
 
-   object->packData( &stream );
+   packData(&stream);
    stream.setPosition(0);
-   object->unpackData( &stream );
+   unpackData(&stream);
 
-   if( !object->preload( false, NetConnection::getErrorBuffer() ) )
+   if (!preload(false, NetConnection::getErrorBuffer()))
    {
-      Con::errorf( NetConnection::getErrorBuffer() );
+      Con::errorf(NetConnection::getErrorBuffer());
       return;
    }
 
    // Trigger a post-apply so that change notifications respond.
-   object->inspectPostApply();
+   inspectPostApply();
+}
+DefineEngineMethod( SimDataBlock, reloadOnLocalClient, void, (),,
+   "Reload the datablock.  This can only be used with a local client configuration." )
+{
+   object->reloadOnLocalClient();
 }
 
 //-----------------------------------------------------------------------------

+ 2 - 0
Engine/source/console/simDatablock.h

@@ -176,6 +176,8 @@ public:
    /// Used by the console system to automatically tell datablock classes apart
    /// from non-datablock classes.
    static const bool __smIsDatablock = true;
+
+   void reloadOnLocalClient();
 protected:
    struct SubstitutionStatement
    {

+ 4 - 1
Engine/source/console/simObject.cpp

@@ -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;
 
@@ -3296,6 +3296,9 @@ DefineEngineMethod( SimObject, getGroup, SimGroup*, (),,
 DefineEngineMethod( SimObject, delete, void, (),,
    "Delete and remove the object." )
 {
+   if (!object->isProperlyAdded() || object->isRemoved())
+      return;
+
    object->deleteObject();
 }
 

+ 4 - 1
Engine/source/forest/forestItem.h

@@ -143,7 +143,10 @@ public:
       return theSignal;
    }
 
-   void onShapeChanged() {}
+   void onShapeChanged()
+   {
+      reloadOnLocalClient();
+   }
 };
 
 typedef Vector<ForestItemData*> ForestItemDataVector;

+ 1 - 1
Engine/source/gui/editor/popupMenu.cpp

@@ -230,7 +230,7 @@ void PopupMenu::enableItem(S32 pos, bool enable)
 
 void PopupMenu::checkItem(S32 pos, bool checked)
 {
-   if (mMenuItems.empty() || mMenuItems.size() < pos || pos < 0)
+   if (mMenuItems.empty() || mMenuItems.size() <= pos || pos < 0)
       return;
 
    if (checked && mMenuItems[pos].mCheckGroup != -1 && mRadioSelection)

+ 304 - 246
Engine/source/math/mathTypes.cpp

@@ -24,6 +24,7 @@
 #include "console/consoleTypes.h"
 #include "console/console.h"
 #include "console/engineAPI.h"
+#include "console/propertyParsing.h"
 #include "math/mPoint2.h"
 #include "math/mPoint3.h"
 #include "math/mMatrix.h"
@@ -169,21 +170,27 @@ ImplementConsoleTypeCasters( TypePoint2I, Point2I )
 
 ConsoleGetType( TypePoint2I )
 {
-   Point2I *pt = (Point2I *) dptr;
-   static const U32 bufSize = 256;
-   char* returnBuffer = Con::getReturnBuffer(bufSize);
-   dSprintf(returnBuffer, bufSize, "%d %d", pt->x, pt->y);
-   return returnBuffer;
+   const char* buff = PropertyInfo::FormatProperty<S32, 2>(dptr);
+   return buff;
 }
 
 ConsoleSetType( TypePoint2I )
 {
-   if(argc == 1)
-      dSscanf(argv[0], "%d %d", &((Point2I *) dptr)->x, &((Point2I *) dptr)->y);
-   else if(argc == 2)
-      *((Point2I *) dptr) = Point2I(dAtoi(argv[0]), dAtoi(argv[1]));
-   else
-      Con::printf("Point2I must be set as { x, y } or \"x y\"");
+   if (argc >= 1)
+   {
+      S32 parsed[2];
+      // Combine argv into a single space-separated string if argc > 1
+      char buffer[256] = { 0 };
+
+      dStrncpy(buffer, *argv, sizeof(buffer));
+
+      if (PropertyInfo::ParseProperty<S32, 2>(buffer, parsed)) {
+         *((Point2I*)dptr) = Point2I(parsed[0], parsed[1]);
+         return;
+      }
+   }
+
+   Con::warnf("Point2I must be set as { x, y } or \"x y\"");
 }
 
 //-----------------------------------------------------------------------------
@@ -194,21 +201,27 @@ ImplementConsoleTypeCasters( TypePoint2F, Point2F )
 
 ConsoleGetType( TypePoint2F )
 {
-   Point2F *pt = (Point2F *) dptr;
-   static const U32 bufSize = 256;
-   char* returnBuffer = Con::getReturnBuffer(bufSize);
-   dSprintf(returnBuffer, bufSize, "%g %g", pt->x, pt->y);
-   return returnBuffer;
+   const char* buff = PropertyInfo::FormatProperty<F32, 2>(dptr);
+   return buff;
 }
 
 ConsoleSetType( TypePoint2F )
 {
-   if(argc == 1)
-      dSscanf(argv[0], "%g %g", &((Point2F *) dptr)->x, &((Point2F *) dptr)->y);
-   else if(argc == 2)
-      *((Point2F *) dptr) = Point2F(dAtof(argv[0]), dAtof(argv[1]));
-   else
-      Con::printf("Point2F must be set as { x, y } or \"x y\"");
+   if (argc >= 1)
+   {
+      F32 parsed[2];
+      // Combine argv into a single space-separated string if argc > 1
+      char buffer[256] = { 0 };
+
+      dStrncpy(buffer, *argv, sizeof(buffer));
+
+      if (PropertyInfo::ParseProperty<F32, 2>(buffer, parsed)) {
+         *((Point2F*)dptr) = Point2F(parsed[0], parsed[1]);
+         return;
+      }
+   }
+
+   Con::warnf("Point2F must be set as { x, y } or \"x y\"");
 }
 
 //-----------------------------------------------------------------------------
@@ -219,21 +232,27 @@ ImplementConsoleTypeCasters(TypePoint3I, Point3I)
 
 ConsoleGetType( TypePoint3I )
 {
-   Point3I *pt = (Point3I *) dptr;
-   static const U32 bufSize = 256;
-   char* returnBuffer = Con::getReturnBuffer(bufSize);
-   dSprintf(returnBuffer, bufSize, "%d %d %d", pt->x, pt->y, pt->z);
-   return returnBuffer;
+   const char* buff = PropertyInfo::FormatProperty<S32, 3>(dptr);
+   return buff;
 }
 
 ConsoleSetType( TypePoint3I )
 {
-   if(argc == 1)
-      dSscanf(argv[0], "%d %d %d", &((Point3I *) dptr)->x, &((Point3I *) dptr)->y, &((Point3I *) dptr)->z);
-   else if(argc == 3)
-      *((Point3I *) dptr) = Point3I(dAtoi(argv[0]), dAtoi(argv[1]), dAtoi(argv[2]));
-   else
-      Con::printf("Point3I must be set as { x, y, z } or \"x y z\"");
+   if (argc >= 1)
+   {
+      S32 parsed[3];
+      // Combine argv into a single space-separated string if argc > 1
+      char buffer[256] = { 0 };
+
+      dStrncpy(buffer, *argv, sizeof(buffer));
+
+      if (PropertyInfo::ParseProperty<S32, 3>(buffer, parsed)) {
+         *((Point3I*)dptr) = Point3I(parsed[0], parsed[1], parsed[2]);
+         return;
+      }
+   }
+
+   Con::warnf("Point3I must be set as { x, y, z } or \"x y z\"");
 }
 
 //-----------------------------------------------------------------------------
@@ -244,21 +263,27 @@ ImplementConsoleTypeCasters(TypePoint3F, Point3F)
 
 ConsoleGetType( TypePoint3F )
 {
-   Point3F *pt = (Point3F *) dptr;
-   static const U32 bufSize = 256;
-   char* returnBuffer = Con::getReturnBuffer(bufSize);
-   dSprintf(returnBuffer, bufSize, "%g %g %g", pt->x, pt->y, pt->z);
-   return returnBuffer;
+   const char* buff = PropertyInfo::FormatProperty<F32, 3>(dptr);
+   return buff;
 }
 
 ConsoleSetType( TypePoint3F )
 {
-   if(argc == 1)
-      dSscanf(argv[0], "%g %g %g", &((Point3F *) dptr)->x, &((Point3F *) dptr)->y, &((Point3F *) dptr)->z);
-   else if(argc == 3)
-      *((Point3F *) dptr) = Point3F(dAtof(argv[0]), dAtof(argv[1]), dAtof(argv[2]));
-   else
-      Con::printf("Point3F must be set as { x, y, z } or \"x y z\"");
+   if (argc >= 1)
+   {
+      F32 parsed[3];
+      // Combine argv into a single space-separated string if argc > 1
+      char buffer[256] = { 0 };
+
+      dStrncpy(buffer, *argv, sizeof(buffer));
+
+      if (PropertyInfo::ParseProperty<F32, 3>(buffer, parsed)) {
+         *((Point3F*)dptr) = Point3F(parsed[0], parsed[1], parsed[2]);
+         return;
+      }
+   }
+
+   Con::warnf("Point3F must be set as { x, y, z } or \"x y z\"");
 }
 
 //-----------------------------------------------------------------------------
@@ -269,21 +294,27 @@ ImplementConsoleTypeCasters( TypePoint4F, Point4F )
 
 ConsoleGetType( TypePoint4F )
 {
-   Point4F *pt = (Point4F *) dptr;
-   static const U32 bufSize = 256;
-   char* returnBuffer = Con::getReturnBuffer(bufSize);
-   dSprintf(returnBuffer, bufSize, "%g %g %g %g", pt->x, pt->y, pt->z, pt->w);
-   return returnBuffer;
+   const char* buff = PropertyInfo::FormatProperty<F32, 4>(dptr);
+   return buff;
 }
 
 ConsoleSetType( TypePoint4F )
 {
-   if(argc == 1)
-      dSscanf(argv[0], "%g %g %g %g", &((Point4F *) dptr)->x, &((Point4F *) dptr)->y, &((Point4F *) dptr)->z, &((Point4F *) dptr)->w);
-   else if(argc == 4)
-      *((Point4F *) dptr) = Point4F(dAtof(argv[0]), dAtof(argv[1]), dAtof(argv[2]), dAtof(argv[3]));
-   else
-      Con::printf("Point4F must be set as { x, y, z, w } or \"x y z w\"");
+   if (argc >= 1)
+   {
+      F32 parsed[4];
+      // Combine argv into a single space-separated string if argc > 1
+      char buffer[256] = { 0 };
+
+      dStrncpy(buffer, *argv, sizeof(buffer));
+
+      if (PropertyInfo::ParseProperty<F32, 4>(buffer, parsed)) {
+         *((Point4F*)dptr) = Point4F(parsed[0], parsed[1], parsed[2], parsed[3]);
+         return;
+      }
+   }
+
+   Con::warnf("Point4F must be set as { x, y, z, w } or \"x y z w\"");
 }
 
 //-----------------------------------------------------------------------------
@@ -294,23 +325,27 @@ ImplementConsoleTypeCasters( TypeRectI, RectI )
 
 ConsoleGetType( TypeRectI )
 {
-   RectI *rect = (RectI *) dptr;
-   static const U32 bufSize = 256;
-   char* returnBuffer = Con::getReturnBuffer(bufSize);
-   dSprintf(returnBuffer, bufSize, "%d %d %d %d", rect->point.x, rect->point.y,
-            rect->extent.x, rect->extent.y);
-   return returnBuffer;
+   const char* buff = PropertyInfo::FormatProperty<S32, 4>(dptr);
+   return buff;
 }
 
 ConsoleSetType( TypeRectI )
 {
-   if(argc == 1)
-      dSscanf(argv[0], "%d %d %d %d", &((RectI *) dptr)->point.x, &((RectI *) dptr)->point.y,
-              &((RectI *) dptr)->extent.x, &((RectI *) dptr)->extent.y);
-   else if(argc == 4)
-      *((RectI *) dptr) = RectI(dAtoi(argv[0]), dAtoi(argv[1]), dAtoi(argv[2]), dAtoi(argv[3]));
-   else
-      Con::printf("RectI must be set as { x, y, w, h } or \"x y w h\"");
+   if (argc >= 1)
+   {
+      S32 parsed[4];
+      // Combine argv into a single space-separated string if argc > 1
+      char buffer[256] = { 0 };
+
+      dStrncpy(buffer, *argv, sizeof(buffer));
+
+      if (PropertyInfo::ParseProperty<S32, 4>(buffer, parsed)) {
+         *((RectI*)dptr) = RectI(parsed[0], parsed[1], parsed[2], parsed[3]);
+         return;
+      }
+   }
+
+   Con::warnf("RectI must be set as { x, y, w, h } or \"x y w h\"");
 }
 
 //-----------------------------------------------------------------------------
@@ -321,23 +356,27 @@ ImplementConsoleTypeCasters( TypeRectF, RectF )
 
 ConsoleGetType( TypeRectF )
 {
-   RectF *rect = (RectF *) dptr;
-   static const U32 bufSize = 256;
-   char* returnBuffer = Con::getReturnBuffer(bufSize);
-   dSprintf(returnBuffer, bufSize, "%g %g %g %g", rect->point.x, rect->point.y,
-            rect->extent.x, rect->extent.y);
-   return returnBuffer;
+   const char* buff = PropertyInfo::FormatProperty<F32, 4>(dptr);
+   return buff;
 }
 
 ConsoleSetType( TypeRectF )
 {
-   if(argc == 1)
-      dSscanf(argv[0], "%g %g %g %g", &((RectF *) dptr)->point.x, &((RectF *) dptr)->point.y,
-              &((RectF *) dptr)->extent.x, &((RectF *) dptr)->extent.y);
-   else if(argc == 4)
-      *((RectF *) dptr) = RectF(dAtof(argv[0]), dAtof(argv[1]), dAtof(argv[2]), dAtof(argv[3]));
-   else
-      Con::printf("RectF must be set as { x, y, w, h } or \"x y w h\"");
+   if (argc >= 1)
+   {
+      F32 parsed[4];
+      // Combine argv into a single space-separated string if argc > 1
+      char buffer[256] = { 0 };
+
+      dStrncpy(buffer, *argv, sizeof(buffer));
+
+      if (PropertyInfo::ParseProperty<F32, 4>(buffer, parsed)) {
+         *((RectF*)dptr) = RectF(parsed[0], parsed[1], parsed[2], parsed[3]);
+         return;
+      }
+   }
+
+   Con::warnf("RectF must be set as { x, y, w, h } or \"x y w h\"");
 }
 
 //-----------------------------------------------------------------------------
@@ -351,36 +390,44 @@ ImplementConsoleTypeCasters( TypeMatrixF, MatrixF )
 
 ConsoleGetType( TypeMatrixF )
 {
-   MatrixF* mat = ( MatrixF* ) dptr;
+   MatrixF* mat = (MatrixF*)dptr;
 
    Point3F col0, col1, col2;
    mat->getColumn(0, &col0);
    mat->getColumn(1, &col1);
    mat->getColumn(2, &col2);
    static const U32 bufSize = 256;
-   char* returnBuffer = Con::getReturnBuffer(bufSize);
-   dSprintf(returnBuffer,bufSize,"%g %g %g %g %g %g %g %g %g",
-            col0.x, col0.y, col0.z, col1.x, col1.y, col1.z, col2.x, col2.y, col2.z);
-   return returnBuffer;
+   char* buffer = Con::getReturnBuffer(bufSize);
+
+   PropertyInfo::FormatPropertyBuffer<F32, 3>(col0, buffer, bufSize);
+   *buffer++ = ' ';
+   PropertyInfo::FormatPropertyBuffer<F32, 3>(col1, buffer, bufSize);
+   *buffer++ = ' ';
+   PropertyInfo::FormatPropertyBuffer<F32, 3>(col2, buffer, bufSize);
+
+   return buffer;
 }
 
 ConsoleSetType( TypeMatrixF )
 {
-   if( argc != 1 )
+   if (argc == 1)
    {
-      Con::errorf( "MatrixF must be set as \"c0x c0y c0z c1x c1y c1z c2x c2y c2z\"" );
-      return;
+      F32 parsed[9];
+
+      char* buffer = new char[dStrlen(argv[0])];
+      dStrcpy(buffer, argv[0], sizeof(buffer));
+
+      if (PropertyInfo::ParseProperty<F32, 9>(buffer, parsed)) {
+         MatrixF* mat = (MatrixF*)dptr;
+
+         mat->setColumn(0, Point3F(parsed[0], parsed[1], parsed[2]));
+         mat->setColumn(1, Point3F(parsed[3], parsed[4], parsed[5]));
+         mat->setColumn(2, Point3F(parsed[6], parsed[7], parsed[8]));
+         return;
+      }
    }
-   
-   Point3F col0, col1, col2;
-   dSscanf( argv[ 0 ], "%g %g %g %g %g %g %g %g %g",
-            &col0.x, &col0.y, &col0.z, &col1.x, &col1.y, &col1.z, &col2.x, &col2.y, &col2.z );
 
-   MatrixF* mat = ( MatrixF* ) dptr;
-   
-   mat->setColumn( 0, col0 );
-   mat->setColumn( 1, col1 );
-   mat->setColumn( 2, col2 );
+   Con::warnf("MatrixF must be set as \"c0x c0y c0z c1x c1y c1z c2x c2y c2z\"");
 }
 
 //-----------------------------------------------------------------------------
@@ -390,32 +437,40 @@ ConsoleMappedType(MatrixPosition, TypeMatrixPosition, Point3F, MatrixF, "")
 
 ConsoleGetType( TypeMatrixPosition )
 {
-   F32 *col = (F32 *) dptr + 3;
+   F32* col = (F32*)dptr + 3;
    static const U32 bufSize = 256;
    char* returnBuffer = Con::getReturnBuffer(bufSize);
-   if(col[12] == 1.f)
-      dSprintf(returnBuffer, bufSize, "%g %g %g", col[0], col[4], col[8]);
+   Point4F pos(col[0], col[4], col[8], col[12]);
+
+   if (col[12] == 1.0f)
+      PropertyInfo::FormatPropertyBuffer<F32, 3>(&pos, returnBuffer, bufSize);
    else
-      dSprintf(returnBuffer, bufSize, "%g %g %g %g", col[0], col[4], col[8], col[12]);
+      PropertyInfo::FormatPropertyBuffer<F32, 4>(&pos, returnBuffer, bufSize);
+
    return returnBuffer;
 }
 
 ConsoleSetType( TypeMatrixPosition )
 {
-   F32 *col = ((F32 *) dptr) + 3;
-   if (argc == 1)
+   if (argc >= 1)
    {
-      col[0] = col[4] = col[8] = 0.f;
-      col[12] = 1.f;
-      dSscanf(argv[0], "%g %g %g %g", &col[0], &col[4], &col[8], &col[12]);
-   }
-   else if (argc <= 4) 
-   {
-      for (S32 i = 0; i < argc; i++)
-         col[i << 2] = dAtof(argv[i]);
+      F32 parsed[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
+      // Combine argv into a single space-separated string if argc > 1
+      char buffer[256] = { 0 };
+      dStrncpy(buffer, *argv, sizeof(buffer));
+      // we dont want to hard fail based on the count.
+      // this will allow any number of properties to be set.
+      PropertyInfo::ParseProperty<F32, 4>(buffer, parsed);
+      {
+         Point4F temp(parsed[0], parsed[1], parsed[2], parsed[3]);
+         MatrixF* mat = (MatrixF*)dptr;
+         mat->setColumn(3, temp);
+         return;
+      }
    }
-   else
-      Con::printf("Matrix position must be set as { x, y, z, w } or \"x y z w\"");
+
+   Con::warnf("Matrix position must be set as { x, y, z, w } or \"x y z w\"");
+
 }
 
 //-----------------------------------------------------------------------------
@@ -425,42 +480,38 @@ ConsoleMappedType(MatrixRotation, TypeMatrixRotation, AngAxisF, MatrixF, "")
 
 ConsoleGetType( TypeMatrixRotation )
 {
-   AngAxisF aa(*(MatrixF *) dptr);
+   AngAxisF aa(*(MatrixF*)dptr);
    aa.axis.normalize();
-   static const U32 bufSize = 256;
-   char* returnBuffer = Con::getReturnBuffer(bufSize);
-   dSprintf(returnBuffer,bufSize,"%g %g %g %g",aa.axis.x,aa.axis.y,aa.axis.z,mRadToDeg(aa.angle));
-   return returnBuffer;
+   aa.angle = mRadToDeg(aa.angle);
+   const char* buff = PropertyInfo::FormatProperty<F32, 4>(&aa);
+   return buff;
 }
 
 ConsoleSetType( TypeMatrixRotation )
 {
-   // DMM: Note that this will ONLY SET the ULeft 3x3 submatrix.
-   //
-   AngAxisF aa(Point3F(0,0,0),0);
-   if (argc == 1)
-   {
-      dSscanf(argv[0], "%g %g %g %g", &aa.axis.x, &aa.axis.y, &aa.axis.z, &aa.angle);
-      aa.angle = mDegToRad(aa.angle);
-   }
-   else if (argc == 4) 
+   if (argc >= 1)
    {
-         for (S32 i = 0; i < argc; i++)
-            ((F32*)&aa)[i] = dAtof(argv[i]);
-         aa.angle = mDegToRad(aa.angle);
-   }
-   else
-      Con::printf("Matrix rotation must be set as { x, y, z, angle } or \"x y z angle\"");
+      F32 parsed[4];
+      // Combine argv into a single space-separated string if argc > 1
+      char buffer[256] = { 0 };
+      dStrncpy(buffer, *argv, sizeof(buffer));
 
-   //
-   MatrixF temp;
-   aa.setMatrix(&temp);
+      if (PropertyInfo::ParseProperty<F32, 4>(buffer, parsed))
+      {
+         AngAxisF aa(Point3F(parsed[0], parsed[1], parsed[2]), mDegToRad(parsed[3]));
+         MatrixF temp;
+         aa.setMatrix(&temp);
+
+         F32* pDst = *(MatrixF*)dptr;
+         const F32* pSrc = temp;
+         for (U32 i = 0; i < 3; i++)
+            for (U32 j = 0; j < 3; j++)
+               pDst[i * 4 + j] = pSrc[i * 4 + j];
+         return;
+      }
+   }
 
-   F32* pDst = *(MatrixF *)dptr;
-   const F32* pSrc = temp;
-   for (U32 i = 0; i < 3; i++)
-      for (U32 j = 0; j < 3; j++)
-         pDst[i*4 + j] = pSrc[i*4 + j];
+   Con::warnf("Matrix rotation must be set as { x, y, z, angle } or \"x y z angle\"");
 }
 
 //-----------------------------------------------------------------------------
@@ -472,30 +523,29 @@ ImplementConsoleTypeCasters( TypeAngAxisF, AngAxisF )
 ConsoleGetType( TypeAngAxisF )
 {
    AngAxisF* aa = ( AngAxisF* ) dptr;
-   static const U32 bufSize = 256;
-   char* returnBuffer = Con::getReturnBuffer(bufSize);
-   dSprintf(returnBuffer,bufSize,"%g %g %g %g",aa->axis.x,aa->axis.y,aa->axis.z,mRadToDeg(aa->angle));
-   return returnBuffer;
+   aa->angle = mRadToDeg(aa->angle);
+   const char* buff = PropertyInfo::FormatProperty<F32, 4>(aa);
+   return buff;
 }
 
 ConsoleSetType( TypeAngAxisF )
 {
-   // DMM: Note that this will ONLY SET the ULeft 3x3 submatrix.
-   //
-   AngAxisF* aa = ( AngAxisF* ) dptr;
-   if (argc == 1)
-   {
-      dSscanf(argv[0], "%g %g %g %g", &aa->axis.x, &aa->axis.y, &aa->axis.z, &aa->angle);
-      aa->angle = mDegToRad(aa->angle);
-   }
-   else if (argc == 4) 
+   if (argc >= 1)
    {
-      for (S32 i = 0; i < argc; i++)
-         ((F32*)&aa)[i] = dAtof(argv[i]);
-      aa->angle = mDegToRad(aa->angle);
+      F32 parsed[4];
+      // Combine argv into a single space-separated string if argc > 1
+      char buffer[256] = { 0 };
+      dStrncpy(buffer, *argv, sizeof(buffer));
+
+      if(PropertyInfo::ParseProperty<F32, 4>(buffer, parsed))
+      {
+         AngAxisF* aa = (AngAxisF*)dptr;
+         aa->set(Point3F(parsed[0], parsed[1], parsed[2]), mDegToRad(parsed[3]));
+         return;
+      }
    }
-   else
-      Con::printf("AngAxisF must be set as { x, y, z, angle } or \"x y z angle\"");
+
+   Con::warnf("AngAxisF must be set as { x, y, z, angle } or \"x y z angle\"");
 }
 
 
@@ -510,38 +560,35 @@ ImplementConsoleTypeCasters( TypeTransformF, TransformF )
 
 ConsoleGetType( TypeTransformF )
 {
-   TransformF* aa = ( TransformF* ) dptr;
-   static const U32 bufSize = 256;
-   char* returnBuffer = Con::getReturnBuffer(bufSize);
-   dSprintf( returnBuffer, bufSize, "%g %g %g %g %g %g %g",
-             aa->mPosition.x, aa->mPosition.y, aa->mPosition.z,
-             aa->mOrientation.axis.x, aa->mOrientation.axis.y, aa->mOrientation.axis.z, aa->mOrientation.angle );
-   return returnBuffer;
+   const char* buff = PropertyInfo::FormatProperty<F32, 7>(dptr);
+   return buff;
 }
 
 ConsoleSetType( TypeTransformF )
 {
-   TransformF* aa = ( TransformF* ) dptr;
-   if( argc == 1 )
+   if(argc >= 1)
    {
-      U32 count = dSscanf( argv[ 0 ], "%g %g %g %g %g %g %g",
-               &aa->mPosition.x, &aa->mPosition.y, &aa->mPosition.z,
-               &aa->mOrientation.axis.x, &aa->mOrientation.axis.y, &aa->mOrientation.axis.z, &aa->mOrientation.angle );
+      F32 parsed[7];
+      // Combine argv into a single space-separated string if argc > 1
+      char buffer[256] = { 0 };
+      dStrncpy(buffer, *argv, sizeof(buffer));
 
-      aa->mHasRotation = ( count == 7 );
-   }
-   else if( argc == 7 )
-   {
-      aa->mPosition.x = dAtof( argv[ 0 ] );
-      aa->mPosition.y = dAtof( argv[ 1 ] );
-      aa->mPosition.z = dAtof( argv[ 2 ] );
-      aa->mOrientation.axis.x = dAtof( argv[ 3 ] );
-      aa->mOrientation.axis.y = dAtof( argv[ 4 ] );
-      aa->mOrientation.axis.z = dAtof( argv[ 5 ] );
-      aa->mOrientation.angle = dAtof( argv[ 6 ] );
+      if (PropertyInfo::ParseProperty<F32, 7>(buffer, parsed))
+      {
+         TransformF* aa = (TransformF*)dptr;
+         aa->mPosition.x = parsed[0];
+         aa->mPosition.y = parsed[1];
+         aa->mPosition.z = parsed[2];
+         aa->mOrientation.axis.x = parsed[3];
+         aa->mOrientation.axis.y = parsed[4];
+         aa->mOrientation.axis.z = parsed[5];
+         aa->mOrientation.angle = parsed[6];
+         aa->mHasRotation = true;
+         return;
+      }
    }
-   else
-      Con::errorf( "TransformF must be set as { px, py, pz, x, y, z, angle } or \"px py pz x y z angle\"");
+
+   Con::warnf("TransformF must be set as { px, py, pz, x, y, z, angle } or \"px py pz x y z angle\"");
 }
 
 
@@ -554,32 +601,33 @@ ImplementConsoleTypeCasters( TypeBox3F, Box3F )
 
 ConsoleGetType( TypeBox3F )
 {
-   const Box3F* pBox = (const Box3F*)dptr;
-
-   static const U32 bufSize = 256;
-   char* returnBuffer = Con::getReturnBuffer(bufSize);
-   dSprintf(returnBuffer, bufSize, "%g %g %g %g %g %g",
-            pBox->minExtents.x, pBox->minExtents.y, pBox->minExtents.z,
-            pBox->maxExtents.x, pBox->maxExtents.y, pBox->maxExtents.z);
-
-   return returnBuffer;
+   const char* buff = PropertyInfo::FormatProperty<F32, 6>(dptr);
+   return buff;
 }
 
 ConsoleSetType( TypeBox3F )
 {
-   Box3F* pDst = (Box3F*)dptr;
-
-   if (argc == 1) 
+   if (argc >= 1)
    {
-      U32 args = dSscanf(argv[0], "%g %g %g %g %g %g",
-                         &pDst->minExtents.x, &pDst->minExtents.y, &pDst->minExtents.z,
-                         &pDst->maxExtents.x, &pDst->maxExtents.y, &pDst->maxExtents.z);
-      AssertWarn(args == 6, "Warning, box probably not read properly");
-   } 
-   else 
-   {
-      Con::printf("Box3F must be set as \"xMin yMin zMin xMax yMax zMax\"");
+      F32 parsed[6];
+      // Combine argv into a single space-separated string if argc > 1
+      char buffer[256] = { 0 };
+      dStrncpy(buffer, *argv, sizeof(buffer));
+
+      if (PropertyInfo::ParseProperty<F32, 6>(buffer, parsed))
+      {
+         Box3F* pDst = (Box3F*)dptr;
+         pDst->minExtents.x = parsed[0];
+         pDst->minExtents.y = parsed[1];
+         pDst->minExtents.z = parsed[2];
+         pDst->maxExtents.x = parsed[3];
+         pDst->maxExtents.y = parsed[4];
+         pDst->maxExtents.z = parsed[5];
+         return;
+      }
    }
+
+   Con::warnf("Box3F must be set as \"xMin yMin zMin xMax yMax zMax\"");
 }
 
 
@@ -591,31 +639,39 @@ ImplementConsoleTypeCasters( TypeEaseF, EaseF )
 
 ConsoleGetType( TypeEaseF )
 {
-   const EaseF* pEase = (const EaseF*)dptr;
-
    static const U32 bufSize = 256;
-   char* returnBuffer = Con::getReturnBuffer(bufSize);
-   dSprintf(returnBuffer, bufSize, "%d %d %g %g",
-            pEase->mDir, pEase->mType, pEase->mParam[0], pEase->mParam[1]);
+   char* buffer = Con::getReturnBuffer(bufSize);
 
-   return returnBuffer;
+   EaseF* pEase = (EaseF*)dptr;
+   PropertyInfo::FormatPropertyBuffer<S32, 2>(pEase + 0, buffer, bufSize);
+   *buffer++ = ' ';
+   PropertyInfo::FormatPropertyBuffer<F32, 2>(pEase + 2, buffer, bufSize);
+
+   return buffer;
 }
 
 ConsoleSetType( TypeEaseF )
 {
-   EaseF* pDst = (EaseF*)dptr;
-
-   // defaults...
-   pDst->mParam[0] = -1.0f;
-   pDst->mParam[1] = -1.0f;
-   if (argc == 1) {
-      U32 args = dSscanf(argv[0], "%d %d %f %f", // the two params are optional and assumed -1 if not present...
-                         &pDst->mDir, &pDst->mType, &pDst->mParam[0],&pDst->mParam[1]);
-      if( args < 2 )
-         Con::warnf( "Warning, EaseF probably not read properly" );
-   } else {
-      Con::printf("EaseF must be set as \"dir type [param0 param1]\"");
+   if (argc >= 1)
+   {
+      F32 parsed[4];
+      parsed[2] = -1.0f;
+      parsed[3] = -1.0f;
+
+      // Combine argv into a single space-separated string if argc > 1
+      char buffer[256] = { 0 };
+
+      dStrncpy(buffer, *argv, sizeof(buffer));
+
+      // same as matrix do not hard fail based on count!
+      PropertyInfo::ParseProperty<F32, 4>(buffer, parsed);
+      {
+         ((EaseF*)dptr)->set(mRound(parsed[0]), mRound(parsed[1]), parsed[2], parsed[3]);
+         return;
+      }
    }
+
+   Con::warnf("EaseF must be set as \"dir type [param0 param1]\"");
 }
 
 //-----------------------------------------------------------------------------
@@ -633,12 +689,12 @@ ConsoleGetType(TypeRotationF)
    if (pt->mRotationType == RotationF::Euler)
    {
       EulerF out = pt->asEulerF(RotationF::Degrees);
-      dSprintf(returnBuffer, bufSize, "%g %g %g", out.x, out.y, out.z);
+      PropertyInfo::FormatPropertyBuffer<F32, 3>(out, returnBuffer, bufSize);
    }
    else if (pt->mRotationType == RotationF::AxisAngle)
    {
       AngAxisF out = pt->asAxisAngle(RotationF::Degrees);
-      dSprintf(returnBuffer, bufSize, "%g %g %g %g", out.axis.x, out.axis.y, out.axis.z, out.angle);
+      PropertyInfo::FormatPropertyBuffer<F32, 4>(&out, returnBuffer, bufSize);
    }
 
    return returnBuffer;
@@ -646,34 +702,36 @@ ConsoleGetType(TypeRotationF)
 
 ConsoleSetType(TypeRotationF)
 {
-   if (argc == 1)
+   if (argc >= 1)
    {
-      U32 elements = StringUnit::getUnitCount(argv[0], " \t\n");
+      // Combine argv into a single space-separated string if argc > 1
+      char buffer[256] = { 0 };
+      dStrncpy(buffer, *argv, sizeof(buffer));
+
+      U32 elements = StringUnit::getUnitCount(buffer, " \t\n");
       if (elements == 3)
       {
-         EulerF in;
-         dSscanf(argv[0], "%g %g %g", &in.x, &in.y, &in.z);
-         ((RotationF *)dptr)->set(in, RotationF::Degrees);
+         F32 parsed[3];
+         if(PropertyInfo::ParseProperty<F32, 3>(buffer, parsed))
+         {
+            EulerF in(parsed[0], parsed[1], parsed[2]);
+            ((RotationF*)dptr)->set(in, RotationF::Degrees);
+            return;
+         }
       }
-      else
+      else if (elements == 4)
       {
-         AngAxisF in;
-         dSscanf(argv[0], "%g %g %g %g", &in.axis.x, &in.axis.y, &in.axis.z, &in.angle);
-         ((RotationF *)dptr)->set(in, RotationF::Degrees);
+         F32 parsed[4];
+         if (PropertyInfo::ParseProperty<F32, 4>(buffer, parsed))
+         {
+            AngAxisF in(Point3F(parsed[0], parsed[1], parsed[2]), parsed[3]);
+            ((RotationF*)dptr)->set(in, RotationF::Degrees);
+            return;
+         }
       }
    }
-   else if (argc == 3)
-   {
-      EulerF in(dAtof(argv[0]), dAtof(argv[1]), dAtof(argv[2]));
-      ((RotationF *)dptr)->set(in, RotationF::Degrees);
-   }
-   else if (argc == 4)
-   {
-      AngAxisF in(Point3F(dAtof(argv[0]), dAtof(argv[1]), dAtof(argv[2])), dAtof(argv[3]));
-      ((RotationF *)dptr)->set(in, RotationF::Degrees);
-   }
-   else
-      Con::printf("RotationF must be set as { x, y, z, w } or \"x y z w\"");
+
+   Con::warnf("RotationF must be set as { x, y, z, w } or \"x y z w\"");
 }
 
 //-----------------------------------------------------------------------------

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

@@ -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;
    }

+ 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;
 
    /// @}
 

+ 18 - 1
Templates/BaseGame/game/core/clientServer/scripts/client/connectionToServer.tscript

@@ -111,8 +111,12 @@ function handleConnectionErrorMessage(%msgType, %msgString, %msgError)
 //-----------------------------------------------------------------------------
 // Disconnect
 //-----------------------------------------------------------------------------
-
 function disconnect()
+{
+	callOnModules("disconnect");
+}
+
+function Core_ClientServer::disconnect(%this)
 {
    // We need to stop the client side simulation
    // else physics resources will not cleanup properly.
@@ -158,3 +162,16 @@ function disconnectedCleanup()
    
    moduleExec("onDestroyClientConnection", "Game");
 }
+
+function clientCmdsetMoveMap(%movemap)
+{
+   if (!isObject(%movemap)) return;
+   if(isObject(ServerConnection) && isObject(ServerConnection.curMoveMap))
+      ServerConnection.curMoveMap.pop();
+        
+   // clear movement
+   $mvForwardAction = 0;
+   $mvBackwardAction = 0;
+   %movemap.push();
+   ServerConnection.curMoveMap = %movemap;
+}

+ 3 - 1
Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript

@@ -275,7 +275,9 @@ function GameConnection::onPostSpawn( %this )
     if (%this.numModsNeedingLoaded)
         callOnObjectList("onPostSpawn", %modulesIdList, %this);
     else
-        %this.listener.onPostSpawnComplete(%this);    
+        %this.listener.onPostSpawnComplete(%this);
+    if (isObject(%this.player.getDatablock().controlMap))
+        commandToClient(%this, 'setMoveMap', %this.player.getDatablock().controlMap);
 }
 
 function GameConnectionListener::onPostSpawnComplete(%this, %client)

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

@@ -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;
+};
+

+ 2 - 4
Templates/BaseGame/game/core/utility/scripts/gameObjectManagement.tscript

@@ -78,17 +78,15 @@ function spawnGameObject(%name, %addToScene)
 	return 0;
 }
 
-function GameBaseData::onNewDataBlock(%this, %obj)
+function GameBaseData::onNewDataBlock(%this, %obj, %reload)
 {
-   if (%obj.firstDataCheck)
+   if (%reload)
    {
       if(%this.isMethod("onRemove"))
          %this.onRemove(%obj);
       if(%this.isMethod("onAdd"))
          %this.onAdd(%obj);
    }
-   else
-      %obj.firstDataCheck = true;
 }
 
 function saveGameObject(%name, %tamlPath, %scriptPath)

+ 11 - 0
Templates/BaseGame/game/data/DamageModel/DamageModel.module

@@ -0,0 +1,11 @@
+<ModuleDefinition
+    ModuleId="DamageModel"
+    VersionId="1"
+    Group="Game"
+    scriptFile="DamageModel.tscript"
+    CreateFunction="onCreate"
+    DestroyFunction="onDestroy">
+    <DeclaredAssets
+        Extension="asset.taml"
+        Recurse="true"/>
+</ModuleDefinition>

+ 48 - 0
Templates/BaseGame/game/data/DamageModel/DamageModel.tscript

@@ -0,0 +1,48 @@
+function DamageModel::onCreate(%this)
+{
+}
+
+function DamageModel::onDestroy(%this)
+{
+}
+
+//This is called when the server is initially set up by the game application
+function DamageModel::initServer(%this)
+{
+}
+
+//This is called when the server is created for an actual game/map to be played
+function DamageModel::onCreateGameServer(%this)
+{
+   %this.registerDatablock("./scripts/managedData/managedParticleData");
+   %this.registerDatablock("./scripts/managedData/managedParticleEmitterData");
+   %this.queueExec("./scripts/server/utility");
+   %this.queueExec("./scripts/server/radiusDamage");
+   %this.queueExec("./scripts/server/projectile");
+   %this.queueExec("./scripts/server/weapon");
+   %this.queueExec("./scripts/server/shapeBase");
+   %this.queueExec("./scripts/server/vehicle");
+   %this.queueExec("./scripts/server/player");
+}
+
+//This is called when the server is shut down due to the game/map being exited
+function DamageModel::onDestroyGameServer(%this)
+{
+}
+
+//This is called when the client is initially set up by the game application
+function DamageModel::initClient(%this)
+{
+   %this.queueExec("./guis/damageGuiOverlay.gui");
+   %this.queueExec("./scripts/client/playGui");
+}
+
+//This is called when a client connects to a server
+function DamageModel::onCreateClientConnection(%this)
+{
+}
+
+//This is called when a client disconnects from a server
+function DamageModel::onDestroyClientConnection(%this)
+{
+}

+ 1 - 0
Templates/BaseGame/game/data/DamageModel/guis/damageGuiOverlay.asset.taml

@@ -0,0 +1 @@
+<GUIAsset canSave="true" canSaveDynamicFields="true" AssetName="damageGuiOverlay" scriptFile="@assetFile=damageGuiOverlay.gui" GUIFile="@assetFile=damageGuiOverlay.gui" VersionId="1"/>

+ 285 - 0
Templates/BaseGame/game/data/DamageModel/guis/damageGuiOverlay.gui

@@ -0,0 +1,285 @@
+//--- OBJECT WRITE BEGIN ---
+$guiContent = new GuiContainer(DamageGuiOverlay) {
+   isContainer = "1";
+   Profile = "GuiContentProfile";
+   HorizSizing = "relative";
+   VertSizing = "relative";
+   position = "0 0";
+   Extent = "1024 768";
+   MinExtent = "8 8";
+   canSave = "1";
+   Visible = "1";
+   tooltipprofile = "GuiToolTipProfile";
+   hovertime = "1000";
+   canSaveDynamicFields = "1";
+      Enabled = "1";
+      helpTag = "0";
+      noCursor = "1";
+   new GuiShapeNameHud() {
+      fillColor = "0 0 0 0.25";
+      frameColor = "0 1 0 1";
+      textColor = "0 1 0 1";
+      showFill = "0";
+      showFrame = "0";
+      verticalOffset = "0.2";
+      distanceFade = "0.1";
+      isContainer = "0";
+      Profile = "GuiModelessDialogProfile";
+      HorizSizing = "width";
+      VertSizing = "height";
+      position = "0 0";
+      Extent = "1024 768";
+      MinExtent = "8 8";
+      canSave = "1";
+      Visible = "1";
+      tooltipprofile = "GuiToolTipProfile";
+      hovertime = "1000";
+      canSaveDynamicFields = "0";
+   };
+   new GuiCrossHairHud(Reticle) {
+      damageFillColor = "0 1 0 1";
+      damageFrameColor = "1 0.6 0 1";
+      damageRect = "50 4";
+      damageOffset = "0 10";
+      bitmapAsset = "FPSEquipment:blank_image";
+      wrap = "0";
+      isContainer = "0";
+      Profile = "GuiModelessDialogProfile";
+      HorizSizing = "center";
+      VertSizing = "center";
+      position = "496 368";
+      Extent = "32 32";
+      MinExtent = "8 8";
+      canSave = "1";
+      Visible = "1";
+      tooltipprofile = "GuiToolTipProfile";
+      hovertime = "1000";
+      canSaveDynamicFields = "0";
+   };
+   new GuiCrossHairHud(ZoomReticle) {
+      damageFillColor = "0 1 0 1";
+      damageFrameColor = "1 0.6 0 1";
+      damageRect = "50 4";
+      damageOffset = "0 10";
+      bitmapAsset = "DamageModel:bino_image";
+      wrap = "0";
+      isContainer = "0";
+      Profile = "GuiModelessDialogProfile";
+      HorizSizing = "width";
+      VertSizing = "height";
+      position = "0 0";
+      Extent = "1024 768";
+      MinExtent = "8 8";
+      canSave = "1";
+      Visible = "0";
+      tooltipprofile = "GuiToolTipProfile";
+      hovertime = "1000";
+      canSaveDynamicFields = "0";
+   };
+   new GuiBitmapBorderCtrl(WeaponHUD) {
+      isContainer = "0";
+      Profile = "ChatHudBorderProfile";
+      HorizSizing = "right";
+      VertSizing = "top";
+      position = "78 693";
+      Extent = "124 72";
+      MinExtent = "8 8";
+      canSave = "1";
+      Visible = "1";
+      tooltipprofile = "GuiToolTipProfile";
+      hovertime = "1000";
+      canSaveDynamicFields = "0";
+
+      new GuiBitmapCtrl() {
+         bitmap = "UI:hudfill_image";
+         wrap = "0";
+         isContainer = "0";
+         Profile = "GuiDefaultProfile";
+         HorizSizing = "width";
+         VertSizing = "height";
+         position = "8 8";
+         Extent = "108 56";
+         MinExtent = "8 8";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "GuiToolTipProfile";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+      new GuiBitmapCtrl(PreviewImage) {
+         bitmapAsset = "UI:hudfill_image";
+         wrap = "0";
+         isContainer = "0";
+         Profile = "GuiDefaultProfile";
+         HorizSizing = "width";
+         VertSizing = "height";
+         position = "8 8";
+         Extent = "108 56";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "GuiToolTipProfile";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+      new GuiTextCtrl(AmmoAmount) {
+         maxLength = "255";
+         Margin = "0 0 0 0";
+         Padding = "0 0 0 0";
+         AnchorTop = "0";
+         AnchorBottom = "0";
+         AnchorLeft = "0";
+         AnchorRight = "0";
+         isContainer = "0";
+         Profile = "HudTextItalicProfile";
+         HorizSizing = "right";
+         VertSizing = "top";
+         position = "40 8";
+         Extent = "120 16";
+         MinExtent = "8 8";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "GuiToolTipProfile";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+   };
+   new GuiHealthTextHud() {
+      fillColor = "0 0 0 0.65";
+      frameColor = "0 0 0 1";
+      textColor = "1 1 1 1";
+      warningColor = "1 0 0 1";
+      showFill = "1";
+      showFrame = "1";
+      showTrueValue = "0";
+      showEnergy = "0";
+      warnThreshold = "25";
+      pulseThreshold = "15";
+      pulseRate = "750";
+      position = "5 693";
+      extent = "72 72";
+      minExtent = "8 2";
+      horizSizing = "right";
+      vertSizing = "top";
+      profile = "GuiBigTextProfile";
+      visible = "1";
+      active = "1";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "0";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+   new GuiControl(DamageHUD) {
+      position = "384 256";
+      extent = "256 256";
+      minExtent = "8 2";
+      horizSizing = "center";
+      vertSizing = "center";
+      profile = "GuiDefaultProfile";
+      visible = "1";
+      active = "1";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "1";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+
+      new GuiBitmapCtrl(DamageFront) {
+         bitmapAsset = "DamageModel:damageFront_image";
+         wrap = "0";
+         position = "0 0";
+         extent = "256 32";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiDefaultProfile";
+         visible = "0";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "0";
+         internalName = "Damage[Front]";
+         hidden = "1";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiBitmapCtrl(DamageTop) {
+         bitmapAsset = "DamageModel:damageTop_image";
+         wrap = "0";
+         position = "0 0";
+         extent = "256 32";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiDefaultProfile";
+         visible = "0";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "0";
+         internalName = "Damage[Top]";
+         hidden = "1";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiBitmapCtrl(DamageBottom) {
+         bitmapAsset = "DamageModel:damageBottom_image";
+         wrap = "0";
+         position = "0 224";
+         extent = "256 32";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiDefaultProfile";
+         visible = "0";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "0";
+         internalName = "Damage[Bottom]";
+         hidden = "1";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiBitmapCtrl(DamageLeft) {
+         bitmapAsset = "DamageModel:damageLeft_image";
+         wrap = "0";
+         position = "0 0";
+         extent = "32 256";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiDefaultProfile";
+         visible = "0";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "0";
+         internalName = "Damage[Left]";
+         hidden = "1";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiBitmapCtrl(DamageRight) {
+         bitmapAsset = "DamageModel:damageRight_image";
+         wrap = "0";
+         position = "224 0";
+         extent = "32 256";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiDefaultProfile";
+         visible = "0";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "0";
+         internalName = "Damage[Right]";
+         hidden = "1";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+   };
+};
+//--- OBJECT WRITE END ---

BIN
Templates/BaseGame/game/data/DamageModel/images/crosshair.png


BIN
Templates/BaseGame/game/data/DamageModel/images/crosshair_blue.png


+ 3 - 0
Templates/BaseGame/game/data/DamageModel/images/crosshair_blue_image.asset.taml

@@ -0,0 +1,3 @@
+<ImageAsset
+    AssetName="crosshair_blue_image"
+    imageFile="@assetFile=crosshair_blue.png"/>

BIN
Templates/BaseGame/game/data/DamageModel/images/damageBottom.png


+ 3 - 0
Templates/BaseGame/game/data/DamageModel/images/damageBottom_image.asset.taml

@@ -0,0 +1,3 @@
+<ImageAsset
+    AssetName="damageBottom_image"
+    imageFile="@assetFile=damageBottom.png"/>

BIN
Templates/BaseGame/game/data/DamageModel/images/damageFront.png


+ 3 - 0
Templates/BaseGame/game/data/DamageModel/images/damageFront_image.asset.taml

@@ -0,0 +1,3 @@
+<ImageAsset
+    AssetName="damageFront_image"
+    imageFile="@assetFile=damageFront.png"/>

BIN
Templates/BaseGame/game/data/DamageModel/images/damageLeft.png


+ 3 - 0
Templates/BaseGame/game/data/DamageModel/images/damageLeft_image.asset.taml

@@ -0,0 +1,3 @@
+<ImageAsset
+    AssetName="damageLeft_image"
+    imageFile="@assetFile=damageLeft.png"/>

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно