Added basic Walkabout with #define renamed and no editor.

This commit is contained in:
Daniel Buckmaster 2014-11-28 19:42:10 +11:00
parent c08413ffde
commit f4c940f4fe
22 changed files with 3725 additions and 196 deletions

View file

@ -100,6 +100,12 @@ AIPlayer::AIPlayer()
mTargetInLOS = false;
mAimOffset = Point3F(0.0f, 0.0f, 0.0f);
#ifdef TORQUE_NAVIGATION_ENABLED
mJump = None;
mNavSize = Regular;
mLinkTypes = LinkData(AllFlags);
#endif // TORQUE_NAVIGATION_ENABLED
mIsAiControlled = true;
}
@ -136,6 +142,27 @@ void AIPlayer::initPersistFields()
endGroup( "AI" );
#ifdef TORQUE_NAVIGATION_ENABLED
addGroup("Pathfinding");
addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, AIPlayer),
"Allow the character to walk on dry land.");
addField("allowJump", TypeBool, Offset(mLinkTypes.jump, AIPlayer),
"Allow the character to use jump links.");
addField("allowDrop", TypeBool, Offset(mLinkTypes.drop, AIPlayer),
"Allow the character to use drop links.");
addField("allowSwim", TypeBool, Offset(mLinkTypes.swim, AIPlayer),
"Allow the character tomove in water.");
addField("allowLedge", TypeBool, Offset(mLinkTypes.ledge, AIPlayer),
"Allow the character to jump ledges.");
addField("allowClimb", TypeBool, Offset(mLinkTypes.climb, AIPlayer),
"Allow the character to use climb links.");
addField("allowTeleport", TypeBool, Offset(mLinkTypes.teleport, AIPlayer),
"Allow the character to use teleporters.");
endGroup("Pathfinding");
#endif // TORQUE_NAVIGATION_ENABLED
Parent::initPersistFields();
}
@ -152,6 +179,16 @@ bool AIPlayer::onAdd()
return true;
}
#ifdef TORQUE_NAVIGATION_ENABLED
void AIPlayer::onRemove()
{
clearPath();
clearCover();
clearFollow();
Parent::onRemove();
}
#endif // TORQUE_NAVIGATION_ENABLED
/**
* Sets the speed at which this AI moves
*
@ -168,6 +205,11 @@ void AIPlayer::setMoveSpeed( F32 speed )
void AIPlayer::stopMove()
{
mMoveState = ModeStop;
#ifdef TORQUE_NAVIGATION_ENABLED
clearPath();
clearCover();
clearFollow();
#endif // TORQUE_NAVIGATION_ENABLED
}
/**
@ -258,6 +300,32 @@ bool AIPlayer::getAIMove(Move *movePtr)
Point3F location = eye.getPosition();
Point3F rotation = getRotation();
#ifdef TORQUE_NAVIGATION_ENABLED
if(mDamageState == Enabled)
{
if(mMoveState != ModeStop)
updateNavMesh();
if(!mFollowData.object.isNull())
{
if(mPathData.path.isNull())
{
if((getPosition() - mFollowData.object->getPosition()).len() > mFollowData.radius)
followObject(mFollowData.object, mFollowData.radius);
}
else
{
if((mPathData.path->mTo - mFollowData.object->getPosition()).len() > mFollowData.radius)
repath();
else if((getPosition() - mFollowData.object->getPosition()).len() < mFollowData.radius)
{
clearPath();
mMoveState = ModeStop;
}
}
}
}
#endif // TORQUE_NAVIGATION_ENABLED
// Orient towards the aim point, aim object, or towards
// our destination.
if (mAimObject || mAimLocationSet || mMoveState != ModeStop)
@ -340,7 +408,11 @@ bool AIPlayer::getAIMove(Move *movePtr)
if (mFabs(xDiff) < mMoveTolerance && mFabs(yDiff) < mMoveTolerance)
{
mMoveState = ModeStop;
#ifdef TORQUE_NAVIGATION_ENABLED
onReachDestination();
#else
throwCallback("onReachDestination");
#endif // TORQUE_NAVIGATION_ENABLED
}
else
{
@ -409,7 +481,11 @@ bool AIPlayer::getAIMove(Move *movePtr)
if ( mMoveState != ModeSlowing || locationDelta == 0 )
{
mMoveState = ModeStuck;
#ifdef TORQUE_NAVIGATION_ENABLED
onStuck();
#else
throwCallback("onMoveStuck");
#endif // TORQUE_NAVIGATION_ENABLED
}
}
}
@ -419,28 +495,53 @@ bool AIPlayer::getAIMove(Move *movePtr)
// 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 (mAimObject)
{
if (checkInLos(mAimObject.getPointer()))
{
if (!mTargetInLOS)
{
throwCallback("onTargetEnterLOS");
mTargetInLOS = true;
if (mAimObject) {
MatrixF eyeMat;
getEyeTransform(&eyeMat);
eyeMat.getColumn(3,&location);
Point3F targetLoc = mAimObject->getBoxCenter();
// This ray ignores non-static shapes. Cast Ray returns true
// if it hit something.
RayInfo dummy;
if (getContainer()->castRay( location, targetLoc,
StaticShapeObjectType | StaticObjectType |
TerrainObjectType, &dummy)) {
if (mTargetInLOS) {
throwCallback( "onTargetExitLOS" );
mTargetInLOS = false;
}
}
else if (mTargetInLOS)
{
throwCallback("onTargetExitLOS");
mTargetInLOS = false;
}
else
if (!mTargetInLOS) {
throwCallback( "onTargetEnterLOS" );
mTargetInLOS = true;
}
}
// Replicate the trigger state into the move so that
// triggers can be controlled from scripts.
for( S32 i = 0; i < MaxTriggerKeys; i++ )
for( int i = 0; i < MaxTriggerKeys; i++ )
movePtr->trigger[i] = getImageTriggerState(i);
#ifdef TORQUE_NAVIGATION_ENABLED
if(mJump == Now)
{
movePtr->trigger[2] = true;
mJump = None;
}
else if(mJump == Ledge)
{
// If we're not touching the ground, jump!
RayInfo info;
if(!getContainer()->castRay(getPosition(), getPosition() - Point3F(0, 0, 0.4f), StaticShapeObjectType, &info))
{
movePtr->trigger[2] = true;
mJump = None;
}
}
#endif // TORQUE_NAVIGATION_ENABLED
mLastLocation = location;
return true;
@ -457,6 +558,453 @@ void AIPlayer::throwCallback( const char *name )
Con::executef(getDataBlock(), name, getIdString());
}
#ifdef TORQUE_NAVIGATION_ENABLED
/**
* Called when we get within mMoveTolerance of our destination set using
* setMoveDestination(). Only fires the script callback if we are at the end
* of a pathfinding path, or have no pathfinding path.
*/
void AIPlayer::onReachDestination()
{
if(!mPathData.path.isNull())
{
if(mPathData.index == mPathData.path->size() - 1)
{
// Handle looping paths.
if(mPathData.path->mIsLooping)
moveToNode(0);
// Otherwise end path.
else
{
clearPath();
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
throwCallback("onReachDestination");
}
/**
* Called when we move less than mMoveStuckTolerance in a tick, signalling
* that some obstacle is preventing us from getting where we need to go.
*/
void AIPlayer::onStuck()
{
if(!mPathData.path.isNull())
repath();
else
throwCallback("onMoveStuck");
}
// --------------------------------------------------------------------------------------------
// Pathfinding
// --------------------------------------------------------------------------------------------
void AIPlayer::clearPath()
{
// Only delete if we own the path.
if(!mPathData.path.isNull() && mPathData.owned)
mPathData.path->deleteObject();
// Reset path data.
mPathData = PathData();
}
void AIPlayer::clearCover()
{
// Notify cover that we are no longer on our way.
if(!mCoverData.cover.isNull())
mCoverData.cover->setOccupied(false);
mCoverData = CoverData();
}
void AIPlayer::clearFollow()
{
mFollowData = FollowData();
}
void AIPlayer::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;
}
bool AIPlayer::setPathDestination(const Point3F &pos)
{
// Pathfinding only happens on the server.
if(!isServerObject())
return false;
if(!getNavMesh())
updateNavMesh();
// If we can't find a mesh, just move regularly.
if(!getNavMesh())
{
//setMoveDestination(pos);
return false;
}
// Create a new path.
NavPath *path = new NavPath();
if(path)
{
path->mMesh = getNavMesh();
path->mFrom = getPosition();
path->mTo = pos;
path->mFromSet = path->mToSet = true;
path->mAlwaysRender = true;
path->mLinkTypes = mLinkTypes;
path->mXray = true;
// Paths plan automatically upon being registered.
if(!path->registerObject())
{
delete path;
return false;
}
}
else
return false;
if(path->success())
{
// Clear any current path we might have.
clearPath();
clearCover();
clearFollow();
// Store new path.
mPathData.path = path;
mPathData.owned = true;
// Skip node 0, which we are currently standing on.
moveToNode(1);
return true;
}
else
{
// Just move normally if we can't path.
//setMoveDestination(pos, true);
//return;
//throwCallback("onPathFailed");
path->deleteObject();
return false;
}
}
DefineEngineMethod(AIPlayer, 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->setPathDestination(goal);
}
Point3F AIPlayer::getPathDestination() const
{
if(!mPathData.path.isNull())
return mPathData.path->mTo;
return Point3F(0, 0, 0);
}
DefineEngineMethod(AIPlayer, 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->getPathDestination();
}
void AIPlayer::followNavPath(NavPath *path)
{
if(!isServerObject())
return;
// Get rid of our current path.
clearPath();
clearCover();
clearFollow();
// Follow new path.
mPathData.path = path;
mPathData.owned = false;
// Start from 0 since we might not already be there.
moveToNode(0);
}
DefineEngineMethod(AIPlayer, 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->followNavPath(path);
}
void AIPlayer::followObject(SceneObject *obj, F32 radius)
{
if(!isServerObject())
return;
if(setPathDestination(obj->getPosition()))
{
clearCover();
mFollowData.object = obj;
mFollowData.radius = radius;
}
}
DefineEngineMethod(AIPlayer, 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;
if(Sim::findObject(obj, follow))
object->followObject(follow, radius);
}
void AIPlayer::repath()
{
// Ineffectual if we don't have a path, or are using someone else's.
if(mPathData.path.isNull() || !mPathData.owned)
return;
// If we're following, get their position.
if(!mFollowData.object.isNull())
mPathData.path->mTo = mFollowData.object->getPosition();
// Update from position and replan.
mPathData.path->mFrom = getPosition();
mPathData.path->plan();
// Move to first node (skip start pos).
moveToNode(1);
}
DefineEngineMethod(AIPlayer, 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->repath();
}
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 AIPlayer::findCover(const Point3F &from, F32 radius)
{
if(radius <= 0)
return false;
// Create a search state.
CoverSearch s;
s.loc = getPosition();
s.dist = radius;
// Direction we seek cover FROM.
s.from = from;
// Find cover points.
Box3F box(radius * 2.0f);
box.setCenter(getPosition());
getContainer()->findObjects(box, MarkerObjectType, findCoverCallback, &s);
// Go to cover!
if(s.point)
{
// Calling setPathDestination clears cover...
bool foundPath = setPathDestination(s.point->getPosition());
// Now store the cover info.
mCoverData.cover = s.point;
s.point->setOccupied(true);
return foundPath;
}
return false;
}
DefineEngineMethod(AIPlayer, 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();
return cover ? cover->getId() : -1;
}
else
{
return -1;
}
}
NavMesh *AIPlayer::findNavMesh() const
{
// 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(getWorldBox()))
{
// Check that mesh size is appropriate.
if(mMount.object) // Should use isMounted() but it's not const. Grr.
{
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;
}
DefineEngineMethod(AIPlayer, 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->getNavMesh();
return mesh ? mesh->getId() : -1;
}
void AIPlayer::updateNavMesh()
{
NavMesh *old = mNavMesh;
if(mNavMesh.isNull())
mNavMesh = findNavMesh();
else
{
if(!mNavMesh->getWorldBox().isContained(getWorldBox()))
mNavMesh = findNavMesh();
}
// See if we need to update our path.
if(mNavMesh != old && !mPathData.path.isNull())
{
setPathDestination(mPathData.path->mTo);
}
}
DefineEngineMethod(AIPlayer, getNavMesh, S32, (),,
"@brief Return the NavMesh this AIPlayer is using to navigate.\n\n")
{
NavMesh *m = object->getNavMesh();
return m ? m->getId() : 0;
}
DefineEngineMethod(AIPlayer, setNavSize, void, (const char *size),,
"@brief Set the size of NavMesh this character uses. One of \"Small\", \"Regular\" or \"Large\".")
{
if(!dStrcmp(size, "Small"))
object->setNavSize(AIPlayer::Small);
else if(!dStrcmp(size, "Regular"))
object->setNavSize(AIPlayer::Regular);
else if(!dStrcmp(size, "Large"))
object->setNavSize(AIPlayer::Large);
else
Con::errorf("AIPlayer::setNavSize: no such size '%s'.", size);
}
DefineEngineMethod(AIPlayer, getNavSize, const char*, (),,
"@brief Return the size of NavMesh this character uses for pathfinding.")
{
switch(object->getNavSize())
{
case AIPlayer::Small:
return "Small";
case AIPlayer::Regular:
return "Regular";
case AIPlayer::Large:
return "Large";
}
return "";
}
#endif // TORQUE_NAVIGATION_ENABLED
// --------------------------------------------------------------------------------------------
// Console Functions
@ -713,4 +1261,4 @@ DefineEngineMethod(AIPlayer, checkInFoV, bool, (ShapeBase* obj, F32 fov, bool ch
"@checkEnabled check whether the object can take damage and if so is still alive.(Defaults to false)\n")
{
return object->checkInFoV(obj, fov, checkEnabled);
}
}