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