diff --git a/Engine/source/CMakeLists.txt b/Engine/source/CMakeLists.txt index 35d9d81d7..34991cce7 100644 --- a/Engine/source/CMakeLists.txt +++ b/Engine/source/CMakeLists.txt @@ -58,7 +58,7 @@ torqueAddSourceDirectories("platform" "platform/threads" "platform/async" torqueAddSourceDirectories("platform/nativeDialogs") # Handle T3D -torqueAddSourceDirectories( "T3D" "T3D/assets" "T3D/decal" "T3D/examples" "T3D/fps" "T3D/fx" +torqueAddSourceDirectories( "T3D" "T3D/AI" "T3D/assets" "T3D/decal" "T3D/examples" "T3D/fps" "T3D/fx" "T3D/gameBase" "T3D/gameBase/std" "T3D/lighting" "T3D/physics" diff --git a/Engine/source/T3D/AI/AIAimTarget.cpp b/Engine/source/T3D/AI/AIAimTarget.cpp new file mode 100644 index 000000000..907b2da3d --- /dev/null +++ b/Engine/source/T3D/AI/AIAimTarget.cpp @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "AIAimTarget.h" +#include "AIController.h" + +static U32 sAILoSMask = TerrainObjectType | StaticShapeObjectType | StaticObjectType; + +bool AIAimTarget::checkInLos(GameBase* target, bool _useMuzzle, bool _checkEnabled) +{ + ShapeBase* sbo = dynamic_cast(getCtrl()->getAIInfo()->mObj.getPointer()); + if (!target) + { + target = dynamic_cast(mObj.getPointer()); + if (!target) + return false; + } + if (_checkEnabled) + { + if (target->getTypeMask() & ShapeBaseObjectType) + { + ShapeBase* shapeBaseCheck = static_cast(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; +} diff --git a/Engine/source/T3D/AI/AIAimTarget.h b/Engine/source/T3D/AI/AIAimTarget.h new file mode 100644 index 000000000..650a4f9b8 --- /dev/null +++ b/Engine/source/T3D/AI/AIAimTarget.h @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- +#ifndef _AIAIMTARGET_H_ +#define _AIAIMTARGET_H_ + +#include "AIInfo.h" +struct AIAimTarget : AIInfo +{ + typedef AIInfo Parent; + Point3F mAimOffset; + bool mTargetInLOS; // Is target object visible? + Point3F getPosition() { return ((mObj) ? mObj->getPosition() : mPosition) + mAimOffset; } + bool checkInLos(GameBase* target = NULL, bool _useMuzzle = false, bool _checkEnabled = false); + bool checkInFoV(GameBase* target = NULL, F32 camFov = 45.0f, bool _checkEnabled = false); + AIAimTarget(AIController* controller) : Parent(controller) {}; + AIAimTarget(AIController* controller, SimObjectPtr objIn, F32 radIn) : Parent(controller, objIn, radIn) {}; + AIAimTarget(AIController* controller, Point3F pointIn, F32 radIn) : Parent(controller, pointIn, radIn) {}; +}; + +#endif diff --git a/Engine/source/T3D/AI/AIController.cpp b/Engine/source/T3D/AI/AIController.cpp new file mode 100644 index 000000000..ba14b2c34 --- /dev/null +++ b/Engine/source/T3D/AI/AIController.cpp @@ -0,0 +1,430 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "AIController.h" +#include "T3D/player.h" + + +IMPLEMENT_CONOBJECT(AIController); + +//----------------------------------------------------------------------------- +void AIController::throwCallback(const char* name) +{ + Con::executef(mControllerData, name, getIdString()); //controller data callbacks + + GameBase* gbo = dynamic_cast(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(obj); + AIControllerData* data; + if (Sim::findObject(db, data)) + { + object->mControllerData = data; + return true; + } + Con::errorf("AIController::setControllerDataProperty - Could not find ControllerData \"%s\"", db); + return false; +} + +#ifdef TORQUE_NAVIGATION_ENABLED +bool AIController::getAIMove(Move* movePtr) +{ + *movePtr = NullMove; + ShapeBase* sbo = dynamic_cast(getAIInfo()->mObj.getPointer()); + if (!sbo) return false; + + // Use the eye as the current position. + MatrixF eye; + sbo->getEyeTransform(&eye); + Point3F location = eye.getPosition(); + Point3F rotation = sbo->getTransform().getForwardVector(); + +#ifdef TORQUE_NAVIGATION_ENABLED + if (sbo->getDamageState() == ShapeBase::Enabled) + { + if (mMovement.mMoveState != ModeStop) + getNav()->updateNavMesh(); + if (!getGoal()->mObj.isNull()) + { + if (getNav()->mPathData.path.isNull()) + { + if (getGoal()->getDist() > mControllerData->mMoveTolerance) + getNav()->followObject(getGoal()); + } + else + { + if (getGoal()->getDist() > mControllerData->mMoveTolerance) + getNav()->repath(); + + if (getAim()->getDist() < mControllerData->mMoveTolerance) + { + getNav()->clearPath(); + mMovement.mMoveState = ModeStop; + throwCallback("onTargetInRange"); + } + else if (getAim()->getDist() < mControllerData->mAttackRadius) + { + throwCallback("onTargetInFiringRange"); + } + } + } + } +#endif // TORQUE_NAVIGATION_ENABLED + + // Orient towards the aim point, aim object, or towards + // our destination. + if (getAim()->mObj || getAim()->mPosSet || mMovement.mMoveState != ModeStop) + { + // Update the aim position if we're aiming for an object or explicit position + if (getAim()->mObj || getAim()->mPosSet) + mMovement.mAimLocation = getAim()->getPosition(); + else + mMovement.mAimLocation = mMovement.mMoveDestination; + + mControllerData->resolveYaw(this, location, movePtr); + mControllerData->resolvePitch(this, location, movePtr); + mControllerData->resolveRoll(this, location, movePtr); + mControllerData->resolveSpeed(this, location, movePtr); + mControllerData->resolveStuck(this); + } + + // Test for target location in sight if it's an object. The LOS is + // run from the eye position to the center of the object's bounding, + // which is not very accurate. + if (getAim()->mObj) + { + GameBase* gbo = dynamic_cast(getAIInfo()->mObj.getPointer()); + if (getAim()->checkInLos(gbo)) + { + if (!getAim()->mTargetInLOS) + { + throwCallback("onTargetEnterLOS"); + getAim()->mTargetInLOS = true; + } + } + else if (getAim()->mTargetInLOS) + { + throwCallback("onTargetExitLOS"); + getAim()->mTargetInLOS = false; + } + } + + /* + // Replicate the trigger state into the move so that + // triggers can be controlled from scripts. + for (U32 i = 0; i < MaxTriggerKeys; i++) + movePtr->trigger[i] = getImageTriggerState(i); + */ + +#ifdef TORQUE_NAVIGATION_ENABLED + if (getNav()->mJump == AINavigation::Now) + { + movePtr->trigger[2] = true; + getNav()->mJump = AINavigation::None; + } + else if (getNav()->mJump == AINavigation::Ledge) + { + // If we're not touching the ground, jump! + RayInfo info; + if (!getAIInfo()->mObj->getContainer()->castRay(getAIInfo()->getPosition(), getAIInfo()->getPosition() - Point3F(0, 0, 0.4f), StaticShapeObjectType, &info)) + { + movePtr->trigger[2] = true; + getNav()->mJump = AINavigation::None; + } + } +#endif // TORQUE_NAVIGATION_ENABLED + + return true; +} + +void AIController::clearCover() +{ + // Notify cover that we are no longer on our way. + if (!getCover()->mCoverPoint.isNull()) + getCover()->mCoverPoint->setOccupied(false); + SAFE_DELETE(mCover); +} + +//----------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(AIControllerData); +void AIControllerData::resolveYaw(AIController* obj, Point3F location, Move* move) +{ + F32 xDiff = obj->mMovement.mAimLocation.x - location.x; + F32 yDiff = obj->mMovement.mAimLocation.y - location.y; + Point3F rotation = obj->getAIInfo()->mObj->getTransform().getForwardVector(); + + if (!mIsZero(xDiff) || !mIsZero(yDiff)) + { + // First do Yaw + // use the cur yaw between -Pi and Pi + F32 curYaw = rotation.z; + while (curYaw > M_2PI_F) + curYaw -= M_2PI_F; + while (curYaw < -M_2PI_F) + curYaw += M_2PI_F; + + // find the yaw offset + F32 newYaw = mAtan2(xDiff, yDiff); + F32 yawDiff = newYaw - curYaw; + + // make it between 0 and 2PI + if (yawDiff < 0.0f) + yawDiff += M_2PI_F; + else if (yawDiff >= M_2PI_F) + yawDiff -= M_2PI_F; + + // now make sure we take the short way around the circle + if (yawDiff > M_PI_F) + yawDiff -= M_2PI_F; + else if (yawDiff < -M_PI_F) + yawDiff += M_2PI_F; + + move->yaw = yawDiff; + } +} + + +void AIControllerData::resolveRoll(AIController* obj, Point3F location, Move* movePtr) +{ +} + +void AIControllerData::resolveSpeed(AIController* obj, Point3F location, Move* movePtr) +{ + // Move towards the destination + if (obj->mMovement.mMoveState != AIController::ModeStop) + { + F32 xDiff = obj->mMovement.mMoveDestination.x - location.x; + F32 yDiff = obj->mMovement.mMoveDestination.y - location.y; + Point3F rotation = obj->getAIInfo()->mObj->getTransform().getForwardVector(); + + // Check if we should mMove, or if we are 'close enough' + if (mFabs(xDiff) < mMoveTolerance && mFabs(yDiff) < mMoveTolerance) + { + obj->mMovement.mMoveState = AIController::ModeStop; + obj->getNav()->onReachDestination(); + } + else + { + // Build move direction in world space + if (mIsZero(xDiff)) + movePtr->y = (location.y > obj->mMovement.mMoveDestination.y) ? -1.0f : 1.0f; + else + if (mIsZero(yDiff)) + movePtr->x = (location.x > obj->mMovement.mMoveDestination.x) ? -1.0f : 1.0f; + else + if (mFabs(xDiff) > mFabs(yDiff)) + { + F32 value = mFabs(yDiff / xDiff); + movePtr->y = (location.y > obj->mMovement.mMoveDestination.y) ? -value : value; + movePtr->x = (location.x > obj->mMovement.mMoveDestination.x) ? -1.0f : 1.0f; + } + else + { + F32 value = mFabs(xDiff / yDiff); + movePtr->x = (location.x > obj->mMovement.mMoveDestination.x) ? -value : value; + movePtr->y = (location.y > obj->mMovement.mMoveDestination.y) ? -1.0f : 1.0f; + } + + // Rotate the move into object space (this really only needs + // a 2D matrix) + Point3F newMove; + MatrixF moveMatrix; + moveMatrix.set(EulerF(0.0f, 0.0f, -(rotation.z + movePtr->yaw))); + moveMatrix.mulV(Point3F(movePtr->x, movePtr->y, 0.0f), &newMove); + movePtr->x = newMove.x; + movePtr->y = newMove.y; + + // Set movement speed. We'll slow down once we get close + // to try and stop on the spot... + if (obj->mMovement.mMoveSlowdown) + { + F32 speed = obj->mMovement.mMoveSpeed; + F32 dist = mSqrt(xDiff * xDiff + yDiff * yDiff); + F32 maxDist = mMoveTolerance * 2; + if (dist < maxDist) + speed *= dist / maxDist; + movePtr->x *= speed; + movePtr->y *= speed; + + obj->mMovement.mMoveState = AIController::ModeSlowing; + } + else + { + movePtr->x *= obj->mMovement.mMoveSpeed; + movePtr->y *= obj->mMovement.mMoveSpeed; + + obj->mMovement.mMoveState = AIController::ModeMove; + } + } + } +} + +void AIControllerData::resolveStuck(AIController* obj) +{ + ShapeBase* sbo = dynamic_cast(obj->getAIInfo()->mObj.getPointer()); + // Don't check for ai stuckness if animation during + // an anim-clip effect override. + if (sbo->getDamageState() == ShapeBase::Enabled && !(sbo->anim_clip_flags & ShapeBase::ANIM_OVERRIDDEN) && !sbo->isAnimationLocked()) { + if (obj->mMovement.mMoveStuckTestCountdown > 0) + --obj->mMovement.mMoveStuckTestCountdown; + else + { + // We should check to see if we are stuck... + F32 locationDelta = (obj->getAIInfo()->getPosition() - obj->getAIInfo()->mLastPos).len(); + if (locationDelta < mMoveStuckTolerance && (sbo->getDamageState() == ShapeBase::Enabled)) + { + // If we are slowing down, then it's likely that our location delta will be less than + // our move stuck tolerance. Because we can be both slowing and stuck + // we should TRY to check if we've moved. This could use better detection. + if (obj->mMovement.mMoveState != AIController::ModeSlowing || locationDelta == 0) + { + obj->mMovement.mMoveState = AIController::ModeStuck; + obj->throwCallback("onStuck"); + } + } + } + obj->getAIInfo()->mLastPos = obj->getAIInfo()->getPosition(); + } +} + +void AIControllerData::initPersistFields() +{ + docsURL; + addGroup("AI"); + + addFieldV("moveTolerance", TypeRangedF32, Offset(mMoveTolerance, AIControllerData), &CommonValidators::PositiveFloat, + "@brief Distance from destination before stopping.\n\n" + "When the AIPlayer is moving to a given destination it will move to within " + "this distance of the destination and then stop. By providing this tolerance " + "it helps the AIPlayer from never reaching its destination due to minor obstacles, " + "rounding errors on its position calculation, etc. By default it is set to 0.25.\n"); + + addFieldV("followTolerance", TypeRangedF32, Offset(mFollowTolerance, AIControllerData), &CommonValidators::PositiveFloat, + "@brief Distance from destination before stopping.\n\n" + "When the AIPlayer is moving to a given destination it will move to within " + "this distance of the destination and then stop. By providing this tolerance " + "it helps the AIPlayer from never reaching its destination due to minor obstacles, " + "rounding errors on its position calculation, etc. By default it is set to 0.25.\n"); + + addFieldV("moveStuckTolerance", TypeRangedF32, Offset(mMoveStuckTolerance, AIControllerData), &CommonValidators::PositiveFloat, + "@brief Distance tolerance on stuck check.\n\n" + "When the AIPlayer is moving to a given destination, if it ever moves less than " + "this tolerance during a single tick, the AIPlayer is considered stuck. At this point " + "the onMoveStuck() callback is called on the datablock.\n"); + + addFieldV("moveStuckTestDelay", TypeRangedS32, Offset(mMoveStuckTestDelay, AIControllerData), &CommonValidators::PositiveInt, + "@brief The number of ticks to wait before testing if the AIPlayer is stuck.\n\n" + "When the AIPlayer is asked to move, this property is the number of ticks to wait " + "before the AIPlayer starts to check if it is stuck. This delay allows the AIPlayer " + "to accelerate to full speed without its initial slow start being considered as stuck.\n" + "@note Set to zero to have the stuck test start immediately.\n"); + + addFieldV("AttackRadius", TypeRangedF32, Offset(mAttackRadius, AIControllerData), &CommonValidators::PositiveFloat, + "@brief Distance considered in firing range for callback purposes."); + + endGroup("AI"); + +#ifdef TORQUE_NAVIGATION_ENABLED + addGroup("Pathfinding"); + + addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, AIControllerData), + "Allow the character to walk on dry land."); + addField("allowJump", TypeBool, Offset(mLinkTypes.jump, AIControllerData), + "Allow the character to use jump links."); + addField("allowDrop", TypeBool, Offset(mLinkTypes.drop, AIControllerData), + "Allow the character to use drop links."); + addField("allowSwim", TypeBool, Offset(mLinkTypes.swim, AIControllerData), + "Allow the character to move in water."); + addField("allowLedge", TypeBool, Offset(mLinkTypes.ledge, AIControllerData), + "Allow the character to jump ledges."); + addField("allowClimb", TypeBool, Offset(mLinkTypes.climb, AIControllerData), + "Allow the character to use climb links."); + addField("allowTeleport", TypeBool, Offset(mLinkTypes.teleport, AIControllerData), + "Allow the character to use teleporters."); + + endGroup("Pathfinding"); +#endif // TORQUE_NAVIGATION_ENABLED + + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void AIPlayerControllerData::resolvePitch(AIController* obj, Point3F location, Move* movePtr) +{ + Player* po = dynamic_cast(obj->getAIInfo()->mObj.getPointer()); + if (!po) return;//not a player + + if (obj->getAim()->mObj || obj->getAim()->mPosSet || obj->mMovement.mMoveState != AIController::ModeStop) + { + // Next do pitch. + if (!obj->getAim()->mObj && !obj->getAim()->mPosSet) + { + // Level out if were just looking at our next way point. + Point3F headRotation = po->getHeadRotation(); + movePtr->pitch = -headRotation.x; + } + else + { + F32 xDiff = obj->mMovement.mAimLocation.x - location.x; + F32 yDiff = obj->mMovement.mAimLocation.y - location.y; + // This should be adjusted to run from the + // eye point to the object's center position. Though this + // works well enough for now. + F32 vertDist = obj->mMovement.mAimLocation.z - location.z; + F32 horzDist = mSqrt(xDiff * xDiff + yDiff * yDiff); + F32 newPitch = mAtan2(horzDist, vertDist) - (M_PI_F / 2.0f); + if (mFabs(newPitch) > 0.01f) + { + Point3F headRotation = po->getHeadRotation(); + movePtr->pitch = newPitch - headRotation.x; + } + } + } + else + { + // Level out if we're not doing anything else + Point3F headRotation = po->getHeadRotation(); + movePtr->pitch = -headRotation.x; + } +} +#endif //_AICONTROLLER_H_ diff --git a/Engine/source/T3D/AI/AIController.h b/Engine/source/T3D/AI/AIController.h new file mode 100644 index 000000000..69ebbe27f --- /dev/null +++ b/Engine/source/T3D/AI/AIController.h @@ -0,0 +1,155 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- +#ifndef _AICONTROLLER_H_ +#define _AICONTROLLER_H_ +#ifdef TORQUE_NAVIGATION_ENABLED +#include "navigation/coverPoint.h" +#include "AIInfo.h" +#include "AIGoal.h" +#include "AIAimTarget.h" +#include "AICover.h" +#include "AINavigation.h" +class AIControllerData; +class AIController; + +//----------------------------------------------------------------------------- +class AIController : public SimObject { + + typedef SimObject Parent; + +public: + AIControllerData* mControllerData; +protected: + static bool setControllerDataProperty(void* object, const char* index, const char* data); +public: + enum MoveState { + ModeStop, // AI has stopped moving. + ModeMove, // AI is currently moving. + ModeStuck, // AI is stuck, but wants to move. + ModeSlowing, // AI is slowing down as it reaches it's destination. + }; + +private: + AIInfo*mAIInfo; +public: + void setAIInfo(SimObjectPtr objIn, F32 rad = 0.0f) { delete(mAIInfo); mAIInfo = new AIInfo(this, objIn, rad); } + AIInfo* getAIInfo() { return mAIInfo; } +private: + AIGoal* mGoal; +public: + void setGoal(AIInfo* targ) { mGoal = (targ) ? new AIGoal(this, targ->getPosition(), targ->mRadius) : NULL; } + void setGoal(Point3F loc, F32 rad = 0.0f) { delete(mGoal); mGoal = new AIGoal(this, loc, rad); } + void setGoal(SimObjectPtr objIn, F32 rad = 0.0f) { delete(mGoal); mGoal = new AIGoal(this, objIn, rad); } + AIGoal* getGoal() { return mGoal; } + void clearGoal() { SAFE_DELETE(mGoal); } +private: + AIAimTarget* mAimTarget; +public: + void setAim(Point3F loc, F32 rad = 0.0f, Point3F offset = Point3F(0.0f,0.0f,0.0f)) { delete(mAimTarget); mAimTarget = new AIAimTarget(this, loc, rad); mAimTarget->mAimOffset = offset; } + void setAim(SimObjectPtr objIn, F32 rad = 0.0f, Point3F offset = Point3F(0.0f, 0.0f, 0.0f)) { delete(mAimTarget); mAimTarget = new AIAimTarget(this, objIn, rad); mAimTarget->mAimOffset = offset; } + AIAimTarget* getAim() { return mAimTarget; } + void clearAim() { SAFE_DELETE(mAimTarget); } +private: + AICover* mCover; +public: + void setCover(Point3F loc, F32 rad = 0.0f) { delete(mCover); mCover = new AICover(this, loc, rad); } + void setCover(SimObjectPtr objIn, F32 rad = 0.0f) { delete(mCover); mCover = new AICover(this, objIn, rad); } + AICover* getCover() { return mCover; } + bool findCover(const Point3F& from, F32 radius); + void clearCover(); + + // Utility Methods + void throwCallback(const char* name); + AINavigation* mNav; + AINavigation* getNav() { return mNav; }; + struct Movement + { + MoveState mMoveState; + F32 mMoveSpeed = 1.0; + bool mMoveSlowdown; // Slowdown as we near the destination + Point3F mLastLocation; // For stuck check + S32 mMoveStuckTestCountdown; // The current countdown until at AI starts to check if it is stuck + Point3F mAimLocation; + Point3F mMoveDestination; + // move triggers + bool mMoveTriggers[MaxTriggerKeys]; + void stopMove(); + void onStuck(); + } mMovement; + + struct TriggerState + { + // Trigger sets/gets + void setMoveTrigger(U32 slot, const bool isSet = true); + bool getMoveTrigger(U32 slot) const; + void clearMoveTriggers(); + } mTriggerState; + bool getAIMove(Move* move); + + static void initPersistFields(); + AIController() + { + mControllerData = NULL; + mAIInfo = new AIInfo(this); + mGoal = new AIGoal(this); + mAimTarget = new AIAimTarget(this); + mCover = new AICover(this); + mNav = new AINavigation(this); + }; + + DECLARE_CONOBJECT(AIController); +}; + +//----------------------------------------------------------------------------- +class AIControllerData : public SimDataBlock { + + typedef SimDataBlock Parent; + +public: + + AIControllerData() {}; + ~AIControllerData() {}; + + static void initPersistFields(); + DECLARE_CONOBJECT(AIControllerData); + + F32 mMoveTolerance; // Distance from destination point before we stop + F32 mFollowTolerance; // Distance from destination object before we stop + F32 mAttackRadius; // Distance to trigger weaponry calcs + F32 mMoveStuckTolerance; // Distance tolerance on stuck check + S32 mMoveStuckTestDelay; // The number of ticks to wait before checking if the AI is stuck + /// Types of link we can use. + LinkData mLinkTypes; + + void resolveYaw(AIController* obj, Point3F location, Move* movePtr); + void resolvePitch(AIController* obj, Point3F location, Move* movePtr) {}; + void resolveRoll(AIController* obj, Point3F location, Move* movePtr); + void resolveSpeed(AIController* obj, Point3F location, Move* movePtr); + void resolveStuck(AIController* obj); +}; + +class AIPlayerControllerData : AIControllerData +{ + void resolvePitch(AIController* obj, Point3F location, Move* movePtr); +}; +#endif // TORQUE_NAVIGATION_ENABLED +#endif //_AICONTROLLER_H_ diff --git a/Engine/source/T3D/AI/AICover.cpp b/Engine/source/T3D/AI/AICover.cpp new file mode 100644 index 000000000..509b0f0c0 --- /dev/null +++ b/Engine/source/T3D/AI/AICover.cpp @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- diff --git a/Engine/source/T3D/AI/AICover.h b/Engine/source/T3D/AI/AICover.h new file mode 100644 index 000000000..d9d1cb638 --- /dev/null +++ b/Engine/source/T3D/AI/AICover.h @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- +#ifndef _AICOVER_H_ +#define _AICOVER_H_ + +#include "AIInfo.h" + +struct AICover : AIInfo +{ + typedef AIInfo Parent; + /// Pointer to a cover point. + SimObjectPtr mCoverPoint; + AICover(AIController* controller) : Parent(controller) {}; + AICover(AIController* controller, SimObjectPtr objIn, F32 radIn) : Parent(controller, objIn, radIn) {}; + AICover(AIController* controller, Point3F pointIn, F32 radIn) : Parent(controller, pointIn, radIn) {}; +}; + +#endif diff --git a/Engine/source/T3D/AI/AIGoal.cpp b/Engine/source/T3D/AI/AIGoal.cpp new file mode 100644 index 000000000..e94fdd8ee --- /dev/null +++ b/Engine/source/T3D/AI/AIGoal.cpp @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "AIGoal.h" +#include "AIController.h" diff --git a/Engine/source/T3D/AI/AIGoal.h b/Engine/source/T3D/AI/AIGoal.h new file mode 100644 index 000000000..e169e489d --- /dev/null +++ b/Engine/source/T3D/AI/AIGoal.h @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- +#ifndef _AIGOAL_H_ +#define _AIGOAL_H_ + +#include "AIInfo.h" + +struct AIGoal : AIInfo +{ + typedef AIInfo Parent; + AIGoal(AIController* controller): Parent(controller) {}; + AIGoal(AIController* controller, SimObjectPtr objIn, F32 radIn) : Parent(controller, objIn, radIn) {}; + AIGoal(AIController* controller, Point3F pointIn, F32 radIn) : Parent(controller, pointIn, radIn) {}; +}; +#endif diff --git a/Engine/source/T3D/AI/AIInfo.cpp b/Engine/source/T3D/AI/AIInfo.cpp new file mode 100644 index 000000000..3ce55d86c --- /dev/null +++ b/Engine/source/T3D/AI/AIInfo.cpp @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "AIInfo.h" +#include "AIController.h" + +AIInfo::AIInfo(AIController* controller) +{ + mControllerRef = controller; + mObj = NULL; + mPosition = mLastPos = Point3F(0.0f, 0.0f, 0.0f); + mRadius = 0.0f; + mPosSet = false; +}; + +AIInfo::AIInfo(AIController* controller, SimObjectPtr objIn, F32 radIn) +{ + mControllerRef = controller; + mObj = objIn; + mPosition = mLastPos = objIn->getPosition(); + mRadius = radIn; + mPosSet = false; +}; + +AIInfo::AIInfo(AIController* controller, Point3F pointIn, F32 radIn) +{ + mControllerRef = controller; + mObj = NULL; + mPosition = mLastPos = pointIn; + mRadius = radIn; + mPosSet = true; +}; + +F32 AIInfo::getDist() +{ + AIInfo* controlObj = getCtrl()->getAIInfo(); + F32 ret = VectorF(controlObj->mObj->getPosition() - getPosition()).len(); + ret -= controlObj->mRadius + mRadius; + return ret; +} diff --git a/Engine/source/T3D/AI/AIInfo.h b/Engine/source/T3D/AI/AIInfo.h new file mode 100644 index 000000000..db0dc191d --- /dev/null +++ b/Engine/source/T3D/AI/AIInfo.h @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- +#ifndef _AIINFO_H_ +#define _AIINFO_H_ + +#ifndef _SHAPEBASE_H_ +#include "T3D/shapeBase.h" +#endif + +class AIController; +struct AIInfo +{ + AIController* mControllerRef; + AIController* getCtrl() { return mControllerRef; }; + void setCtrl(AIController* controller) { mControllerRef = controller; }; + SimObjectPtr mObj; + Point3F mPosition, mLastPos; + bool mPosSet; + F32 mRadius; + Point3F getPosition() { return (mObj) ? mObj->getPosition() : mPosition; } + F32 getDist(); + AIInfo() = delete; + AIInfo(AIController* controller); + AIInfo(AIController* controller, SimObjectPtr objIn, F32 radIn = 0.0f); + AIInfo(AIController* controller,Point3F pointIn, F32 radIn = 0.0f); +}; +#endif diff --git a/Engine/source/T3D/AI/AINavigation.cpp b/Engine/source/T3D/AI/AINavigation.cpp new file mode 100644 index 000000000..18efeb3da --- /dev/null +++ b/Engine/source/T3D/AI/AINavigation.cpp @@ -0,0 +1,259 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- +#include "AINavigation.h" +#include "AIController.h" + +AINavigation::AINavigation(AIController* controller) +{ + mControllerRef = controller; +} + +NavMesh* AINavigation::findNavMesh() const +{ + GameBase* gbo = dynamic_cast(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(set->at(i)); + if (m->getWorldBox().isContained(gbo->getWorldBox())) + { + if (!mesh || m->getWorldBox().getVolume() < mesh->getWorldBox().getVolume()) + mesh = m; + } + } + return mesh; +} + +void AINavigation::updateNavMesh() +{ + GameBase* gbo = dynamic_cast(mControllerRef->getAIInfo()->mObj.getPointer()); + NavMesh* old = mNavMesh; + if (mNavMesh.isNull()) + mNavMesh = findNavMesh(); + else + { + if (!mNavMesh->getWorldBox().isContained(gbo->getWorldBox())) + mNavMesh = findNavMesh(); + } + // See if we need to update our path. + if (mNavMesh != old && !mPathData.path.isNull()) + { + setPathDestination(mPathData.path->mTo); + } +} + +void AINavigation::moveToNode(S32 node) +{ + if (mPathData.path.isNull()) + return; + + // -1 is shorthand for 'last path node'. + if (node == -1) + node = mPathData.path->size() - 1; + + // Consider slowing down on the last path node. + setMoveDestination(mPathData.path->getNode(node), false); + + // Check flags for this segment. + if (mPathData.index) + { + U16 flags = mPathData.path->getFlags(node - 1); + // Jump if we must. + if (flags & LedgeFlag) + mJump = Ledge; + else if (flags & JumpFlag) + mJump = Now; + else + // Catch pathing errors. + mJump = None; + } + + // Store current index. + mPathData.index = node; +} + +void AINavigation::repath() +{ + // Ineffectual if we don't have a path, or are using someone else's. + if (mPathData.path.isNull() || !mPathData.owned) + return; + + // If we're following, get their position. + mPathData.path->mTo = mControllerRef->getGoal()->getPosition(); + // Update from position and replan. + mPathData.path->mFrom = mControllerRef->getAIInfo()->getPosition(); + mPathData.path->plan(); + // Move to first node (skip start pos). + moveToNode(1); +} + +Point3F AINavigation::getPathDestination() const +{ + if (!mPathData.path.isNull()) + return mPathData.path->mTo; + return Point3F(0, 0, 0); + +} + +void AINavigation::setMoveDestination(const Point3F& location, bool slowdown) +{ + mMoveDestination = location; + mControllerRef->mMovement.mMoveState = AIController::ModeMove; + mControllerRef->mMovement.mMoveSlowdown = slowdown; + mControllerRef->mMovement.mMoveStuckTestCountdown = mControllerRef->mControllerData->mMoveStuckTestDelay; +} + +void AINavigation::onReachDestination() +{ + +#ifdef TORQUE_NAVIGATION_ENABLED + if (!getPath().isNull()) + { + if (mPathData.index == getPath()->size() - 1) + { + // Handle looping paths. + if (getPath()->mIsLooping) + moveToNode(0); + // Otherwise end path. + else + { + clearPath(); + getCtrl()->throwCallback("onReachDestination"); + } + } + else + { + moveToNode(mPathData.index + 1); + // Throw callback every time if we're on a looping path. + //if(mPathData.path->mIsLooping) + //throwCallback("onReachDestination"); + } + } + else +#endif + getCtrl()->throwCallback("onReachDestination"); +} + +bool AINavigation::setPathDestination(const Point3F& pos) +{ + if (!mNavMesh) + updateNavMesh(); + // If we can't find a mesh, just move regularly. + if (!mNavMesh) + { + //setMoveDestination(pos); + mControllerRef->throwCallback("onPathFailed"); + return false; + } + + // Create a new path. + NavPath* path = new NavPath(); + + path->mMesh = mNavMesh; + path->mFrom = mControllerRef->getAIInfo()->getPosition(); + path->mTo = pos; + path->mFromSet = path->mToSet = true; + path->mAlwaysRender = true; + path->mLinkTypes = mControllerRef->mControllerData->mLinkTypes; + path->mXray = true; + // Paths plan automatically upon being registered. + if (!path->registerObject()) + { + delete path; + return false; + } + + if (path->success()) + { + // Clear any current path we might have. + clearPath(); + mControllerRef->clearCover(); + clearFollow(); + // Store new path. + mPathData.path = path; + mPathData.owned = true; + // Skip node 0, which we are currently standing on. + moveToNode(1); + mControllerRef->throwCallback("onPathSuccess"); + return true; + } + else + { + // Just move normally if we can't path. + //setMoveDestination(pos, true); + //return; + mControllerRef->throwCallback("onPathFailed"); + path->deleteObject(); + return false; + } +} + +void AINavigation::followObject(AIInfo* targ) +{ + if (!targ) return; + + if (targ->getDist() < mControllerRef->mControllerData->mMoveTolerance) + return; + + if (setPathDestination(targ->getPosition())) + { + mControllerRef->clearCover(); + mControllerRef->setGoal(targ); + } +} + +void AINavigation::followObject(SceneObject* obj, F32 radius) +{ + mControllerRef->setGoal(obj, radius); + followObject(mControllerRef->getGoal()); +} + +void AINavigation::clearFollow() +{ + mControllerRef->clearGoal(); +} + +void AINavigation::followNavPath(NavPath* path) +{ + // Get rid of our current path. + clearPath(); + mControllerRef->clearCover(); + clearFollow(); + + // Follow new path. + mPathData.path = path; + mPathData.owned = false; + // Start from 0 since we might not already be there. + moveToNode(0); +} + +void AINavigation::clearPath() +{ + // Only delete if we own the path. + if (!mPathData.path.isNull() && mPathData.owned) + mPathData.path->deleteObject(); + // Reset path data. + mPathData = PathData(); +} diff --git a/Engine/source/T3D/AI/AINavigation.h b/Engine/source/T3D/AI/AINavigation.h new file mode 100644 index 000000000..130305eea --- /dev/null +++ b/Engine/source/T3D/AI/AINavigation.h @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- +#ifndef _AINAVIGATION_H_ +#define _AINAVIGATION_H_ + +#include "AIInfo.h" + +#include "navigation/navPath.h" +#include "navigation/navMesh.h" + +class AIController; +struct AINavigation +{ + AIController* mControllerRef; + AIController* getCtrl() { return mControllerRef; }; + + AINavigation() = delete; + AINavigation(AIController* controller); + + /// Stores information about a path. + struct PathData { + /// Pointer to path object. + SimObjectPtr path; + /// Do we own our path? If so, we will delete it when finished. + bool owned; + /// Path node we're at. + U32 index; + /// Default constructor. + PathData() : path(NULL) + { + owned = false; + index = 0; + } + }; + + /// Should we jump? + enum JumpStates { + None, ///< No, don't jump. + Now, ///< Jump immediately. + Ledge, ///< Jump when we walk off a ledge. + }; + + Point3F mMoveDestination; + void setMoveDestination(const Point3F& location, bool slowdown); + void onReachDestination(); + + /// NavMesh we pathfind on. + SimObjectPtr mNavMesh; + NavMesh* findNavMesh() const; + void updateNavMesh(); + PathData mPathData; + JumpStates mJump; + + /// Clear out the current path. + void clearPath(); + bool setPathDestination(const Point3F& pos); + Point3F getPathDestination() const; + void repath(); + + /// Get the current path we're following. + SimObjectPtr getPath() { return mPathData.path; }; + void followNavPath(NavPath* path); + + void followObject(AIInfo* targ); + void followObject(SceneObject* obj, F32 radius); + void clearFollow(); + /// Move to the specified node in the current path. + void moveToNode(S32 node); + +}; + +#endif diff --git a/Engine/source/T3D/player.cpp b/Engine/source/T3D/player.cpp index 801df58df..cd32ea024 100644 --- a/Engine/source/T3D/player.cpp +++ b/Engine/source/T3D/player.cpp @@ -460,6 +460,7 @@ PlayerData::PlayerData() jumpTowardsNormal = true; physicsPlayerType = StringTable->EmptyString(); + mControlMap = StringTable->EmptyString(); dMemset( actionList, 0, sizeof(actionList) ); } @@ -739,7 +740,9 @@ void PlayerData::initPersistFields() endGroup( "Camera" ); addGroup( "Movement" ); - + addField("controlMap", TypeString, Offset(mControlMap, PlayerData), + "@brief movemap used by these types of objects.\n\n"); + addFieldV( "maxStepHeight", TypeRangedF32, Offset(maxStepHeight, PlayerData), &CommonValidators::PositiveFloat, "@brief Maximum height the player can step up.\n\n" "The player will automatically step onto changes in ground height less " @@ -1738,7 +1741,7 @@ bool Player::onAdd() world ); mPhysicsRep->setTransform( getTransform() ); } - + mAIController = NULL; return true; } @@ -2256,8 +2259,36 @@ void Player::advanceTime(F32 dt) } } +bool Player::setAIController(const char* controller) +{ + if (Sim::findObject(controller, mAIController)) + { + mAIController->setAIInfo(this); + return true; + } + + mAIController = NULL; + return false; +} + +DefineEngineMethod(Player, setAIController, bool, (const char* controller), , "") +{ + return object->setAIController(controller); +} + +DefineEngineMethod(Player, getAIController, AIController*, (), , "") +{ + return object->getAIController(); +} + + bool Player::getAIMove(Move* move) { + if (mAIController) + { + mAIController->getAIMove(move); //actual result + } + return false; } diff --git a/Engine/source/T3D/player.h b/Engine/source/T3D/player.h index b80b3f0e5..78367edab 100644 --- a/Engine/source/T3D/player.h +++ b/Engine/source/T3D/player.h @@ -50,6 +50,13 @@ class Player; class OpenVRTrackedObject; #endif +#ifdef TORQUE_NAVIGATION_ENABLED +#include "navigation/navPath.h" +#include "navigation/navMesh.h" +#include "navigation/coverPoint.h" +#endif // TORQUE_NAVIGATION_ENABLED +#include "AI/AIController.h" + //---------------------------------------------------------------------------- struct PlayerData: public ShapeBaseData { @@ -346,7 +353,8 @@ struct PlayerData: public ShapeBaseData { // Jump off surfaces at their normal rather than straight up bool jumpTowardsNormal; - + StringTableEntry mControlMap; + AIControllerData* mAIControllData; // For use if/when mPhysicsPlayer is created StringTableEntry physicsPlayerType; @@ -488,6 +496,7 @@ protected: SimObjectPtr mControlObject; ///< Controlling object + AIController* mAIController; /// @name Animation threads & data /// @{ @@ -754,6 +763,8 @@ public: void setMomentum(const Point3F &momentum) override; bool displaceObject(const Point3F& displaceVector) override; virtual bool getAIMove(Move*); + bool setAIController(const char* controller); + AIController* getAIController() { return mAIController; }; bool checkDismountPosition(const MatrixF& oldPos, const MatrixF& newPos); ///< Is it safe to dismount here? diff --git a/Engine/source/T3D/shapeBase.h b/Engine/source/T3D/shapeBase.h index 706f54b72..66a48fc29 100644 --- a/Engine/source/T3D/shapeBase.h +++ b/Engine/source/T3D/shapeBase.h @@ -1895,7 +1895,6 @@ public: void registerCollisionCallback(CollisionEventCallback*); void unregisterCollisionCallback(CollisionEventCallback*); -protected: enum { ANIM_OVERRIDDEN = BIT(0), BLOCK_USER_CONTROL = BIT(1), @@ -1903,6 +1902,8 @@ protected: BAD_ANIM_ID = 999999999, BLENDED_CLIP = 0x80000000, }; + U8 anim_clip_flags; +protected: struct BlendThread { TSThread* thread; @@ -1910,7 +1911,6 @@ protected: }; Vector blend_clips; static U32 unique_anim_tag_counter; - U8 anim_clip_flags; S32 last_anim_id; U32 last_anim_tag; U32 last_anim_lock_tag;