bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) { ESM::Position pos = actor.getRefData().getPosition(); //position of the actor const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow if(target == MWWorld::Ptr()) return true; //Target doesn't exist //Set the target desition from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 200) { //Stop when you get close actor.getClass().getMovementSettings(actor).mPosition[1] = 0; MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); MWBase::Environment::get().getWorld()->activate(target, actor); return true; } else { pathTo(actor, dest, duration); //Go to the destination } return false; }
bool AiActivate::execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should check whether the target is currently registered // with the MechanicsManager ) return true; //Target doesn't exist //Set the target destination for the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; if (pathTo(actor, dest, duration, MWBase::Environment::get().getWorld()->getMaxActivationDistance())) //Stop when you get in activation range { // activate when reached MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); MWBase::Environment::get().getWorld()->activate(target, actor); return true; } return false; }
bool AiPursue::execute (const MWWorld::Ptr& actor, float duration) { ESM::Position pos = actor.getRefData().getPosition(); //position of the actor const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); //The target to follow if(target == MWWorld::Ptr()) return true; //Target doesn't exist actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); //Set the target desition from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 100) { //Stop when you get close actor.getClass().getMovementSettings(actor).mPosition[1] = 0; target.getClass().activate(target,actor).get()->execute(actor); //Arrest player return true; } else { pathTo(actor, dest, duration); //Go to the destination } actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run return false; }
vector<int> findMinHeightTrees(int n, vector<pair<int, int>>& edges) { // Init VI ans; VVI adj(n); QI q; VI d(n, -1), pathTo(n); for(auto& pr: edges) { int u = pr.first, v = pr.second; adj[u].push_back(v); adj[v].push_back(u); } // Find one endpoint of longest path int start = 0; q.push(0); d[0] = 0; while(!q.empty()) { int u = q.front(); q.pop(); start = u; for(int v: adj[u]) { if(d[v] == -1) { d[v] = d[u] + 1; q.push(v); } } } // Find longest path int maxd = 0, end = start; q = QI(); fill(d.begin(), d.end(), -1); q.push(start); d[start] = 0; while(!q.empty()) { int u = q.front(); q.pop(); maxd = d[u]; end = u; for(int v: adj[u]) { if(d[v] == -1) { pathTo[v] = u; d[v] = d[u] + 1; q.push(v); } } } // Find mid points int s = end; for(int i = 0; i < maxd / 2; i++) { s = pathTo[s]; } ans.push_back(s); if(maxd & 1) { ans.push_back(pathTo[s]); } return ans; }
void BreadthFirstSearch::printByString(int e) { vector<int> links = pathTo(e); cout << s << " to " << e << " :"; for (auto x : links) { cout << " " << x; } cout << endl; }
int pathTo(int v){ int s = edgeTo[v]; if(s!=source){ pathTo(s); } cout << s << "-" << v << endl; }
bool AiCombat::execute (const MWWorld::Ptr& actor, CharacterController& characterController, 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; if (!storage.isFleeing()) { if (storage.mCurrentAction.get()) // need to wait to init action with its attack range { //Update every frame. UpdateLOS uses a timer, so the LOS check does not happen every frame. updateLOS(actor, target, duration, storage); float targetReachedTolerance = 0.0f; if (storage.mLOS) targetReachedTolerance = storage.mAttackRange; bool is_target_reached = pathTo(actor, target.getRefData().getPosition().pos, duration, targetReachedTolerance); if (is_target_reached) storage.mReadyToAttack = true; } storage.updateCombatMove(duration); if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage); storage.updateAttack(characterController); } else { updateFleeing(actor, target, duration, storage); } storage.mActionCooldown -= duration; float& timerReact = storage.mTimerReact; if (timerReact < AI_REACTION_TIME) { timerReact += duration; } else { timerReact = 0; if (attack(actor, target, storage, characterController)) return true; } return false; }
bool AiEscort::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, 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(mRemainingDuration != 0) { mRemainingDuration -= duration; if (duration <= 0) return true; } if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), actor.getRefData().getPosition().asVec3())) return false; if (!mCellId.empty() && mCellId != actor.getCell()->getCell()->getCellId().mWorldspace) return false; // Not in the correct cell, pause and rely on the player to go back through a teleport door actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); 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]); double distanceBetweenResult = (differenceBetween[0] * differenceBetween[0]) + (differenceBetween[1] * differenceBetween[1]) + (differenceBetween[2] * differenceBetween[2]); if(distanceBetweenResult <= mMaxDist * mMaxDist) { ESM::Pathgrid::Point point(static_cast<int>(mX), static_cast<int>(mY), static_cast<int>(mZ)); point.mAutogenerated = 0; point.mConnectionNum = 0; point.mUnknown = 0; if(pathTo(actor,point,duration)) //Returns true on path complete return true; mMaxDist = 450; } else { // Stop moving if the player is to far away MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); actor.getClass().getMovementSettings(actor).mPosition[1] = 0; mMaxDist = 250; } 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 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 = target.getRefData().getPosition().pos; if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 100) //Stop when you get close actor.getClass().getMovementSettings(actor).mPosition[1] = 0; else { pathTo(actor, dest, duration); //Go to the destination } //Check if you're far away if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 1000) actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run else if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 800) //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 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(mRemainingDuration != 0) { mRemainingDuration -= duration; if (duration <= 0) return true; } actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); 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) { ESM::Pathgrid::Point point(mX,mY,mZ); point.mAutogenerated = 0; point.mConnectionNum = 0; point.mUnknown = 0; if(pathTo(actor,point,duration)) //Returns true on path complete return true; mMaxDist = 450; } else { // Stop moving if the player is to far away MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); actor.getClass().getMovementSettings(actor).mPosition[1] = 0; mMaxDist = 250; } return false; }
bool AiTravel::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { ESM::Position pos = actor.getRefData().getPosition(); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), pos.asVec3())) return false; if (pathTo(actor, ESM::Pathgrid::Point(static_cast<int>(mX), static_cast<int>(mY), static_cast<int>(mZ)), duration)) { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; return true; } return false; }
bool AiEscort::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, 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) { mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); if (mRemainingDuration <= 0) { mRemainingDuration = mDuration; return true; } } if (!mCellId.empty() && mCellId != actor.getCell()->getCell()->getCellId().mWorldspace) return false; // Not in the correct cell, pause and rely on the player to go back through a teleport door actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false); const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3(); const osg::Vec3f followerPos = follower.getRefData().getPosition().asVec3(); if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist) { const osg::Vec3f dest(mX, mY, mZ); if (pathTo(actor, dest, duration)) //Returns true on path complete { mRemainingDuration = mDuration; return true; } mMaxDist = 450; } else { // Stop moving if the player is too far away MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); actor.getClass().getMovementSettings(actor).mPosition[1] = 0; mMaxDist = 250; } return false; }
bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { if(actor.getClass().getCreatureStats(actor).isDead()) return true; const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); //The target to follow if(target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered // with the MechanicsManager ) return true; //Target doesn't exist if (isTargetMagicallyHidden(target)) return true; if(target.getClass().getCreatureStats(target).isDead()) return true; actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); //Set the target desition from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; ESM::Position aPos = actor.getRefData().getPosition(); float pathTolerance = 100.0; if (pathTo(actor, dest, duration, pathTolerance) && std::abs(dest.mZ - aPos.pos[2]) < pathTolerance) // check the true distance in case the target is far away in Z-direction { target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached return true; } actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run 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 // The distances below are approximations based on observations of the original engine. // If only one actor is following the target, it uses 186. // If there are multiple actors following the same target, they form a group with each group member at 313 + (130 * i) distance to the target. short followDistance = 186; std::list<int> followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingIndices(target); if (followers.size() >= 2) { followDistance = 313; short i = 0; followers.sort(); for (std::list<int>::iterator it = followers.begin(); it != followers.end(); ++it) { if (*it == mFollowIndex) followDistance += 130 * i; ++i; } } if (!mAlwaysFollow) //Update if you only follow for a bit { //Check if we've run out of time if (mDuration > 0) { mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); if (mRemainingDuration <= 0) { mRemainingDuration = mDuration; 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; if (!storage.mMoving) { const short threshold = 10; // to avoid constant switching between moving/stopping followDistance += threshold; } storage.mMoving = !pathTo(actor, dest, duration, followDistance); // Go to the destination if (storage.mMoving) { //Check if you're far away float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]); 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 threshold actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk } return false; }
void MWMechanics::AiCombat::updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage) { static const float BLIND_RUN_DURATION = 1.0f; updateLOS(actor, target, duration, storage); AiCombatStorage::FleeState& state = storage.mFleeState; switch (state) { case AiCombatStorage::FleeState_None: return; case AiCombatStorage::FleeState_Idle: { float triggerDist = getMaxAttackDistance(target); if (storage.mLOS && (triggerDist >= 1000 || getDistanceMinusHalfExtents(actor, target) <= triggerDist)) { const ESM::Pathgrid* pathgrid = MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*storage.mCell->getCell()); bool runFallback = true; if (pathgrid && !actor.getClass().isPureWaterCreature(actor)) { ESM::Pathgrid::PointList points; CoordinateConverter coords(storage.mCell->getCell()); osg::Vec3f localPos = actor.getRefData().getPosition().asVec3(); coords.toLocal(localPos); int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, localPos); for (int i = 0; i < static_cast<int>(pathgrid->mPoints.size()); i++) { if (i != closestPointIndex && getPathGridGraph(storage.mCell).isPointConnected(closestPointIndex, i)) { points.push_back(pathgrid->mPoints[static_cast<size_t>(i)]); } } if (!points.empty()) { ESM::Pathgrid::Point dest = points[Misc::Rng::rollDice(points.size())]; coords.toWorld(dest); state = AiCombatStorage::FleeState_RunToDestination; storage.mFleeDest = ESM::Pathgrid::Point(dest.mX, dest.mY, dest.mZ); runFallback = false; } } if (runFallback) { state = AiCombatStorage::FleeState_RunBlindly; storage.mFleeBlindRunTimer = 0.0f; } } } break; case AiCombatStorage::FleeState_RunBlindly: { // timer to prevent twitchy movement that can be observed in vanilla MW if (storage.mFleeBlindRunTimer < BLIND_RUN_DURATION) { storage.mFleeBlindRunTimer += duration; storage.mMovement.mRotation[2] = osg::PI + getZAngleToDir(target.getRefData().getPosition().asVec3()-actor.getRefData().getPosition().asVec3()); storage.mMovement.mPosition[1] = 1; updateActorsMovement(actor, duration, storage); } else state = AiCombatStorage::FleeState_Idle; } break; case AiCombatStorage::FleeState_RunToDestination: { static const float fFleeDistance = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fFleeDistance")->mValue.getFloat(); float dist = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length(); if ((dist > fFleeDistance && !storage.mLOS) || pathTo(actor, storage.mFleeDest, duration)) { state = AiCombatStorage::FleeState_Idle; } } break; }; }
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; }