void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos) { zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); // check if stuck due to obstacles if (!mObstacleCheck.check(actor, duration)) return; // first check if obstacle is a door MWWorld::Ptr door = getNearbyDoor(actor); // NOTE: checks interior cells only if (door != MWWorld::Ptr()) { // note: AiWander currently does not open doors if (getTypeId() != TypeIdWander && !door.getCellRef().getTeleport() && door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 && door.getClass().getDoorState(door) == 0) { MWBase::Environment::get().getWorld()->activateDoor(door, 1); } } else // any other obstacle (NPC, crate, etc.) { mObstacleCheck.takeEvasiveAction(movement); } }
void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos) { zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); if (mObstacleCheck.check(actor, duration)) { // first check if we're walking into a door MWWorld::Ptr door = getNearbyDoor(actor); if (door != MWWorld::Ptr()) // NOTE: checks interior cells only { if (!door.getCellRef().getTeleport() && door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 && door.getClass().getDoorState(door) == 0) { MWBase::Environment::get().getWorld()->activateDoor(door, 1); } } else // probably walking into another NPC { mObstacleCheck.takeEvasiveAction(movement); } } else { //Not stuck, so reset things movement.mPosition[1] = 1; //Just run forward } }
bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor,float duration) { ESM::Position pos = actor.getRefData().getPosition(); if(mDuration == 1) //If it just started, get the actor position as the stuck detection thing mLastPos = pos; mDuration -= duration; //Update timer if(mDuration < 0) { float x = pos.pos[0] - mLastPos.pos[0]; float y = pos.pos[1] - mLastPos.pos[1]; float z = pos.pos[2] - mLastPos.pos[2]; int distance = x * x + y * y + z * z; if(distance < 10 * 10) { //Got stuck, didn't move if(mAdjAngle == 0) //Try going in various directions mAdjAngle = 1.57079632679f; //pi/2 else if (mAdjAngle == 1.57079632679f) mAdjAngle = -1.57079632679; else mAdjAngle = 0; mDuration = 1; //reset timer } else //Not stuck return true; // We have tried backing up for more than one second, we've probably cleared it } if (!mDoorPtr.getClass().getDoorState(mDoorPtr)) return true; //Door is no longer opening ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door float x = pos.pos[0] - tPos.pos[0]; float y = pos.pos[1] - tPos.pos[1]; actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); // Turn away from the door and move when turn completed if (zTurn(actor, Ogre::Radian(std::atan2(x,y) + mAdjAngle), Ogre::Degree(5))) actor.getClass().getMovementSettings(actor).mPosition[1] = 1; else actor.getClass().getMovementSettings(actor).mPosition[1] = 0; actor.getClass().getMovementSettings(actor).mPosition[0] = 0; // Make all nearby actors also avoid the door std::vector<MWWorld::Ptr> actors; MWBase::Environment::get().getMechanicsManager()->getActorsInRange(Ogre::Vector3(pos.pos[0],pos.pos[1],pos.pos[2]),100,actors); for(std::vector<MWWorld::Ptr>::iterator it = actors.begin(); it != actors.end(); it++) { if(*it != MWBase::Environment::get().getWorld()->getPlayerPtr()) { //Not the player MWMechanics::AiSequence& seq = it->getClass().getCreatureStats(*it).getAiSequence(); if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) { //Only add it once seq.stack(MWMechanics::AiAvoidDoor(mDoorPtr),*it); } } } return false; }
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration) { //Update various Timers mTimer += duration; //Update timer mStuckTimer += duration; //Update stuck timer mTotalTime += duration; //Update total time following ESM::Position pos = actor.getRefData().getPosition(); //position of the actor /// Stops the actor when it gets too close to a unloaded cell { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const ESM::Cell *cell = actor.getCell()->getCell(); Movement &movement = actor.getClass().getMovementSettings(actor); //Ensure pursuer doesn't leave loaded cells if(cell->mData.mX != player.getCell()->getCell()->mData.mX) { int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); //check if actor is near the border of an inactive cell. If so, stop walking. if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) { movement.mPosition[1] = 0; return false; } } if(cell->mData.mY != player.getCell()->getCell()->mData.mY) { int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); //check if actor is near the border of an inactive cell. If so, stop walking. if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) { movement.mPosition[1] = 0; return false; } } } //Start position ESM::Pathgrid::Point start = pos.pos; //*********************** /// Checks if you can't get to the end position at all, adds end position to end of path /// Rebuilds path every quarter of a second, in case the target has moved //*********************** if(mTimer > 0.25) { if(distance(mPrevDest, dest) > 10) { //Only rebuild path if it's moved mPathFinder.buildPath(start, dest, actor.getCell(), true); //Rebuild path, in case the target has moved mPrevDest = dest; } if(!mPathFinder.getPath().empty()) //Path has points in it { ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path if(distance(dest, lastPos) > 100) //End of the path is far from the destination mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go } mTimer = 0; } //************************ /// Checks if you aren't moving; attempts to unstick you //************************ if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) //Path finished? return true; else if(mStuckTimer>0.5) //Every half second see if we need to take action to avoid something { /// TODO (tluppi#1#): Use ObstacleCheck here. Not working for some reason //if(mObstacleCheck.check(actor, duration)) { if(distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < 10 && distance(dest, start) > 20) { //Actually stuck, and far enough away from destination to care // first check if we're walking into a door MWWorld::Ptr door = getNearbyDoor(actor); if(door != MWWorld::Ptr()) // NOTE: checks interior cells only { if(door.getCellRef().mTrap.empty() && mLastDoorChecked != door) { //Open the door if untrapped door.getClass().activate(door, actor).get()->execute(actor); mLastDoorChecked = door; } } else // probably walking into another NPC { // TODO: diagonal should have same animation as walk forward // but doesn't seem to do that? actor.getClass().getMovementSettings(actor).mPosition[0] = 1; actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; // change the angle a bit, too zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); } } else { //Not stuck, so reset things mStuckTimer = 0; mStuckPos = pos; mLastDoorChecked = MWWorld::Ptr(); //Resets it, in case he gets stuck behind the door again actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward } } else { actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward the rest of the time } zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); return false; }
bool AiCombat::execute (const MWWorld::Ptr& actor,float duration) { //General description if(!actor.getClass().getCreatureStats(actor).isHostile() || actor.getClass().getCreatureStats(actor).getHealth().getCurrent() <= 0) return true; if(mTarget.getClass().getCreatureStats(mTarget).isDead()) return true; //Update every frame if(mCombatMove) { mTimerCombatMove -= duration; if( mTimerCombatMove <= 0) { mTimerCombatMove = 0; mMovement.mPosition[1] = mMovement.mPosition[0] = 0; mCombatMove = false; } } actor.getClass().getMovementSettings(actor) = mMovement; if (mRotate) { if (zTurn(actor, Ogre::Degree(mTargetAngle))) mRotate = false; } mTimerAttack -= duration; actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike); float tReaction = 0.25f; if(mTimerReact < tReaction) { mTimerReact += duration; return false; } //Update with period = tReaction mTimerReact = 0; //actual attacking logic //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f float attackPeriod = 1.0f; if(mReadyToAttack) { if(mTimerAttack <= -attackPeriod) { //TODO: should depend on time between 'start' to 'min attack' //for better controlling of NPCs' attack strength. //Also it seems that this time is different for slash/thrust/chop mTimerAttack = 0.35f * static_cast<float>(rand())/RAND_MAX; mStrike = true; //say a provoking combat phrase if (actor.getClass().isNpc()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); int chance = store.get<ESM::GameSetting>().find("iVoiceAttackOdds")->getInt(); int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99] if (roll < chance) { MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); } } } else if (mTimerAttack <= 0) mStrike = false; } else { mTimerAttack = -attackPeriod; mStrike = false; } const MWWorld::Class &cls = actor.getClass(); const ESM::Weapon *weapon = NULL; MWMechanics::WeaponType weaptype; float weapRange, weapSpeed = 1.0f; actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); if (actor.getClass().hasInventoryStore(actor)) { MWMechanics::DrawState_ state = actor.getClass().getCreatureStats(actor).getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) actor.getClass().getCreatureStats(actor).setDrawState(MWMechanics::DrawState_Weapon); //Get weapon speed and range MWWorld::ContainerStoreIterator weaponSlot = MWMechanics::getActiveWeapon(cls.getCreatureStats(actor), cls.getInventoryStore(actor), &weaptype); if (weaptype == WeapType_HandToHand) { const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); weapRange = gmst.find("fHandToHandReach")->getFloat(); } else { weapon = weaponSlot->get<ESM::Weapon>()->mBase; weapRange = weapon->mData.mReach; weapSpeed = weapon->mData.mSpeed; } weapRange *= 100.0f; } else //is creature { weaptype = WeapType_HandToHand; //doesn't matter, should only reflect if it is melee or distant weapon weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit) } ESM::Position pos = actor.getRefData().getPosition(); float rangeMelee; float rangeCloseUp; bool distantCombat = false; if (weaptype==WeapType_BowAndArrow || weaptype==WeapType_Crossbow || weaptype==WeapType_Thrown) { rangeMelee = 1000; // TODO: should depend on archer skill rangeCloseUp = 0; //doesn't needed when attacking from distance distantCombat = true; } else { rangeMelee = weapRange; rangeCloseUp = 300; } Ogre::Vector3 vStart(pos.pos[0], pos.pos[1], pos.pos[2]); ESM::Position targetPos = mTarget.getRefData().getPosition(); Ogre::Vector3 vDest(targetPos.pos[0], targetPos.pos[1], targetPos.pos[2]); Ogre::Vector3 vDir = vDest - vStart; float distBetween = vDir.length(); if(distBetween < rangeMelee || (distBetween <= rangeCloseUp && mFollowTarget) ) { //Melee and Close-up combat vDir.z = 0; float dirLen = vDir.length(); mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / dirLen) * sgn(Ogre::Math::ASin(vDir.x / dirLen)) ).valueDegrees(); mRotate = true; //bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget); if (mFollowTarget && distBetween > rangeMelee) { //Close-up combat: just run up on target mMovement.mPosition[1] = 1; } else { //Melee: stop running and attack mMovement.mPosition[1] = 0; // When attacking with a weapon, choose between slash, thrust or chop if (actor.getClass().hasInventoryStore(actor)) chooseBestAttack(weapon, mMovement); if(mMovement.mPosition[0] || mMovement.mPosition[1]) { mTimerCombatMove = 0.1f + 0.1f * static_cast<float>(rand())/RAND_MAX; mCombatMove = true; } else if(actor.getClass().isNpc() && (!distantCombat || (distantCombat && rangeMelee/5))) { //apply sideway movement (kind of dodging) with some probability if(static_cast<float>(rand())/RAND_MAX < 0.25) { mMovement.mPosition[0] = static_cast<float>(rand())/RAND_MAX < 0.5? 1: -1; mTimerCombatMove = 0.05f + 0.15f * static_cast<float>(rand())/RAND_MAX; mCombatMove = true; } } if(distantCombat && distBetween < rangeMelee/4) { mMovement.mPosition[1] = -1; } mReadyToAttack = true; //only once got in melee combat, actor is allowed to use close-up shortcutting mFollowTarget = true; } } else { //target is at far distance: build path to target OR follow target (if previously actor had reached it once) mFollowTarget = false; buildNewPath(actor); //may fail to build a path, check before use //delete visited path node mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); //if no new path leave mTargetAngle unchanged if(!mPathFinder.getPath().empty()) { //try shortcut if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); else mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); mRotate = true; } mMovement.mPosition[1] = 1; mReadyToAttack = false; } if(distBetween > rangeMelee) { //special run attack; it shouldn't affect melee combat tactics if(actor.getClass().getMovementSettings(actor).mPosition[1] == 1) { //check if actor can overcome the distance = distToTarget - attackerWeapRange //less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing) //then start attacking float speed1 = cls.getSpeed(actor); float speed2 = mTarget.getClass().getSpeed(mTarget); if(mTarget.getClass().getMovementSettings(mTarget).mPosition[0] == 0 && mTarget.getClass().getMovementSettings(mTarget).mPosition[1] == 0) speed2 = 0; float s1 = distBetween - weapRange; float t = s1/speed1; float s2 = speed2 * t; float t_swing = 0.17f/weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags if (t + s2/speed1 <= t_swing) { mReadyToAttack = true; if(mTimerAttack <= -attackPeriod) { mTimerAttack = 0.3f*static_cast<float>(rand())/RAND_MAX; mStrike = true; } } } } actor.getClass().getMovementSettings(actor) = mMovement; return false; }
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance) { mTimer += duration; //Update timer ESM::Position pos = actor.getRefData().getPosition(); //position of the actor /// Stops the actor when it gets too close to a unloaded cell //... At current time, this test is unnecessary. AI shuts down when actor is more than 7168 //... units from player, and exterior cells are 8192 units long and wide. //... But AI processing distance may increase in the future. if (isNearInactiveCell(pos)) { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; return false; } // handle path building and shortcutting ESM::Pathgrid::Point start = pos.pos; float distToTarget = distance(start, dest); bool isDestReached = (distToTarget <= destTolerance); if (!isDestReached && mTimer > AI_REACTION_TIME) { bool wasShortcutting = mIsShortcutting; bool destInLOS = false; if (getTypeId() != TypeIdWander) // prohibit shortcuts for AiWander mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS); // try to shortcut first if (!mIsShortcutting) { if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path { mPathFinder.buildSyncedPath(start, dest, actor.getCell()); mRotateOnTheRunChecks = 3; // give priority to go directly on target if there is minimal opportunity if (destInLOS && mPathFinder.getPath().size() > 1) { // get point just before dest std::list<ESM::Pathgrid::Point>::const_iterator pPointBeforeDest = mPathFinder.getPath().end(); --pPointBeforeDest; --pPointBeforeDest; // if start point is closer to the target then last point of path (excluding target itself) then go straight on the target if (distance(start, dest) <= distance(dest, *pPointBeforeDest)) { mPathFinder.clearPath(); mPathFinder.addPointToPath(dest); } } } if (!mPathFinder.getPath().empty()) //Path has points in it { ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path if(distance(dest, lastPos) > 100) //End of the path is far from the destination mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go } } mTimer = 0; } if (isDestReached || mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) // if path is finished { // turn to destination point zTurn(actor, getZAngleToPoint(start, dest)); smoothTurn(actor, getXAngleToPoint(start, dest), 0); return true; } else { if (mRotateOnTheRunChecks == 0 || isReachableRotatingOnTheRun(actor, *mPathFinder.getPath().begin())) // to prevent circling around a path point { actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // move to the target if (mRotateOnTheRunChecks > 0) mRotateOnTheRunChecks--; } // handle obstacles on the way evadeObstacles(actor, duration, pos); } // turn to next path point by X,Z axes zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); smoothTurn(actor, mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2]), 0); return false; }
bool MWMechanics::AiPursue::execute (const MWWorld::Ptr& actor, float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::Position pos = actor.getRefData().getPosition(); Movement &movement = actor.getClass().getMovementSettings(actor); const ESM::Cell *cell = actor.getCell()->getCell(); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); MWWorld::Ptr player = world->getPlayerPtr(); if(cell->mData.mX != player.getCell()->getCell()->mData.mX) { int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); //check if actor is near the border of an inactive cell. If so, stop walking. if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) { movement.mPosition[1] = 0; return false; } } if(cell->mData.mY != player.getCell()->getCell()->mData.mY) { int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); //check if actor is near the border of an inactive cell. If so, stop walking. if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) { movement.mPosition[1] = 0; return false; } } // Big TODO: Sync this with current AiFollow. Move common code to a shared base class or helpers (applies to all AI packages, way too much duplicated code) MWWorld::Ptr target = world->getPtr(mObjectId,false); ESM::Position targetPos = target.getRefData().getPosition(); bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY; if(!mPathFinder.isPathConstructed() || cellChange || mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) { mCellX = cell->mData.mX; mCellY = cell->mData.mY; ESM::Pathgrid::Point dest; dest.mX = targetPos.pos[0]; dest.mY = targetPos.pos[1]; dest.mZ = targetPos.pos[2]; ESM::Pathgrid::Point start; start.mX = pos.pos[0]; start.mY = pos.pos[1]; start.mZ = pos.pos[2]; mPathFinder.buildPath(start, dest, actor.getCell(), true); } if((pos.pos[0]-targetPos.pos[0])*(pos.pos[0]-targetPos.pos[0])+ (pos.pos[1]-targetPos.pos[1])*(pos.pos[1]-targetPos.pos[1])+ (pos.pos[2]-targetPos.pos[2])*(pos.pos[2]-targetPos.pos[2]) < 100*100) { movement.mPosition[1] = 0; MWWorld::Ptr target = world->getPtr(mObjectId,false); MWWorld::Class::get(target).activate(target,actor).get()->execute(actor); return true; } float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); zTurn(actor, Ogre::Degree(zAngle)); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; movement.mPosition[1] = 1; return false; }
bool AiTravel::execute (const MWWorld::Ptr& actor,float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::Position pos = actor.getRefData().getPosition(); Movement &movement = actor.getClass().getMovementSettings(actor); const ESM::Cell *cell = actor.getCell()->getCell(); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); MWWorld::Ptr player = world->getPlayerPtr(); if(cell->mData.mX != player.getCell()->getCell()->mData.mX) { int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); //check if actor is near the border of an inactive cell. If so, stop walking. if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) { movement.mPosition[1] = 0; return false; } } if(cell->mData.mY != player.getCell()->getCell()->mData.mY) { int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); //check if actor is near the border of an inactive cell. If so, stop walking. if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) { movement.mPosition[1] = 0; return false; } } bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY; if(!mPathFinder.isPathConstructed() || cellChange) { mCellX = cell->mData.mX; mCellY = cell->mData.mY; ESM::Pathgrid::Point dest; dest.mX = mX; dest.mY = mY; dest.mZ = mZ; ESM::Pathgrid::Point start; start.mX = pos.pos[0]; start.mY = pos.pos[1]; start.mZ = pos.pos[2]; mPathFinder.buildPath(start, dest, actor.getCell(), true); } if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) { movement.mPosition[1] = 0; return true; } zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); movement.mPosition[1] = 1; return false; }
bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) { const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorId, false); //The target to follow if(target == MWWorld::Ptr()) return true; //Target doesn't exist mTimer = mTimer + duration; //Update timer mStuckTimer = mStuckTimer + duration; //Update stuck timer mTotalTime = mTotalTime + duration; //Update total time following ESM::Position pos = actor.getRefData().getPosition(); //position of the actor if(!mAlwaysFollow) //Update if you only follow for a bit { if(mTotalTime > mDuration && mDuration != 0) //Check if we've run out of time return true; if((pos.pos[0]-mX)*(pos.pos[0]-mX) + (pos.pos[1]-mY)*(pos.pos[1]-mY) + (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100) //Close-ish to final position { if(actor.getCell()->isExterior()) //Outside? { if(mCellId == "") //No cell to travel to return true; } else { if(mCellId == actor.getCell()->getCell()->mName) //Cell to travel to return true; } } } //Set the target desition from the actor ESM::Pathgrid::Point dest; dest.mX = target.getRefData().getPosition().pos[0]; dest.mY = target.getRefData().getPosition().pos[1]; dest.mZ = target.getRefData().getPosition().pos[2]; //Current position, for pathfilding stuff ESM::Pathgrid::Point start; start.mX = pos.pos[0]; start.mY = pos.pos[1]; start.mZ = pos.pos[2]; //Build the path to get to the destination if(mPathFinder.getPath().empty()) mPathFinder.buildPath(start, dest, actor.getCell(), true); //*********************** // Checks if you can't get to the end position at all //*********************** if(mTimer > 0.25) { if(!mPathFinder.getPath().empty()) //Path has points in it { ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path if((dest.mX - lastPos.mX)*(dest.mX - lastPos.mX) +(dest.mY - lastPos.mY)*(dest.mY - lastPos.mY) +(dest.mZ - lastPos.mZ)*(dest.mZ - lastPos.mZ) > 100*100) //End of the path is far from the destination mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go } mTimer = 0; } //************************ // Checks if you aren't moving; you're stuck //************************ if(mStuckTimer>0.5) //Checks every half of a second { if((mStuckPos.pos[0] - pos.pos[0])*(mStuckPos.pos[0] - pos.pos[0]) +(mStuckPos.pos[1] - pos.pos[1])*(mStuckPos.pos[1] - pos.pos[1]) +(mStuckPos.pos[2] - pos.pos[2])*(mStuckPos.pos[2] - pos.pos[2]) < 100) //NPC is stuck mPathFinder.buildPath(start, dest, actor.getCell(), true); mStuckTimer = 0; mStuckPos = pos; } //Checks if the path isn't over, turn tomards the direction that you're going if(!mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) { zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); } if((dest.mX - pos.pos[0])*(dest.mX - pos.pos[0])+(dest.mY - pos.pos[1])*(dest.mY - pos.pos[1])+(dest.mZ - pos.pos[2])*(dest.mZ - pos.pos[2]) < 100*100) actor.getClass().getMovementSettings(actor).mPosition[1] = 0; else actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Check if you're far away if((dest.mX - start.mX)*(dest.mX - start.mX) +(dest.mY - start.mY)*(dest.mY - start.mY) +(dest.mZ - start.mZ)*(dest.mZ - start.mZ) > 1000*1000) actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run else if((dest.mX - start.mX)*(dest.mX - start.mX) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold +(dest.mY - start.mY)*(dest.mY - start.mY) +(dest.mZ - start.mZ)*(dest.mZ - start.mZ) < 800*800) actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk return false; }
bool AiEscort::execute (const MWWorld::Ptr& actor,float duration) { // If AiEscort has ran for as long or longer then the duration specified // and the duration is not infinite, the package is complete. if(mDuration != 0) { MWWorld::TimeStamp current = MWBase::Environment::get().getWorld()->getTimeStamp(); unsigned int currentSecond = ((current.getHour() - int(current.getHour())) * 100); if(currentSecond - mStartingSecond >= mDuration) return true; } MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); ESM::Position pos = actor.getRefData().getPosition(); bool cellChange = actor.getCell()->mCell->mData.mX != mCellX || actor.getCell()->mCell->mData.mY != mCellY; if(actor.getCell()->mCell->mData.mX != player.getCell()->mCell->mData.mX) { int sideX = sgn(actor.getCell()->mCell->mData.mX - player.getCell()->mCell->mData.mX); // Check if actor is near the border of an inactive cell. If so, pause walking. if(sideX * (pos.pos[0] - actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE / 2.0 - 200)) { MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; return false; } } if(actor.getCell()->mCell->mData.mY != player.getCell()->mCell->mData.mY) { int sideY = sgn(actor.getCell()->mCell->mData.mY - player.getCell()->mCell->mData.mY); // Check if actor is near the border of an inactive cell. If so, pause walking. if(sideY*(pos.pos[1] - actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE / 2.0 - 200)) { MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; return false; } } if(!mPathFinder.isPathConstructed() || cellChange) { mCellX = actor.getCell()->mCell->mData.mX; mCellY = actor.getCell()->mCell->mData.mY; ESM::Pathgrid::Point dest; dest.mX = mX; dest.mY = mY; dest.mZ = mZ; ESM::Pathgrid::Point start; start.mX = pos.pos[0]; start.mY = pos.pos[1]; start.mZ = pos.pos[2]; mPathFinder.buildPath(start, dest, actor.getCell(), true); } if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) { MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; return true; } const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); const float* const leaderPos = actor.getRefData().getPosition().pos; const float* const followerPos = follower.getRefData().getPosition().pos; double differenceBetween[3]; for (short counter = 0; counter < 3; counter++) differenceBetween[counter] = (leaderPos[counter] - followerPos[counter]); float distanceBetweenResult = (differenceBetween[0] * differenceBetween[0]) + (differenceBetween[1] * differenceBetween[1]) + (differenceBetween[2] * differenceBetween[2]); if(distanceBetweenResult <= mMaxDist * mMaxDist) { float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); zTurn(actor, Ogre::Degree(zAngle)); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; mMaxDist = 470; } else { // Stop moving if the player is to far away MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; mMaxDist = 330; } return false; }
void hummingBird::TurnRight(float amount) { zTurn(-amount); }
void hummingBird::TurnLeft(float amount) { zTurn(amount); }
/* * Current AiCombat movement states (as of 0.29.0), ignoring the details of the * attack states such as CombatMove, Strike and ReadyToAttack: * * +----(within strike range)----->attack--(beyond strike range)-->follow * | | ^ | | * | | | | | * pursue<---(beyond follow range)-----+ +----(within strike range)---+ | * ^ | * | | * +-------------------------(beyond follow range)--------------------+ * * * Below diagram is high level only, the code detail is a little different * (but including those detail will just complicate the diagram w/o adding much) * * +----------(same)-------------->attack---------(same)---------->follow * | |^^ ||| * | ||| ||| * | +--(same)-----------------+|+----------(same)------------+|| * | | | || * | | | (in range) || * | <---+ (too far) | || * pursue<-------------------------[door open]<-----+ || * ^^^ | || * ||| | || * ||+----------evade-----+ | || * || | [closed door] | || * |+----> maybe stuck, check --------------> back up, check door || * | ^ | ^ | ^ || * | | | | | | || * | | +---+ +---+ || * | +-------------------------------------------------------+| * | | * +---------------------------(same)---------------------------------+ * * FIXME: * * The new scheme is way too complicated, should really be implemented as a * proper state machine. * * TODO: * * Use the Observer Pattern to co-ordinate attacks, provide intelligence on * whether the target was hit, etc. */ bool AiCombat::execute (const MWWorld::Ptr& actor, AiState& state, float duration) { // get or create temporary storage AiCombatStorage& storage = state.get<AiCombatStorage>(); //General description if(actor.getClass().getCreatureStats(actor).isDead()) return true; MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); if (target.isEmpty()) return false; if(!target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered // with the MechanicsManager || target.getClass().getCreatureStats(target).isDead()) return true; const MWWorld::Class& actorClass = actor.getClass(); MWBase::World* world = MWBase::Environment::get().getWorld(); //Update every frame bool& combatMove = storage.mCombatMove; float& timerCombatMove = storage.mTimerCombatMove; MWMechanics::Movement& movement = storage.mMovement; if(combatMove) { timerCombatMove -= duration; if( timerCombatMove <= 0) { timerCombatMove = 0; movement.mPosition[1] = movement.mPosition[0] = 0; combatMove = false; } } actorClass.getMovementSettings(actor) = movement; actorClass.getMovementSettings(actor).mRotation[0] = 0; actorClass.getMovementSettings(actor).mRotation[2] = 0; if(movement.mRotation[2] != 0) { if(zTurn(actor, Ogre::Degree(movement.mRotation[2]))) movement.mRotation[2] = 0; } if(movement.mRotation[0] != 0) { if(smoothTurn(actor, Ogre::Degree(movement.mRotation[0]), 0)) movement.mRotation[0] = 0; } //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f float attacksPeriod = 1.0f; ESM::Weapon::AttackType attackType; bool& attack = storage.mAttack; bool& readyToAttack = storage.mReadyToAttack; float& timerAttack = storage.mTimerAttack; bool& minMaxAttackDurationInitialised = storage.mMinMaxAttackDurationInitialised; float (&minMaxAttackDuration)[3][2] = storage.mMinMaxAttackDuration; if(readyToAttack) { if (!minMaxAttackDurationInitialised) { // TODO: this must be updated when a different weapon is equipped getMinMaxAttackDuration(actor, minMaxAttackDuration); minMaxAttackDurationInitialised = true; } if (timerAttack < 0) attack = false; timerAttack -= duration; } else { timerAttack = -attacksPeriod; attack = false; } actorClass.getCreatureStats(actor).setAttackingOrSpell(attack); float& actionCooldown = storage.mActionCooldown; actionCooldown -= duration; float& timerReact = storage.mTimerReact; float tReaction = 0.25f; if(timerReact < tReaction) { timerReact += duration; return false; } //Update with period = tReaction // Stop attacking if target is not seen if (target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude() > 0 || target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude() > 75) { movement.mPosition[1] = movement.mPosition[0] = 0; return false; // TODO: run away instead of doing nothing } timerReact = 0; const MWWorld::CellStore*& currentCell = storage.mCell; bool cellChange = currentCell && (actor.getCell() != currentCell); if(!currentCell || cellChange) { currentCell = actor.getCell(); } MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(actor); if (!anim) // shouldn't happen return false; actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); if (actionCooldown > 0) return false; float rangeAttack = 0; float rangeFollow = 0; boost::shared_ptr<Action>& currentAction = storage.mCurrentAction; if (anim->upperBodyReady()) { currentAction = prepareNextAction(actor, target); actionCooldown = currentAction->getActionCooldown(); } if (currentAction.get()) currentAction->getCombatRange(rangeAttack, rangeFollow); // FIXME: consider moving this stuff to ActionWeapon::getCombatRange const ESM::Weapon *weapon = NULL; MWMechanics::WeaponType weaptype = WeapType_None; float weapRange = 1.0f; // Get weapon characteristics if (actorClass.hasInventoryStore(actor)) { // TODO: Check equipped weapon and equip a different one if we can't attack with it // (e.g. no ammunition, or wrong type of ammunition equipped, etc. autoEquip is not very smart in this regard)) //Get weapon speed and range MWWorld::ContainerStoreIterator weaponSlot = MWMechanics::getActiveWeapon(actorClass.getCreatureStats(actor), actorClass.getInventoryStore(actor), &weaptype); if (weaptype == WeapType_HandToHand) { static float fHandToHandReach = world->getStore().get<ESM::GameSetting>().find("fHandToHandReach")->getFloat(); weapRange = fHandToHandReach; } else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell && weaptype != WeapType_None) { // All other WeapTypes are actually weapons, so get<ESM::Weapon> is safe. weapon = weaponSlot->get<ESM::Weapon>()->mBase; weapRange = weapon->mData.mReach; } weapRange *= 100.0f; } else //is creature { weaptype = actorClass.getCreatureStats(actor).getDrawState() == DrawState_Spell ? WeapType_Spell : WeapType_HandToHand; weapRange = 150.0f; //TODO: use true attack range (the same problem in Creature::hit) } bool distantCombat = false; if (weaptype != WeapType_Spell) { // TODO: move to ActionWeapon if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) { rangeAttack = 1000; rangeFollow = 0; // not needed in ranged combat distantCombat = true; } else { rangeAttack = weapRange; rangeFollow = 300; } } else { distantCombat = (rangeAttack > 500); weapRange = 150.f; } float& strength = storage.mStrength; // start new attack if(readyToAttack) { if(timerAttack <= -attacksPeriod) { attack = true; // attack starts just now if (!distantCombat) attackType = chooseBestAttack(weapon, movement); else attackType = ESM::Weapon::AT_Chop; // cause it's =0 strength = OEngine::Misc::Rng::rollClosedProbability(); // Note: may be 0 for some animations timerAttack = minMaxAttackDuration[attackType][0] + (minMaxAttackDuration[attackType][1] - minMaxAttackDuration[attackType][0]) * strength; //say a provoking combat phrase if (actor.getClass().isNpc()) { const MWWorld::ESMStore &store = world->getStore(); int chance = store.get<ESM::GameSetting>().find("iVoiceAttackOdds")->getInt(); if (OEngine::Misc::Rng::roll0to99() < chance) { MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); } } } } /* * Some notes on meanings of variables: * * rangeAttack: * * - Distance where attack using the actor's weapon is possible: * longer for ranged weapons (obviously?) vs. melee weapons * - Determined by weapon's reach parameter; hardcoded value * for ranged weapon and for creatures * - Once within this distance mFollowTarget is triggered * * rangeFollow: * * - Applies to melee weapons or hand to hand only (or creatures without * weapons) * - Distance a little further away than the actor's weapon reach * i.e. rangeFollow > rangeAttack for melee weapons * - Hardcoded value (0 for ranged weapons) * - Once the target gets beyond this distance mFollowTarget is cleared * and a path to the target needs to be found * * mFollowTarget: * * - Once triggered, the actor follows the target with LOS shortcut * (the shortcut really only applies to cells where pathgrids are * available, since the default path without pathgrids is direct to * target even if LOS is not achieved) */ ESM::Position pos = actor.getRefData().getPosition(); Ogre::Vector3 vActorPos(pos.pos); Ogre::Vector3 vTargetPos(target.getRefData().getPosition().pos); Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos; float distToTarget = vDirToTarget.length(); Ogre::Vector3& lastActorPos = storage.mLastActorPos; bool& followTarget = storage.mFollowTarget; bool isStuck = false; float speed = 0.0f; if(movement.mPosition[1] && (lastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * tReaction / 2) isStuck = true; lastActorPos = vActorPos; // check if actor can move along z-axis bool canMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor); // for distant combat we should know if target is in LOS even if distToTarget < rangeAttack bool inLOS = distantCombat ? world->getLOS(actor, target) : true; // can't fight if attacker can't go where target is. E.g. A fish can't attack person on land. if (distToTarget >= rangeAttack && !actorClass.isNpc() && !MWMechanics::isEnvironmentCompatible(actor, target)) { // TODO: start fleeing? movement.mPosition[0] = 0; movement.mPosition[1] = 0; movement.mPosition[2] = 0; readyToAttack = false; actorClass.getCreatureStats(actor).setAttackingOrSpell(false); return false; } // (within attack dist) || (not quite attack dist while following) if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && followTarget && !isStuck))) { //Melee and Close-up combat // getXAngleToDir determines vertical angle to target: // if actor can move along z-axis it will control movement dir // if can't - it will control correct aiming. // note: in getZAngleToDir if we preserve dir.z then horizontal angle can be inaccurate if (distantCombat) { Ogre::Vector3& lastTargetPos = storage.mLastTargetPos; Ogre::Vector3 vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, tReaction, weaptype, strength); lastTargetPos = vTargetPos; movement.mRotation[0] = getXAngleToDir(vAimDir); movement.mRotation[2] = getZAngleToDir(vAimDir); } else { movement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); movement.mRotation[2] = getZAngleToDir(vDirToTarget); } // (not quite attack dist while following) if (followTarget && distToTarget > rangeAttack) { //Close-up combat: just run up on target movement.mPosition[1] = 1; } else // (within attack dist) { if(movement.mPosition[0] || movement.mPosition[1]) { timerCombatMove = 0.1f + 0.1f * OEngine::Misc::Rng::rollClosedProbability(); combatMove = true; } // only NPCs are smart enough to use dodge movements else if(actorClass.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2))) { //apply sideway movement (kind of dodging) with some probability if (OEngine::Misc::Rng::rollClosedProbability() < 0.25) { movement.mPosition[0] = OEngine::Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; timerCombatMove = 0.05f + 0.15f * OEngine::Misc::Rng::rollClosedProbability(); combatMove = true; } } if(distantCombat && distToTarget < rangeAttack/4) { movement.mPosition[1] = -1; } readyToAttack = true; //only once got in melee combat, actor is allowed to use close-up shortcutting followTarget = true; } } else // remote pathfinding { bool preferShortcut = false; if (!distantCombat) inLOS = world->getLOS(actor, target); // check if shortcut is available bool& forceNoShortcut = storage.mForceNoShortcut; ESM::Position& shortcutFailPos = storage.mShortcutFailPos; if(inLOS && (!isStuck || readyToAttack) && (!forceNoShortcut || (Ogre::Vector3(shortcutFailPos.pos) - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) { if(speed == 0.0f) speed = actorClass.getSpeed(actor); // maximum dist before pit/obstacle for actor to avoid them depending on his speed float maxAvoidDist = tReaction * speed + speed / MAX_VEL_ANGULAR.valueRadians() * 2; // *2 - for reliability preferShortcut = checkWayIsClear(vActorPos, vTargetPos, Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); } // don't use pathgrid when actor can move in 3 dimensions if(canMoveByZ) preferShortcut = true; if(preferShortcut) { if (canMoveByZ) movement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); movement.mRotation[2] = getZAngleToDir(vDirToTarget); forceNoShortcut = false; shortcutFailPos.pos[0] = shortcutFailPos.pos[1] = shortcutFailPos.pos[2] = 0; mPathFinder.clearPath(); } else // if shortcut failed stick to path grid { if(!isStuck && shortcutFailPos.pos[0] == 0.0f && shortcutFailPos.pos[1] == 0.0f && shortcutFailPos.pos[2] == 0.0f) { forceNoShortcut = true; shortcutFailPos = pos; } followTarget = false; buildNewPath(actor, target); //may fail to build a path, check before use //delete visited path node mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1]); // This works on the borders between the path grid and areas with no waypoints. if(inLOS && mPathFinder.getPath().size() > 1) { // get point just before target std::list<ESM::Pathgrid::Point>::const_iterator pntIter = --mPathFinder.getPath().end(); --pntIter; Ogre::Vector3 vBeforeTarget(PathFinder::MakeOgreVector3(*pntIter)); // if current actor pos is closer to target then last point of path (excluding target itself) then go straight on target if(distToTarget <= (vTargetPos - vBeforeTarget).length()) { movement.mRotation[2] = getZAngleToDir(vDirToTarget); preferShortcut = true; } } // if there is no new path, then go straight on target if(!preferShortcut) { if(!mPathFinder.getPath().empty()) movement.mRotation[2] = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); else movement.mRotation[2] = getZAngleToDir(vDirToTarget); } } movement.mPosition[1] = 1; if (readyToAttack) { // to stop possible sideway moving after target moved out of attack range combatMove = true; timerCombatMove = 0; } readyToAttack = false; } if(!isStuck && distToTarget > rangeAttack && !distantCombat) { //special run attack; it shouldn't affect melee combat tactics if(actorClass.getMovementSettings(actor).mPosition[1] == 1) { /* check if actor can overcome the distance = distToTarget - attackerWeapRange less than in time of swinging with weapon (t_swing), then start attacking */ float speed1 = actorClass.getSpeed(actor); float speed2 = target.getClass().getSpeed(target); if(target.getClass().getMovementSettings(target).mPosition[0] == 0 && target.getClass().getMovementSettings(target).mPosition[1] == 0) speed2 = 0; float s1 = distToTarget - weapRange; float t = s1/speed1; float s2 = speed2 * t; float t_swing = minMaxAttackDuration[ESM::Weapon::AT_Thrust][0] + (minMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - minMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * OEngine::Misc::Rng::rollClosedProbability(); if (t + s2/speed1 <= t_swing) { readyToAttack = true; if(timerAttack <= -attacksPeriod) { timerAttack = t_swing; attack = true; } } } } // NOTE: This section gets updated every tReaction, which is currently hard // coded at 250ms or 1/4 second // // TODO: Add a parameter to vary DURATION_SAME_SPOT? if((distToTarget > rangeAttack || followTarget) && mObstacleCheck.check(actor, tReaction)) // check if evasive action needed { // probably walking into another NPC TODO: untested in combat situation // TODO: diagonal should have same animation as walk forward // but doesn't seem to do that? actor.getClass().getMovementSettings(actor).mPosition[0] = 1; actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; // change the angle a bit, too if(mPathFinder.isPathConstructed()) zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); if(followTarget) followTarget = false; // FIXME: can fool actors to stay behind doors, etc. // Related to Bug#1102 and to some degree #1155 as well } return false; }
bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { MWWorld::Ptr target = getTarget(); if (target.isEmpty() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered // with the MechanicsManager ) return false; // Target is not here right now, wait for it to return actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); AiFollowStorage& storage = state.get<AiFollowStorage>(); // AiFollow requires the target to be in range and within sight for the initial activation if (!mActive) { storage.mTimer -= duration; if (storage.mTimer < 0) { if ((actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length2() < 500*500 && MWBase::Environment::get().getWorld()->getLOS(actor, target)) mActive = true; storage.mTimer = 0.5f; } } if (!mActive) return false; ESM::Position pos = actor.getRefData().getPosition(); //position of the actor float followDistance = 180; // When there are multiple actors following the same target, they form a group with each group member at 180*(i+1) distance to the target int i=0; std::list<int> followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingIndices(target); followers.sort(); for (std::list<int>::iterator it = followers.begin(); it != followers.end(); ++it) { if (*it == mFollowIndex) followDistance *= (i+1); ++i; } if(!mAlwaysFollow) //Update if you only follow for a bit { //Check if we've run out of time if (mRemainingDuration != 0) { mRemainingDuration -= duration; if (duration <= 0) return true; } if((pos.pos[0]-mX)*(pos.pos[0]-mX) + (pos.pos[1]-mY)*(pos.pos[1]-mY) + (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < followDistance*followDistance) //Close-ish to final position { if(actor.getCell()->isExterior()) //Outside? { if(mCellId == "") //No cell to travel to return true; } else { if(mCellId == actor.getCell()->getCell()->mName) //Cell to travel to return true; } } } //Set the target destination from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]); const float threshold = 10; if (storage.mMoving) //Stop when you get close storage.mMoving = (dist > followDistance); else storage.mMoving = (dist > followDistance + threshold); if(!storage.mMoving) { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; // turn towards target anyway float directionX = target.getRefData().getPosition().pos[0] - actor.getRefData().getPosition().pos[0]; float directionY = target.getRefData().getPosition().pos[1] - actor.getRefData().getPosition().pos[1]; zTurn(actor, std::atan2(directionX,directionY), osg::DegreesToRadians(5.f)); } else { pathTo(actor, dest, duration); //Go to the destination } //Check if you're far away if(dist > 450) actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run else if(dist < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk return false; }
bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) { const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); mTimer = mTimer + duration; mStuckTimer = mStuckTimer + duration; mTotalTime = mTotalTime + duration; ESM::Position pos = actor.getRefData().getPosition(); if(mTotalTime > mDuration && mDuration != 0) return true; if((pos.pos[0]-mX)*(pos.pos[0]-mX) + (pos.pos[1]-mY)*(pos.pos[1]-mY) + (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100) { if(actor.getCell()->isExterior()) { if(mCellId == "") return true; } else { if(mCellId == actor.getCell()->mCell->mName) return true; } } ESM::Pathgrid::Point dest; dest.mX = target.getRefData().getPosition().pos[0]; dest.mY = target.getRefData().getPosition().pos[1]; dest.mZ = target.getRefData().getPosition().pos[2]; ESM::Pathgrid::Point start; start.mX = pos.pos[0]; start.mY = pos.pos[1]; start.mZ = pos.pos[2]; if(mPathFinder.getPath().empty()) mPathFinder.buildPath(start, dest, actor.getCell(), true); if(mTimer > 0.25) { if(!mPathFinder.getPath().empty()) { ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); if((dest.mX - lastPos.mX)*(dest.mX - lastPos.mX) +(dest.mY - lastPos.mY)*(dest.mY - lastPos.mY) +(dest.mZ - lastPos.mZ)*(dest.mZ - lastPos.mZ) > 100*100) mPathFinder.addPointToPath(dest); } mTimer = 0; } if(mStuckTimer>0.5) { if((mStuckPos.pos[0] - pos.pos[0])*(mStuckPos.pos[0] - pos.pos[0]) +(mStuckPos.pos[1] - pos.pos[1])*(mStuckPos.pos[1] - pos.pos[1]) +(mStuckPos.pos[2] - pos.pos[2])*(mStuckPos.pos[2] - pos.pos[2]) < 100) //NPC is stuck mPathFinder.buildPath(start, dest, actor.getCell(), true); mStuckTimer = 0; mStuckPos = pos; } if(!mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) { zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); } if((dest.mX - pos.pos[0])*(dest.mX - pos.pos[0])+(dest.mY - pos.pos[1])*(dest.mY - pos.pos[1])+(dest.mZ - pos.pos[2])*(dest.mZ - pos.pos[2]) < 100*100) actor.getClass().getMovementSettings(actor).mPosition[1] = 0; else actor.getClass().getMovementSettings(actor).mPosition[1] = 1; return false; }