From c773ed9f9ad71c144cafbee40677a04bc1e3305c Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sat, 4 Jul 2015 18:00:16 +0300 Subject: [PATCH 01/54] move checkWayIsClear to pathfinding; move shortcut logic to separate func (AiPackage::shortcutPath); rework AiPackage::pathTo --- apps/openmw/mwmechanics/aiactivate.cpp | 11 +- apps/openmw/mwmechanics/aicombat.cpp | 42 +------ apps/openmw/mwmechanics/aifollow.cpp | 14 +-- apps/openmw/mwmechanics/aipackage.cpp | 153 ++++++++++++++++++++++-- apps/openmw/mwmechanics/aipackage.hpp | 15 ++- apps/openmw/mwmechanics/aipursue.cpp | 8 +- apps/openmw/mwmechanics/aiwander.cpp | 4 +- apps/openmw/mwmechanics/pathfinding.cpp | 78 +++++++++--- apps/openmw/mwmechanics/pathfinding.hpp | 26 +++- 9 files changed, 248 insertions(+), 103 deletions(-) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 9e25084d3..aa937022d 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -34,20 +34,17 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor, AiState& state ) return true; //Target doesn't exist - //Set the target desition from the actor + //Set the target destination for 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; + if (pathTo(actor, dest, duration, 200)) //Go to the destination + { + // activate when reached 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; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 8f43c6280..fb47c1109 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -37,46 +37,6 @@ namespace Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos, float duration, int weapType, float strength); - - float getZAngleToDir(const Ogre::Vector3& dir) - { - return Ogre::Math::ATan2(dir.x,dir.y).valueDegrees(); - } - - float getXAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f) - { - float len = (dirLen > 0.0f)? dirLen : dir.length(); - return -Ogre::Math::ASin(dir.z / len).valueDegrees(); - } - - - const float PATHFIND_Z_REACH = 50.0f; - // distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid - const float PATHFIND_CAUTION_DIST = 500.0f; - // distance after which actor (failed previously to shortcut) will try again - const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; - - // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; - // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH - bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY) - { - if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z - to.z) <= PATHFIND_Z_REACH) - { - Ogre::Vector3 dir = to - from; - dir.z = 0; - dir.normalise(); - float verticalOffset = 200; // instead of '200' here we want the height of the actor - Ogre::Vector3 _from = from + dir*offsetXY + Ogre::Vector3::UNIT_Z * verticalOffset; - - // cast up-down ray and find height in world space of hit - float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1); - - if(std::abs(from.z - h) <= PATHFIND_Z_REACH) - return true; - } - - return false; - } } namespace MWMechanics @@ -702,7 +662,7 @@ namespace MWMechanics if (doesPathNeedRecalc(newPathTarget, actor.getCell()->getCell())) { ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actor.getRefData().getPosition())); - mPathFinder.buildSyncedPath(start, newPathTarget, actor.getCell(), false); + mPathFinder.buildSyncedPath(start, newPathTarget, actor.getCell()); } } diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index ddfc14581..55414bfff 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -130,19 +130,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, float duratio //Set the target destination from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < followDistance) //Stop when you get close - { - 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, Ogre::Math::ATan2(directionX,directionY), Ogre::Degree(5)); - } - else - { - pathTo(actor, dest, duration); //Go to the destination - } + pathTo(actor, dest, duration, followDistance); //Go to the destination //Check if you're far away if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 450) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 216bf7b09..425593624 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -16,12 +16,14 @@ MWMechanics::AiPackage::~AiPackage() {} -MWMechanics::AiPackage::AiPackage() : mTimer(0.26f), mStuckTimer(0) { //mTimer starts at .26 to force initial pathbuild - +MWMechanics::AiPackage::AiPackage() : + mTimer(0.26f), mStuckTimer(0), //mTimer starts at .26 to force initial pathbuild + mShortcutProhibited(false), mShortcutFailPos() +{ } -bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration) +bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance) { //Update various Timers mTimer += duration; //Update timer @@ -63,13 +65,51 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po //*********************** /// 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 + /// Rebuilds path every [AI_REACTION_TIME] seconds, in case the target has moved //*********************** - if(mTimer > 0.25) + + bool isStuck = distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < actor.getClass().getSpeed(actor)*0.05 + && distance(dest, start) > 20; + + float distToNextWaypoint = distance(start, dest); + + bool isDestReached = (distToNextWaypoint <= destTolerance); + + if (!isDestReached && mTimer > AI_REACTION_TIME) { - if (doesPathNeedRecalc(dest, cell)) { //Only rebuild path if it's moved - mPathFinder.buildSyncedPath(start, dest, actor.getCell(), true); //Rebuild path, in case the target has moved - mPrevDest = dest; + bool needPathRecalc = doesPathNeedRecalc(dest, cell); + + bool isWayClear = true; + + if (!needPathRecalc) // TODO: add check if actor is actually shortcutting + { + isWayClear = checkWayIsClearForActor(start, dest, actor); // check if current shortcut is safe to follow + } + + if (!isWayClear || needPathRecalc) // Only rebuild path if the target has moved or can't follow current shortcut + { + bool destInLOS = false; + + if (isStuck || !isWayClear || !shortcutPath(start, dest, actor, &destInLOS)) + { + mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + + // 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::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 @@ -86,13 +126,22 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po //************************ /// Checks if you aren't moving; attempts to unstick you //************************ - if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1])) //Path finished? + if (isDestReached || mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1])) //Path finished? + { + actor.getClass().getMovementSettings(actor).mPosition[0] = 0; // stop moving + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + actor.getClass().getMovementSettings(actor).mPosition[2] = 0; + + // turn to destination point + zTurn(actor, Ogre::Degree(getZAngleToPoint(start, dest))); + smoothTurn(actor, Ogre::Degree(getXAngleToPoint(start, dest)), 0); 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]) < actor.getClass().getSpeed(actor)*0.05 && distance(dest, start) > 20) { //Actually stuck, and far enough away from destination to care + if (isStuck) { //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 @@ -119,12 +168,94 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward the rest of the time } + // turn to next path point by X,Z axes zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + smoothTurn(actor, Ogre::Degree(mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2])), 0); return false; } +bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS) +{ + const MWWorld::Class& actorClass = actor.getClass(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + + // check if actor can move along z-axis + bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || world->isFlying(actor); + + // don't use pathgrid when actor can move in 3 dimensions + bool isPathClear = actorCanMoveByZ; + + if (!isPathClear + && (!mShortcutProhibited || (PathFinder::MakeOgreVector3(mShortcutFailPos) - PathFinder::MakeOgreVector3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) + { + // take the direct path only if there aren't any obstacles + isPathClear = !MWBase::Environment::get().getWorld()->castRay( + static_cast(startPoint.mX), static_cast(startPoint.mY), static_cast(startPoint.mZ), + static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ)); + + if (destInLOS != NULL) *destInLOS = isPathClear; + + if (!isPathClear) + return false; + + // check if an actor can move along the shortcut path + isPathClear = checkWayIsClearForActor(startPoint, endPoint, actor); + } + + if (isPathClear) // can shortcut the path + { + mPathFinder.clearPath(); + mPathFinder.addPointToPath(endPoint); + return true; + } + + return false; +} + +bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor) +{ + bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || MWBase::Environment::get().getWorld()->isFlying(actor); + + if (actorCanMoveByZ) + return true; + + float actorSpeed = actor.getClass().getSpeed(actor); + float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR.valueRadians() * 2; // *2 - for reliability + Ogre::Real distToTarget = Ogre::Vector3(static_cast(endPoint.mX), static_cast(endPoint.mY), 0).length(); + + float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2; + + bool isClear = checkWayIsClear(PathFinder::MakeOgreVector3(startPoint), PathFinder::MakeOgreVector3(endPoint), offsetXY); + + // update shortcut prohibit state + if (isClear) + { + if (mShortcutProhibited) + { + mShortcutProhibited = false; + mShortcutFailPos = ESM::Pathgrid::Point(); + } + } + if (!isClear) + { + if (mShortcutFailPos.mX == 0 && mShortcutFailPos.mY == 0 && mShortcutFailPos.mZ == 0) + { + mShortcutProhibited = true; + mShortcutFailPos = startPoint; + } + } + + return isClear; +} + bool MWMechanics::AiPackage::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) { - return distance(mPrevDest, dest) > 10; + bool needRecalc = distance(mPrevDest, dest) > 10; + if (needRecalc) + mPrevDest = dest; + + return needRecalc; } \ No newline at end of file diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 78a2bfd9f..a22ac9357 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -24,6 +24,7 @@ namespace ESM namespace MWMechanics { + const float AI_REACTION_TIME = 0.25f; /// \brief Base class for AI packages class AiPackage @@ -70,7 +71,16 @@ namespace MWMechanics protected: /// Causes the actor to attempt to walk to the specified location /** \return If the actor has arrived at his destination **/ - bool pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration); + bool pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance = 0.0f); + + /// Check if there aren't any obstacles along the path to make shortcut possible + /// If a shortcut is possible then path will be cleared and filled with the destination point. + /// \param destInLOS If not NULL function will return ray cast check result + /// \return If can shortcut the path + bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS); + + /// Check if the way to the destination is clear, taking into account actor speed + bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor); virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); @@ -83,6 +93,9 @@ namespace MWMechanics ESM::Position mStuckPos; ESM::Pathgrid::Point mPrevDest; + + bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt + ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail }; } diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 8c31a10db..af007231b 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -55,14 +55,10 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, AiState& state, float duratio //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 + if (pathTo(actor, dest, duration, 100)) { + target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached return true; } - else { - pathTo(actor, dest, duration); //Go to the destination - } actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 1f4133c0a..4566740e3 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -413,7 +413,7 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); // don't take shortcuts for wandering - storage.mPathFinder.buildPath(start, dest, actor.getCell(), false); + storage.mPathFinder.buildPath(start, dest, actor.getCell()); if(storage.mPathFinder.isPathConstructed()) { @@ -520,7 +520,7 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); // don't take shortcuts for wandering - storage.mPathFinder.buildPath(start, dest, actor.getCell(), false); + storage.mPathFinder.buildPath(start, dest, actor.getCell()); if(storage.mPathFinder.isPathConstructed()) { diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index fea993e23..c32899dde 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -113,6 +113,49 @@ namespace MWMechanics return sqrt(x * x + y * y + z * z); } + float getZAngleToDir(const Ogre::Vector3& dir) + { + return Ogre::Math::ATan2(dir.x,dir.y).valueDegrees(); + } + + float getXAngleToDir(const Ogre::Vector3& dir, float dirLen) + { + float len = (dirLen > 0.0f)? dirLen : dir.length(); + return -Ogre::Math::ASin(dir.z / len).valueDegrees(); + } + + float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) + { + Ogre::Vector3 dir = PathFinder::MakeOgreVector3(dest) - PathFinder::MakeOgreVector3(origin); + return getZAngleToDir(dir); + } + + float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) + { + Ogre::Vector3 dir = PathFinder::MakeOgreVector3(dest) - PathFinder::MakeOgreVector3(origin); + return getXAngleToDir(dir); + } + + bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY) + { + if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z - to.z) <= PATHFIND_Z_REACH) + { + Ogre::Vector3 dir = to - from; + dir.z = 0; + dir.normalise(); + float verticalOffset = 200; // instead of '200' here we want the height of the actor + Ogre::Vector3 _from = from + dir*offsetXY + Ogre::Vector3::UNIT_Z * verticalOffset; + + // cast up-down ray and find height in world space of hit + float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1); + + if(std::abs(from.z - h) <= PATHFIND_Z_REACH) + return true; + } + + return false; + } + PathFinder::PathFinder() : mPathgrid(NULL), mCell(NULL) @@ -165,23 +208,10 @@ namespace MWMechanics */ void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, - bool allowShortcuts) + const MWWorld::CellStore* cell) { mPath.clear(); - if(allowShortcuts) - { - // if there's a ray cast hit, can't take a direct path - if (!MWBase::Environment::get().getWorld()->castRay( - static_cast(startPoint.mX), static_cast(startPoint.mY), static_cast(startPoint.mZ), - static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ))) - { - mPath.push_back(endPoint); - return; - } - } - if(mCell != cell || !mPathgrid) { mCell = cell; @@ -270,6 +300,19 @@ namespace MWMechanics return Ogre::Math::ATan2(directionX,directionY).valueDegrees(); } + float PathFinder::getXAngleToNext(float x, float y, float z) const + { + // This should never happen (programmers should have an if statement checking + // isPathConstructed that prevents this call if otherwise). + if(mPath.empty()) + return 0.; + + const ESM::Pathgrid::Point &nextPoint = *mPath.begin(); + Ogre::Vector3 dir = MakeOgreVector3(nextPoint) - Ogre::Vector3(x,y,z); + + return -Ogre::Math::ASin(dir.z / dir.length()).valueDegrees(); + } + bool PathFinder::checkPathCompleted(float x, float y, float tolerance) { if(mPath.empty()) @@ -291,19 +334,18 @@ namespace MWMechanics // see header for the rationale void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, - bool allowShortcuts) + const MWWorld::CellStore* cell) { if (mPath.size() < 2) { // if path has one point, then it's the destination. // don't need to worry about bad path for this case - buildPath(startPoint, endPoint, cell, allowShortcuts); + buildPath(startPoint, endPoint, cell); } else { const ESM::Pathgrid::Point oldStart(*getPath().begin()); - buildPath(startPoint, endPoint, cell, allowShortcuts); + buildPath(startPoint, endPoint, cell); if (mPath.size() >= 2) { // if 2nd waypoint of new path == 1st waypoint of old, diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 644d79236..c978c9afa 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -15,8 +15,24 @@ namespace MWWorld namespace MWMechanics { - float distance(ESM::Pathgrid::Point point, float x, float y, float); + float distance(ESM::Pathgrid::Point point, float x, float y, float z); float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b); + float getZAngleToDir(const Ogre::Vector3& dir); + float getXAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f); + float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest); + float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest); + + const float PATHFIND_Z_REACH = 50.0f; + //static const float sMaxSlope = 49.0f; // duplicate as in physicssystem + // distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid + const float PATHFIND_CAUTION_DIST = 500.0f; + // distance after which actor (failed previously to shortcut) will try again + const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; + + // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; + // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH + bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY); + class PathFinder { public: @@ -39,13 +55,15 @@ namespace MWMechanics void clearPath(); void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, bool allowShortcuts = true); + const MWWorld::CellStore* cell); bool checkPathCompleted(float x, float y, float tolerance=32.f); ///< \Returns true if we are within \a tolerance units of the last path point. float getZAngleToNext(float x, float y) const; + float getXAngleToNext(float x, float y, float z) const; + bool isPathConstructed() const { return !mPath.empty(); @@ -69,9 +87,9 @@ namespace MWMechanics Which results in NPC "running in a circle" back to the just passed waypoint. */ void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, bool allowShortcuts = true); + const MWWorld::CellStore* cell); - void addPointToPath(ESM::Pathgrid::Point &point) + void addPointToPath(const ESM::Pathgrid::Point &point) { mPath.push_back(point); } From b960b0af9368643d30edc1394179efbbd65c4fc8 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Fri, 1 Jan 2016 16:41:45 +0300 Subject: [PATCH 02/54] rewrite pathTo for clearer logic; reapply Scrawl's aifollow threshold --- apps/openmw/mwmechanics/aifollow.cpp | 24 ++++---- apps/openmw/mwmechanics/aipackage.cpp | 88 ++++++++++----------------- apps/openmw/mwmechanics/aipackage.hpp | 3 +- 3 files changed, 45 insertions(+), 70 deletions(-) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index f0e67af08..342ee9714 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -127,21 +127,21 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte //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); + const float threshold = 10; // to avoid constant switching between moving/stopping + if (!storage.mMoving) followDistance += threshold; storage.mMoving = !pathTo(actor, dest, duration, followDistance); // 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 + 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 threshhold + actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk + } return false; } diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 571bf0ac8..2999e8a19 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -22,6 +22,7 @@ MWMechanics::AiPackage::~AiPackage() {} MWMechanics::AiPackage::AiPackage() : mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild + mIsShortcutting(false), mShortcutProhibited(false), mShortcutFailPos() { } @@ -41,10 +42,8 @@ bool MWMechanics::AiPackage::followTargetThroughDoors() const return false; } - bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance) { - //Update various Timers mTimer += duration; //Update timer ESM::Position pos = actor.getRefData().getPosition(); //position of the actor @@ -59,39 +58,21 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr return false; } - //*********************** - /// Checks if you can't get to the end position at all, adds end position to end of path - /// Rebuilds path every [AI_REACTION_TIME] seconds, in case the target has moved - //*********************** - + // handle path building and shortcutting ESM::Pathgrid::Point start = pos.pos; - float distToNextWaypoint = distance(start, dest); - bool isDestReached = (distToNextWaypoint <= destTolerance); + float distToTarget = distance(start, dest); + bool isDestReached = (distToTarget <= destTolerance); if (!isDestReached && mTimer > AI_REACTION_TIME) { - osg::Vec3f& lastActorPos = mLastActorPos; - bool isStuck = distance(start, lastActorPos.x(), lastActorPos.y(), lastActorPos.z()) < actor.getClass().getSpeed(actor)*mTimer - && distance(dest, start) > 20; + bool wasShortcutting = mIsShortcutting; + bool destInLOS = false; + mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS); // try to shortcut first - lastActorPos = pos.asVec3(); - - const ESM::Cell *cell = actor.getCell()->getCell(); - bool needPathRecalc = doesPathNeedRecalc(dest, cell); //Only rebuild path if dest point has changed - - bool isWayClear = true; - - if (!needPathRecalc) // TODO: add check if actor is actually shortcutting + if (!mIsShortcutting) { - isWayClear = checkWayIsClearForActor(start, dest, actor); // check if current shortcut is safe to follow - } - - if (!isWayClear || needPathRecalc) // Only rebuild path if the target has moved or can't follow current shortcut - { - bool destInLOS = false; - - if (isStuck || !isWayClear || !shortcutPath(start, dest, actor, &destInLOS)) + if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell()->getCell())) // only rebuild path if the target has moved { mPathFinder.buildSyncedPath(start, dest, actor.getCell()); @@ -111,28 +92,21 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr } } } - } - if(!mPathFinder.getPath().empty()) //Path has points in it - { - ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path + 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 + 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 (isDestReached || mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1])) //Path finished? { - actor.getClass().getMovementSettings(actor).mPosition[0] = 0; // stop moving - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - actor.getClass().getMovementSettings(actor).mPosition[2] = 0; - // turn to destination point zTurn(actor, getZAngleToPoint(start, dest)); smoothTurn(actor, getXAngleToPoint(start, dest), 0); @@ -140,6 +114,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr } else { + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // run to target + // handle obstacles on the way evadeObstacles(actor, duration, pos); } @@ -155,24 +131,22 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); - if (mObstacleCheck.check(actor, duration)) + + // 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()) { - // 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); + 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 { //Not stuck, so reset things - movement.mPosition[1] = 1; //Just run forward + else // any other obstacle (NPC, crate, etc.) + { + mObstacleCheck.takeEvasiveAction(movement); } } @@ -191,7 +165,7 @@ bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint if (!isPathClear && (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) { - // take the direct path only if there aren't any obstacles + // check if target is clearly visible isPathClear = !MWBase::Environment::get().getWorld()->castRay( static_cast(startPoint.mX), static_cast(startPoint.mY), static_cast(startPoint.mZ), static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ)); diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 67c2b0c1b..79537e616 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -82,7 +82,7 @@ namespace MWMechanics bool isTargetMagicallyHidden(const MWWorld::Ptr& target); protected: - /// Causes the actor to attempt to walk to the specified location + /// Handles path building and shortcutting with obstacles avoiding /** \return If the actor has arrived at his destination **/ bool pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance = 0.0f); @@ -108,6 +108,7 @@ namespace MWMechanics ESM::Pathgrid::Point mPrevDest; osg::Vec3f mLastActorPos; + bool mIsShortcutting; // if shortcutting at the moment bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail From bcb1f4ed056b6207c2e038247b3ef002c784b52d Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sun, 3 Jan 2016 15:33:52 +0300 Subject: [PATCH 03/54] refactor AiCombat: remove all pathfinding code, adopt new version of AiPackage::pathTo; fix couple of warnings; --- apps/openmw/mwmechanics/aiactivate.cpp | 1 - apps/openmw/mwmechanics/aicombat.cpp | 255 +++++-------------------- apps/openmw/mwmechanics/aicombat.hpp | 9 +- apps/openmw/mwmechanics/aipackage.cpp | 10 +- apps/openmw/mwmechanics/aipackage.hpp | 3 +- apps/openmw/mwmechanics/aipursue.cpp | 1 - 6 files changed, 51 insertions(+), 228 deletions(-) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 348ddd692..b3c011d50 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -22,7 +22,6 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const } bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, 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 actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 6251cff8d..abf4109ba 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -41,7 +41,7 @@ namespace MWMechanics float mTimerCombatMove; bool mReadyToAttack; bool mAttack; - bool mFollowTarget; + float mAttackRange; bool mCombatMove; osg::Vec3f mLastTargetPos; const MWWorld::CellStore* mCell; @@ -50,8 +50,8 @@ namespace MWMechanics float mStrength; bool mForceNoShortcut; ESM::Position mShortcutFailPos; - osg::Vec3f mLastActorPos; MWMechanics::Movement mMovement; + bool mAdjustAiming; AiCombatStorage(): mAttackCooldown(0), @@ -59,7 +59,7 @@ namespace MWMechanics mTimerCombatMove(0), mReadyToAttack(false), mAttack(false), - mFollowTarget(false), + mAttackRange(200), // default attack range (same as in Creature::Hit) mCombatMove(false), mLastTargetPos(0,0,0), mCell(NULL), @@ -67,8 +67,9 @@ namespace MWMechanics mActionCooldown(0), mStrength(), mForceNoShortcut(false), - mLastActorPos(0,0,0), - mMovement(){} + mMovement(), + mAdjustAiming(false) + {} void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack); void updateCombatMove(float duration); @@ -139,6 +140,7 @@ namespace MWMechanics * Use the Observer Pattern to co-ordinate attacks, provide intelligence on * whether the target was hit, etc. */ + bool AiCombat::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { // get or create temporary storage @@ -158,33 +160,33 @@ namespace MWMechanics return true; //Update every frame + storage.mReadyToAttack = pathTo(actor, target.getRefData().getPosition().pos, duration, storage.mAttackRange); + storage.updateCombatMove(duration); - updateActorsMovement(actor, duration, storage.mMovement); + if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage); storage.updateAttack(characterController); storage.mActionCooldown -= duration; - + float& timerReact = storage.mTimerReact; - if(timerReact < AI_REACTION_TIME) + if (timerReact < AI_REACTION_TIME) { timerReact += duration; - return false; } else { timerReact = 0; - return reactionTimeActions(actor, characterController, storage, target); + attack(actor, target, storage, characterController); } + + return false; } - bool AiCombat::reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController, - AiCombatStorage& storage, MWWorld::Ptr target) + void AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController) { - MWMechanics::Movement& movement = storage.mMovement; - if (isTargetMagicallyHidden(target)) { storage.stopAttack(); - return false; // TODO: run away instead of doing nothing + return; // TODO: run away instead of doing nothing } const MWWorld::CellStore*& currentCell = storage.mCell; @@ -199,10 +201,9 @@ namespace MWMechanics float& actionCooldown = storage.mActionCooldown; if (actionCooldown > 0) - return false; + return; - float rangeAttack = 0; - float rangeFollow = 0; + float &rangeAttack = storage.mAttackRange; boost::shared_ptr& currentAction = storage.mCurrentAction; if (characterController.readyToPrepareAttack()) { @@ -210,6 +211,7 @@ namespace MWMechanics actionCooldown = currentAction->getActionCooldown(); } + float rangeFollow; if (currentAction.get()) currentAction->getCombatRange(rangeAttack, rangeFollow); @@ -243,7 +245,7 @@ namespace MWMechanics 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) + weapRange = 200; //TODO: use true attack range (the same problem in Creature::hit) } bool distantCombat = false; @@ -253,55 +255,19 @@ namespace MWMechanics 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; + weapRange = 200; } - - bool& readyToAttack = storage.mReadyToAttack; - // start new attack - storage.startAttackIfReady(actor, characterController, weapon, distantCombat); - - /* - * 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(); osg::Vec3f vActorPos(pos.asVec3()); osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); @@ -309,153 +275,49 @@ namespace MWMechanics osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); - osg::Vec3f& 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)) * AI_REACTION_TIME / 2) - isStuck = true; - - lastActorPos = vActorPos; - - // check if actor can move along z-axis - bool canMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor)) - || world->isFlying(actor); - // 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? storage.stopAttack(); - return false; + return; } - // for distant combat we should know if target is in LOS even if distToTarget < rangeAttack - bool inLOS = distantCombat ? world->getLOS(actor, target) : true; - - // (within attack dist) || (not quite attack dist while following) - if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && followTarget && !isStuck))) + if (storage.mReadyToAttack) { - mPathFinder.clearPath(); - //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 + storage.startCombatMove(actorClass.isNpc(), distantCombat, distToTarget, rangeAttack); + // start new attack + storage.startAttackIfReady(actor, characterController, weapon, distantCombat); + if (distantCombat) { + // rotate actor taking into account target movement direction and projectile speed osg::Vec3f& lastTargetPos = storage.mLastTargetPos; - vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, weaptype, - storage.mStrength); + vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, weaptype, storage.mStrength); lastTargetPos = vTargetPos; + + MWMechanics::Movement& movement = storage.mMovement; movement.mRotation[0] = getXAngleToDir(vAimDir); movement.mRotation[2] = getZAngleToDir(vAimDir); } - else - { - movement.mRotation[0] = getXAngleToDir(vAimDir); - movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated - } - // (not quite attack dist while following) - if (followTarget && distToTarget > rangeAttack) - { - //Close-up combat: just run up on target - storage.stopCombatMove(); - movement.mPosition[1] = 1; - } - else // (within attack dist) - { - storage.startCombatMove(actorClass.isNpc(), distantCombat, distToTarget, rangeAttack); - - readyToAttack = true; - //only once got in melee combat, actor is allowed to use close-up shortcutting - followTarget = true; - } + storage.mAdjustAiming = distantCombat; } - 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 || (shortcutFailPos.asVec3() - 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 = AI_REACTION_TIME * speed + speed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability - preferShortcut = checkWayIsClear(vActorPos, vTargetPos, osg::Vec3f(vAimDir.x(), vAimDir.y(), 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); - } - - // don't use pathgrid when actor can move in 3 dimensions - if (canMoveByZ) - { - preferShortcut = true; - movement.mRotation[0] = getXAngleToDir(vAimDir); - } - - if(preferShortcut) - { - movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); - 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); - - // should always return a path (even if it's just go straight on target.) - assert(mPathFinder.isPathConstructed()); - } - - if (readyToAttack) - { - // to stop possible sideway moving after target moved out of attack range - storage.stopCombatMove(); - readyToAttack = false; - } - movement.mPosition[1] = 1; - } - - return false; } - void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, MWMechanics::Movement& desiredMovement) + void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage) { + // apply combat movement MWMechanics::Movement& actorMovementSettings = actor.getClass().getMovementSettings(actor); - if (mPathFinder.isPathConstructed()) + actorMovementSettings.mPosition[0] = storage.mMovement.mPosition[0]; + actorMovementSettings.mPosition[1] = storage.mMovement.mPosition[1]; + actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2]; + + if (storage.mAdjustAiming) { - const ESM::Position& pos = actor.getRefData().getPosition(); - if (mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) - { - actorMovementSettings.mPosition[1] = 0; - } - else - { - evadeObstacles(actor, duration, pos); - } - } - else - { - actorMovementSettings = desiredMovement; - rotateActorOnAxis(actor, 2, actorMovementSettings, desiredMovement); - rotateActorOnAxis(actor, 0, actorMovementSettings, desiredMovement); + rotateActorOnAxis(actor, 2, actorMovementSettings, storage.mMovement); + rotateActorOnAxis(actor, 0, actorMovementSettings, storage.mMovement); } } @@ -474,35 +336,6 @@ namespace MWMechanics } } - bool AiCombat::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) - { - if (!mPathFinder.getPath().empty()) - { - osg::Vec3f currPathTarget(PathFinder::MakeOsgVec3(mPathFinder.getPath().back())); - osg::Vec3f newPathTarget = PathFinder::MakeOsgVec3(dest); - float dist = (newPathTarget - currPathTarget).length(); - float targetPosThreshold = (cell->isExterior()) ? 300.0f : 100.0f; - return dist > targetPosThreshold; - } - else - { - // necessarily construct a new path - return true; - } - } - - void AiCombat::buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target) - { - ESM::Pathgrid::Point newPathTarget = PathFinder::MakePathgridPoint(target.getRefData().getPosition()); - - //construct new path only if target has moved away more than on [targetPosThreshold] - if (doesPathNeedRecalc(newPathTarget, actor.getCell()->getCell())) - { - ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actor.getRefData().getPosition())); - mPathFinder.buildSyncedPath(start, newPathTarget, actor.getCell()); - } - } - int AiCombat::getTypeId() const { return TypeIdCombat; @@ -542,13 +375,13 @@ namespace MWMechanics mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); mCombatMove = true; } - // only NPCs are smart enough to use dodge movements + // dodge movements (for NPCs only) else if (isNpc && (!isDistantCombat || (distToTarget < rangeAttack / 2))) { //apply sideway movement (kind of dodging) with some probability if (Misc::Rng::rollClosedProbability() < 0.25) { - mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; + mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right mTimerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability(); mCombatMove = true; } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 93d630529..ad683b505 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -53,19 +53,14 @@ namespace MWMechanics virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; - protected: - virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); - private: int mTargetActorId; - void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); - bool reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController, - AiCombatStorage& storage, MWWorld::Ptr target); + void attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController); /// Transfer desired movement (from AiCombatStorage) to Actor - void updateActorsMovement(const MWWorld::Ptr& actor, float duration, MWMechanics::Movement& movement); + void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage); void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement); }; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 2999e8a19..4be464018 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -72,7 +72,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr if (!mIsShortcutting) { - if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell()->getCell())) // only rebuild path if the target has moved + if (wasShortcutting || doesPathNeedRecalc(dest)) // only rebuild path if the target has moved { mPathFinder.buildSyncedPath(start, dest, actor.getCell()); @@ -226,13 +226,11 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& return isClear; } -bool MWMechanics::AiPackage::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) +bool MWMechanics::AiPackage::doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest) { - bool needRecalc = distance(mPrevDest, dest) > 10; - if (needRecalc) - mPrevDest = dest; + if (mPathFinder.getPath().empty()) return true; - return needRecalc; + return (distance(mPathFinder.getPath().back(), newDest) > 10); } bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target) diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 79537e616..2dc8bd3c7 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -95,7 +95,7 @@ namespace MWMechanics /// Check if the way to the destination is clear, taking into account actor speed bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor); - virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); + virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest); void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); @@ -105,7 +105,6 @@ namespace MWMechanics float mTimer; - ESM::Pathgrid::Point mPrevDest; osg::Vec3f mLastActorPos; bool mIsShortcutting; // if shortcutting at the moment diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 649a72cc0..7909b02c9 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -35,7 +35,6 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte if(actor.getClass().getCreatureStats(actor).isDead()) return true; - 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() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered From b304e98568c15f7e180eef68414cabfa076d9753 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Fri, 15 Jan 2016 21:49:27 +0300 Subject: [PATCH 04/54] implement ActionWeapon::getCombatRange (move logic from AiCombat) --- apps/openmw/mwmechanics/aicombat.cpp | 68 ++++------------------ apps/openmw/mwmechanics/aicombataction.cpp | 62 +++++++++++++++----- apps/openmw/mwmechanics/aicombataction.hpp | 13 +++-- 3 files changed, 64 insertions(+), 79 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index abf4109ba..acdbef299 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -211,61 +211,13 @@ namespace MWMechanics actionCooldown = currentAction->getActionCooldown(); } - float rangeFollow; - 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 - MWBase::World* world = MWBase::Environment::get().getWorld(); - if (actorClass.hasInventoryStore(actor)) + bool isRangedCombat = false; + if (currentAction.get()) { - //Get weapon range - MWWorld::ContainerStoreIterator weaponSlot = - MWMechanics::getActiveWeapon(actorClass.getCreatureStats(actor), actorClass.getInventoryStore(actor), &weaptype); - - if (weaptype == WeapType_HandToHand) - { - static float fHandToHandReach = - world->getStore().get().find("fHandToHandReach")->getFloat(); - weapRange = fHandToHandReach; - } - else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell && weaptype != WeapType_None) - { - // All other WeapTypes are actually weapons, so get is safe. - weapon = weaponSlot->get()->mBase; - weapRange = weapon->mData.mReach; - } - weapRange *= 100.0f; - } - else //is creature - { - weaptype = actorClass.getCreatureStats(actor).getDrawState() == DrawState_Spell ? WeapType_Spell : WeapType_HandToHand; - weapRange = 200; //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; - distantCombat = true; - } - else - { - rangeAttack = weapRange; - } - } - else - { - distantCombat = (rangeAttack > 500); - weapRange = 200; + rangeAttack = currentAction->getCombatRange(isRangedCombat); + // Get weapon characteristics + weapon = currentAction->getWeapon(); } ESM::Position pos = actor.getRefData().getPosition(); @@ -286,15 +238,15 @@ namespace MWMechanics if (storage.mReadyToAttack) { - storage.startCombatMove(actorClass.isNpc(), distantCombat, distToTarget, rangeAttack); + storage.startCombatMove(actorClass.isNpc(), isRangedCombat, distToTarget, rangeAttack); // start new attack - storage.startAttackIfReady(actor, characterController, weapon, distantCombat); + storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); - if (distantCombat) + if (isRangedCombat) { // rotate actor taking into account target movement direction and projectile speed osg::Vec3f& lastTargetPos = storage.mLastTargetPos; - vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, weaptype, storage.mStrength); + vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); lastTargetPos = vTargetPos; MWMechanics::Movement& movement = storage.mMovement; @@ -302,7 +254,7 @@ namespace MWMechanics movement.mRotation[2] = getZAngleToDir(vAimDir); } - storage.mAdjustAiming = distantCombat; + storage.mAdjustAiming = isRangedCombat; } } diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 60446e524..76d5ed952 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -40,23 +40,20 @@ int getRangeTypes (const ESM::EffectList& effects) return types; } -void suggestCombatRange(int rangeTypes, float& rangeAttack, float& rangeFollow) +float suggestCombatRange(int rangeTypes) { if (rangeTypes & Touch) { - rangeAttack = 100.f; - rangeFollow = 300.f; + return 100.f; } else if (rangeTypes & Target) { - rangeAttack = 1000.f; - rangeFollow = 0.f; + return 1000.f; } else { // For Self spells, distance doesn't matter, so back away slightly to avoid enemy hits - rangeAttack = 600.f; - rangeFollow = 0.f; + return 600.f; } } @@ -394,11 +391,13 @@ namespace MWMechanics } } - void ActionSpell::getCombatRange(float& rangeAttack, float& rangeFollow) + float ActionSpell::getCombatRange (bool& isRanged) const { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); int types = getRangeTypes(spell->mEffects); - suggestCombatRange(types, rangeAttack, rangeFollow); + + isRanged = (types & RangeTypes::Target); + return suggestCombatRange(types); } void ActionEnchantedItem::prepare(const MWWorld::Ptr &actor) @@ -408,18 +407,17 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Spell); } - void ActionEnchantedItem::getCombatRange(float& rangeAttack, float& rangeFollow) + float ActionEnchantedItem::getCombatRange(bool& isRanged) const { const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(mItem->getClass().getEnchantment(*mItem)); int types = getRangeTypes(enchantment->mEffects); - suggestCombatRange(types, rangeAttack, rangeFollow); + return suggestCombatRange(types); } - void ActionPotion::getCombatRange(float& rangeAttack, float& rangeFollow) + float ActionPotion::getCombatRange(bool& isRanged) const { // distance doesn't matter, so back away slightly to avoid enemy hits - rangeAttack = 600.f; - rangeFollow = 0.f; + return 600.f; } void ActionPotion::prepare(const MWWorld::Ptr &actor) @@ -430,6 +428,8 @@ namespace MWMechanics void ActionWeapon::prepare(const MWWorld::Ptr &actor) { + mIsNpc = actor.getClass().isNpc(); + if (actor.getClass().hasInventoryStore(actor)) { if (mWeapon.isEmpty()) @@ -449,9 +449,39 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Weapon); } - void ActionWeapon::getCombatRange(float& rangeAttack, float& rangeFollow) + float ActionWeapon::getCombatRange(bool& isRanged) const { - // Already done in AiCombat itself + isRanged = false; + + if (mWeapon.isEmpty()) + { + if (!mIsNpc) + return 200.f; // TODO: use true attack range (the same problem in Creature::hit) + else + { + static float fHandToHandReach = + MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->getFloat(); + + return fHandToHandReach * 100.0f; + } + } + + const ESM::Weapon* weapon = mWeapon.get()->mBase; + + if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) + { + isRanged = true; + return 1000.f; + } + else + return weapon->mData.mReach * 100.0f; + } + + const ESM::Weapon* ActionWeapon::getWeapon() const + { + if (mWeapon.isEmpty()) + return NULL; + return mWeapon.get()->mBase; } boost::shared_ptr prepareNextAction(const MWWorld::Ptr &actor, const MWWorld::Ptr &target) diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index a4a398d05..22f543418 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -16,8 +16,9 @@ namespace MWMechanics public: virtual ~Action() {} virtual void prepare(const MWWorld::Ptr& actor) = 0; - virtual void getCombatRange (float& rangeAttack, float& rangeFollow) = 0; + virtual float getCombatRange (bool& isRanged) const = 0; virtual float getActionCooldown() { return 0.f; } + virtual const ESM::Weapon* getWeapon() const { return NULL; }; }; class ActionSpell : public Action @@ -28,7 +29,7 @@ namespace MWMechanics /// Sets the given spell as selected on the actor's spell list. virtual void prepare(const MWWorld::Ptr& actor); - virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + virtual float getCombatRange (bool& isRanged) const; }; class ActionEnchantedItem : public Action @@ -38,7 +39,7 @@ namespace MWMechanics MWWorld::ContainerStoreIterator mItem; /// Sets the given item as selected enchanted item in the actor's InventoryStore. virtual void prepare(const MWWorld::Ptr& actor); - virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + virtual float getCombatRange (bool& isRanged) const; /// Since this action has no animation, apply a small cool down for using it virtual float getActionCooldown() { return 1.f; } @@ -51,7 +52,7 @@ namespace MWMechanics MWWorld::Ptr mPotion; /// Drinks the given potion. virtual void prepare(const MWWorld::Ptr& actor); - virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + virtual float getCombatRange (bool& isRanged) const; /// Since this action has no animation, apply a small cool down for using it virtual float getActionCooldown() { return 1.f; } @@ -62,6 +63,7 @@ namespace MWMechanics private: MWWorld::Ptr mAmmunition; MWWorld::Ptr mWeapon; + bool mIsNpc; public: /// \a weapon may be empty for hand-to-hand combat @@ -69,7 +71,8 @@ namespace MWMechanics : mAmmunition(ammo), mWeapon(weapon) {} /// Equips the given weapon. virtual void prepare(const MWWorld::Ptr& actor); - virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + virtual float getCombatRange (bool& isRanged) const; + virtual const ESM::Weapon* getWeapon() const; }; float rateSpell (const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); From 39963a2c85720a55d2e21af73911cc6d09d675be Mon Sep 17 00:00:00 2001 From: Roman Proskuryakov Date: Sun, 12 Jun 2016 23:23:08 +0300 Subject: [PATCH 05/54] Removes PreprocessorUtils.cmake Replaces get_version_from_n_defines -> libfind_version_n_header --- cmake/FindFreetype.cmake | 19 +++----- cmake/FindTinyXML.cmake | 1 - cmake/PreprocessorUtils.cmake | 85 ----------------------------------- 3 files changed, 5 insertions(+), 100 deletions(-) delete mode 100644 cmake/PreprocessorUtils.cmake diff --git a/cmake/FindFreetype.cmake b/cmake/FindFreetype.cmake index c154628da..3d28613ae 100644 --- a/cmake/FindFreetype.cmake +++ b/cmake/FindFreetype.cmake @@ -48,7 +48,6 @@ # ====================================== include(LibFindMacros) -include(PreprocessorUtils) set(_REGULAR_INSTALL_PATHS /usr/X11R6 @@ -78,21 +77,13 @@ find_path(Freetype_OLD_INCLUDE_DIR PATH_SUFFIXES freetype2 NO_DEFAULT_PATH ) - -# get version from freetype.h -find_file(Freetype_HEADER - NAMES freetype.h - PATH_SUFFIXES freetype - PATHS ${Freetype_OLD_INCLUDE_DIR} +libfind_version_n_header(Freetype + NAMES freetype/freetype.h freetype.h + PATHS Freetype_OLD_INCLUDE_DIR + DEFINES FREETYPE_MAJOR FREETYPE_MINOR FREETYPE_PATCH ) -if (Freetype_HEADER) - get_version_from_n_defines(Freetype_VERSION - ${Freetype_HEADER} - FREETYPE_MAJOR FREETYPE_MINOR FREETYPE_PATCH - ) -endif() -set(Freetype_PROCESS_INCLUDES Freetype_INCLUDE_DIR Freetype_OLD_INCLUDE_DIR) +set(Freetype_PROCESS_INCLUDES Freetype_OLD_INCLUDE_DIR) libfind_process(Freetype) if (Freetype_INCLUDE_DIRS) diff --git a/cmake/FindTinyXML.cmake b/cmake/FindTinyXML.cmake index d79a21c4a..cfbacb881 100644 --- a/cmake/FindTinyXML.cmake +++ b/cmake/FindTinyXML.cmake @@ -7,7 +7,6 @@ # include(LibFindMacros) -include(PreprocessorUtils) libfind_pkg_detect(TinyXML tinyxml FIND_PATH tinyxml.h diff --git a/cmake/PreprocessorUtils.cmake b/cmake/PreprocessorUtils.cmake deleted file mode 100644 index 7fb135bd3..000000000 --- a/cmake/PreprocessorUtils.cmake +++ /dev/null @@ -1,85 +0,0 @@ -#------------------------------------------------------------------- -# This file is part of the CMake build system for OGRE -# (Object-oriented Graphics Rendering Engine) -# For the latest info, see http://www.ogre3d.org/ -# -# The contents of this file are placed in the public domain. Feel -# free to make use of it in any way you like. -#------------------------------------------------------------------- - -macro(get_preprocessor_entry CONTENTS KEYWORD VARIABLE) - string(REGEX MATCH - "# *define +${KEYWORD} +((\"([^\n]*)\")|([^ \n]*))" - PREPROC_TEMP_VAR - ${${CONTENTS}} - ) - if (CMAKE_MATCH_3) - set(${VARIABLE} ${CMAKE_MATCH_3}) - else () - set(${VARIABLE} ${CMAKE_MATCH_4}) - endif () -endmacro() - -macro(has_preprocessor_entry CONTENTS KEYWORD VARIABLE) - string(REGEX MATCH - "\n *# *define +(${KEYWORD})" - PREPROC_TEMP_VAR - ${${CONTENTS}} - ) - if (CMAKE_MATCH_1) - set(${VARIABLE} TRUE) - else () - set(${VARIABLE} FALSE) - endif () -endmacro() - -macro(replace_preprocessor_entry VARIABLE KEYWORD NEW_VALUE) - string(REGEX REPLACE - "(// *)?# *define +${KEYWORD} +[^ \n]*" - "#define ${KEYWORD} ${NEW_VALUE}" - ${VARIABLE}_TEMP - ${${VARIABLE}} - ) - set(${VARIABLE} ${${VARIABLE}_TEMP}) -endmacro() - -macro(set_preprocessor_entry VARIABLE KEYWORD ENABLE) - if (${ENABLE}) - set(TMP_REPLACE_STR "#define ${KEYWORD}") - else () - set(TMP_REPLACE_STR "// #define ${KEYWORD}") - endif () - string(REGEX REPLACE - "(// *)?# *define +${KEYWORD} *\n" - ${TMP_REPLACE_STR} - ${VARIABLE}_TEMP - ${${VARIABLE}} - ) - set(${VARIABLE} ${${VARIABLE}_TEMP}) -endmacro() - - -# get_version_from_n_defines(result_version_name header_path [list of defines...]) -# -# get_version_from_n_defines(MyPackage_VERSION /Header/Path/HeaderName.h -# MYPACKAGE_VERSION_MAJOR -# MYPACKAGE_VERSION_MINOR -# ) -# Function call will get the values of defines MYPACKAGE_VERSION_MAJOR & MYPACKAGE_VERSION_MINOR -# from header and set "${MYPACKAGE_VERSION_MAJOR}.${MYPACKAGE_VERSION_MINOR}" into MyPackage_VERSION -# - -function(get_version_from_n_defines OUT_VAR HEADER_PATH) - if (NOT EXISTS ${HEADER_PATH}) - message(FATAL_ERROR "Unable to find '${HEADER_PATH}'") - return() - endif () - file(READ ${HEADER_PATH} _CONTENT) - unset(_DEFINES_LIST) - foreach (_DEFINE_NAME ${ARGN}) - get_preprocessor_entry(_CONTENT ${_DEFINE_NAME} _DEFINE_VALUE) - list(APPEND _DEFINES_LIST ${_DEFINE_VALUE}) - endforeach() - string(REPLACE ";" "." _VERSION "${_DEFINES_LIST}") - set(${OUT_VAR} "${_VERSION}" PARENT_SCOPE) -endfunction() From 0793e4a80e03330c3b989f11c009e34fdf8afe37 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Tue, 5 Jul 2016 21:38:41 +0300 Subject: [PATCH 06/54] refactor pathfinding code in AiWander: use AiPackage::pathTo, reuse AiPackage::ObstacleCheck --- apps/openmw/mwmechanics/aipackage.cpp | 9 +++-- apps/openmw/mwmechanics/aiwander.cpp | 47 ++++++++++----------------- apps/openmw/mwmechanics/aiwander.hpp | 2 +- apps/openmw/mwmechanics/obstacle.cpp | 5 +++ apps/openmw/mwmechanics/obstacle.hpp | 1 + 5 files changed, 31 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 4be464018..f2d75001a 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -68,7 +68,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr { bool wasShortcutting = mIsShortcutting; bool destInLOS = false; - mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS); // try to shortcut first + if (getTypeId() != TypeIdWander) // prohibit shortcuts for AiWander + mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS); // try to shortcut first if (!mIsShortcutting) { @@ -139,8 +140,10 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur MWWorld::Ptr door = getNearbyDoor(actor); // NOTE: checks interior cells only if (door != MWWorld::Ptr()) { - if (!door.getCellRef().getTeleport() && door.getCellRef().getTrap().empty() - && door.getCellRef().getLockLevel() <= 0 && door.getClass().getDoorState(door) == 0) { + // 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); } } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 16c8d3156..d7dcad2dd 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -70,8 +70,6 @@ namespace MWMechanics unsigned short mIdleAnimation; std::vector mBadIdles; // Idle animations that when called cause errors - PathFinder mPathFinder; - AiWanderStorage(): mTargetAngleRadians(0), mTurnActorGivingGreetingToFacePlayer(false), @@ -87,7 +85,7 @@ namespace MWMechanics AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) - , mStoredInitialActorPosition(false) + , mStoredInitialActorPosition(false), mIsWanderDestReady(false) { mIdle.resize(8, 0); init(); @@ -252,7 +250,7 @@ namespace MWMechanics if ((wanderState == Wander_MoveNow) && mDistance) { // Construct a new path if there isn't one - if(!storage.mPathFinder.isPathConstructed()) + if(!mPathFinder.isPathConstructed()) { if (mAllowedNodes.size()) { @@ -290,7 +288,7 @@ namespace MWMechanics void AiWander::returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos) { - if (!storage.mPathFinder.isPathConstructed()) + if (!mPathFinder.isPathConstructed()) { ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition)); @@ -298,10 +296,11 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); // don't take shortcuts for wandering - storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + mPathFinder.buildSyncedPath(start, dest, actor.getCell()); - if (storage.mPathFinder.isPathConstructed()) + if (mPathFinder.isPathConstructed()) { + mIsWanderDestReady = true; storage.mState = Wander_Walking; } } @@ -371,7 +370,7 @@ namespace MWMechanics float duration, AiWanderStorage& storage, ESM::Position& pos) { // Are we there yet? - if (storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) + if (mIsWanderDestReady && pathTo(actor, mPathFinder.getPath().back(), duration, DESTINATION_TOLERANCE)) { stopWalking(actor, storage); storage.mState = Wander_ChooseAction; @@ -414,34 +413,21 @@ namespace MWMechanics void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos) { - // turn towards the next point in mPath - zTurn(actor, storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); - - MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); - if (mObstacleCheck.check(actor, duration)) + if (mObstacleCheck.isEvading()) { // first check if we're walking into a door if (proximityToDoor(actor)) // NOTE: checks interior cells only { // remove allowed points then select another random destination mTrimCurrentNode = true; - trimAllowedNodes(mAllowedNodes, storage.mPathFinder); + trimAllowedNodes(mAllowedNodes, mPathFinder); mObstacleCheck.clear(); - storage.mPathFinder.clearPath(); + mPathFinder.clearPath(); storage.mState = Wander_MoveNow; } - else // probably walking into another NPC - { - // TODO: diagonal should have same animation as walk forward - // but doesn't seem to do that? - mObstacleCheck.takeEvasiveAction(movement); - } + mStuckCount++; // TODO: maybe no longer needed } - else - { - movement.mPosition[1] = 1; - } // if stuck for sufficiently long, act like current location was the destination if (mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset @@ -564,11 +550,12 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos)); // don't take shortcuts for wandering - storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell()); - storage.mPathFinder.buildPath(start, dest, actor.getCell()); + mPathFinder.buildSyncedPath(start, dest, actor.getCell()); - if (storage.mPathFinder.isPathConstructed()) + if (mPathFinder.isPathConstructed()) { + mIsWanderDestReady = true; + // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; mAllowedNodes.erase(mAllowedNodes.begin() + randNode); @@ -624,7 +611,8 @@ namespace MWMechanics void AiWander::stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage) { - storage.mPathFinder.clearPath(); + mPathFinder.clearPath(); + mIsWanderDestReady = false; actor.getClass().getMovementSettings(actor).mPosition[1] = 0; } @@ -850,6 +838,7 @@ namespace MWMechanics , mRepeat(wander->mData.mShouldRepeat != 0) , mStoredInitialActorPosition(wander->mStoredInitialActorPosition) , mStartTime(MWWorld::TimeStamp(wander->mStartTime)) + , mIsWanderDestReady(false) { if (mStoredInitialActorPosition) mInitialActorPosition = wander->mInitialActorPosition; diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index b0fabfce3..eacadbcfd 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -130,9 +130,9 @@ namespace MWMechanics const PathFinder& pathfinder); -// ObstacleCheck mObstacleCheck; float mDoorCheckDuration; int mStuckCount; + bool mIsWanderDestReady; // constants for converting idleSelect values into groupNames enum GroupIndex diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 5815d8cbe..f0ffc92e1 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -93,6 +93,11 @@ namespace MWMechanics return mWalkState == State_Norm; } + bool ObstacleCheck::isEvading() const + { + return mWalkState == State_Evade; + } + /* * input - actor, duration (time since last check) * output - true if evasive action needs to be taken diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index 98cc4e7a0..37ccf72b2 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -33,6 +33,7 @@ namespace MWMechanics void clear(); bool isNormalState() const; + bool isEvading() const; // Returns true if there is an obstacle and an evasive action // should be taken From 05794505c82ebca95226012a6b7beb91844ea2f6 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sun, 10 Jul 2016 17:25:50 +0300 Subject: [PATCH 07/54] travis build fix --- apps/openmw/mwmechanics/aicombataction.cpp | 2 +- apps/openmw/mwmechanics/pathfinding.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 8953c8a2a..ae28261ba 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -429,7 +429,7 @@ namespace MWMechanics const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); int types = getRangeTypes(spell->mEffects); - isRanged = (types & RangeTypes::Target); + isRanged = (types & Target); return suggestCombatRange(types); } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index a3e4da76f..2a452907d 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -111,7 +111,7 @@ namespace MWMechanics osg::Vec3f dir = to - from; dir.z() = 0; dir.normalize(); - float verticalOffset = 200; // instead of '200' here we want the height of the actor + float verticalOffset = 200; // instead of '200' here we want the height of the actor osg::Vec3f _from = from + dir*offsetXY + osg::Z_AXIS * verticalOffset; // cast up-down ray and find height in world space of hit From 8d4f0660ce801b159d4b920c34634ced49b98e6e Mon Sep 17 00:00:00 2001 From: mrcheko Date: Tue, 12 Jul 2016 00:17:43 +0300 Subject: [PATCH 08/54] fix hardcoded melee attack ranges --- apps/openmw/mwmechanics/aicombat.cpp | 11 +++++++---- apps/openmw/mwmechanics/aicombataction.cpp | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index e00a71f24..130c146b6 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -54,11 +54,11 @@ namespace MWMechanics AiCombatStorage(): mAttackCooldown(0), - mTimerReact(0), + mTimerReact(AI_REACTION_TIME), mTimerCombatMove(0), mReadyToAttack(false), mAttack(false), - mAttackRange(200), // default attack range (same as in Creature::Hit) + mAttackRange(0), mCombatMove(false), mLastTargetPos(0,0,0), mCell(NULL), @@ -159,8 +159,11 @@ namespace MWMechanics || target.getClass().getCreatureStats(target).isDead()) return true; - //Update every frame - storage.mReadyToAttack = pathTo(actor, target.getRefData().getPosition().pos, duration, storage.mAttackRange); + if (storage.mCurrentAction.get()) // need to wait to init action with it's attack range + { + //Update every frame + storage.mReadyToAttack = pathTo(actor, target.getRefData().getPosition().pos, duration, storage.mAttackRange); + } storage.updateCombatMove(duration); if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage); diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index ae28261ba..a70410035 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -44,7 +44,8 @@ float suggestCombatRange(int rangeTypes) { if (rangeTypes & Touch) { - return 100.f; + static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->getFloat(); + return fCombatDistance; } else if (rangeTypes & Target) { From 59a1a6d117da291c7db953fab14963f63486fe44 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sat, 16 Jul 2016 19:07:48 +0300 Subject: [PATCH 09/54] checkWayIsClear: remove PATHFIND_CAUTION_DIST check --- apps/openmw/mwmechanics/pathfinding.cpp | 22 ++++++++-------------- apps/openmw/mwmechanics/pathfinding.hpp | 2 -- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 2a452907d..ef60a85a4 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -106,22 +106,16 @@ namespace MWMechanics bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY) { - if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z() - to.z()) <= PATHFIND_Z_REACH) - { - osg::Vec3f dir = to - from; - dir.z() = 0; - dir.normalize(); - float verticalOffset = 200; // instead of '200' here we want the height of the actor - osg::Vec3f _from = from + dir*offsetXY + osg::Z_AXIS * verticalOffset; + osg::Vec3f dir = to - from; + dir.z() = 0; + dir.normalize(); + float verticalOffset = 200; // instead of '200' here we want the height of the actor + osg::Vec3f _from = from + dir*offsetXY + osg::Z_AXIS * verticalOffset; - // cast up-down ray and find height in world space of hit - float h = _from.z() - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -osg::Z_AXIS, verticalOffset + PATHFIND_Z_REACH + 1); + // cast up-down ray and find height of hit in world space + float h = _from.z() - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -osg::Z_AXIS, verticalOffset + PATHFIND_Z_REACH + 1); - if(std::abs(from.z() - h) <= PATHFIND_Z_REACH) - return true; - } - - return false; + return (std::abs(from.z() - h) <= PATHFIND_Z_REACH); } PathFinder::PathFinder() diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 05f846991..64608979b 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -23,8 +23,6 @@ namespace MWMechanics const float PATHFIND_Z_REACH = 50.0f; //static const float sMaxSlope = 49.0f; // duplicate as in physicssystem - // distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid - const float PATHFIND_CAUTION_DIST = 500.0f; // distance after which actor (failed previously to shortcut) will try again const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; From 203804ff15c0692125924972a592c1eb998b6c9b Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sat, 16 Jul 2016 19:08:49 +0300 Subject: [PATCH 10/54] fix AiPackage repeating --- apps/openmw/mwmechanics/aipackage.cpp | 12 ++++++++++++ apps/openmw/mwmechanics/aipackage.hpp | 3 +++ apps/openmw/mwmechanics/aisequence.cpp | 1 + 3 files changed, 16 insertions(+) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 90fe8c21b..1131b5e6f 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -58,6 +58,18 @@ bool MWMechanics::AiPackage::getRepeat() const return false; } +void MWMechanics::AiPackage::reset() +{ + // reset all members + mTimer = AI_REACTION_TIME + 1.0f; + mIsShortcutting = false; + mShortcutProhibited = false; + mShortcutFailPos = ESM::Pathgrid::Point(); + + mPathFinder.clearPath(); + mObstacleCheck.clear(); +} + bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance) { mTimer += duration; //Update timer diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index d30ed89ca..4feb13fe0 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -92,6 +92,9 @@ namespace MWMechanics /// Return true if this package should repeat. Currently only used for Wander packages. virtual bool getRepeat() const; + /// Reset pathfinding state + void reset(); + bool isTargetMagicallyHidden(const MWWorld::Ptr& target); protected: diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index f05725cc2..b03586c3b 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -234,6 +234,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac // Put repeating noncombat AI packages on the end of the stack so they can be used again if (isActualAiPackage(packageTypeId) && (mRepeat || package->getRepeat())) { + package->reset(); mPackages.push_back(package->clone()); } // To account for the rare case where AiPackage::execute() queued another AI package From 293a0f768c38d2ff816d29236b253a95c7e43f01 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Wed, 27 Jul 2016 23:43:32 +0300 Subject: [PATCH 11/54] use real distance to target to determinate 'ready to attack' state --- apps/openmw/mwmechanics/aicombat.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 130c146b6..9cba7f2b9 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -162,7 +162,8 @@ namespace MWMechanics if (storage.mCurrentAction.get()) // need to wait to init action with it's attack range { //Update every frame - storage.mReadyToAttack = pathTo(actor, target.getRefData().getPosition().pos, duration, storage.mAttackRange); + bool is_target_reached = pathTo(actor, target.getRefData().getPosition().pos, duration, storage.mAttackRange); + if (is_target_reached) storage.mReadyToAttack = true; } storage.updateCombatMove(duration); @@ -229,9 +230,11 @@ namespace MWMechanics osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); + + storage.mReadyToAttack = (distToTarget <= rangeAttack); // can't fight if attacker can't go where target is. E.g. A fish can't attack person on land. - if (distToTarget >= rangeAttack + if (distToTarget > rangeAttack && !actorClass.isNpc() && !MWMechanics::isEnvironmentCompatible(actor, target)) { // TODO: start fleeing? From e2cd87fcc761bb2b7194eb3e27a2a8948b16c8fe Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sun, 7 Aug 2016 19:23:08 +0300 Subject: [PATCH 12/54] fix veritcal aiming --- apps/openmw/mwmechanics/aicombat.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 9cba7f2b9..f5c64d4ab 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -50,7 +50,6 @@ namespace MWMechanics bool mForceNoShortcut; ESM::Position mShortcutFailPos; MWMechanics::Movement mMovement; - bool mAdjustAiming; AiCombatStorage(): mAttackCooldown(0), @@ -67,8 +66,7 @@ namespace MWMechanics mStrength(), mForceNoShortcut(false), mShortcutFailPos(), - mMovement(), - mAdjustAiming(false) + mMovement() {} void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack); @@ -255,12 +253,14 @@ namespace MWMechanics vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); lastTargetPos = vTargetPos; - MWMechanics::Movement& movement = storage.mMovement; - movement.mRotation[0] = getXAngleToDir(vAimDir); - movement.mRotation[2] = getZAngleToDir(vAimDir); + storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); + storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir); + } + else + { + storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); + storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated } - - storage.mAdjustAiming = isRangedCombat; } } @@ -272,11 +272,8 @@ namespace MWMechanics actorMovementSettings.mPosition[1] = storage.mMovement.mPosition[1]; actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2]; - if (storage.mAdjustAiming) - { - rotateActorOnAxis(actor, 2, actorMovementSettings, storage.mMovement); - rotateActorOnAxis(actor, 0, actorMovementSettings, storage.mMovement); - } + rotateActorOnAxis(actor, 2, actorMovementSettings, storage.mMovement); + rotateActorOnAxis(actor, 0, actorMovementSettings, storage.mMovement); } void AiCombat::rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, From 0963adb2f4a25c90fa4d601adba5618f8035d3c0 Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Mon, 8 Aug 2016 14:36:50 -0400 Subject: [PATCH 13/54] Fix pointer being deleted twice. --- apps/opencs/view/render/orbitcameramode.cpp | 4 ++-- apps/opencs/view/render/orbitcameramode.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/render/orbitcameramode.cpp b/apps/opencs/view/render/orbitcameramode.cpp index c7d980454..ba25beaba 100644 --- a/apps/opencs/view/render/orbitcameramode.cpp +++ b/apps/opencs/view/render/orbitcameramode.cpp @@ -15,9 +15,9 @@ namespace CSVRender , mWorldspaceWidget(worldspaceWidget) , mCenterOnSelection(0) { - mCenterShortcut.reset(new CSMPrefs::Shortcut("orbit-center-selection", worldspaceWidget)); + mCenterShortcut = new CSMPrefs::Shortcut("orbit-center-selection", worldspaceWidget); mCenterShortcut->enable(false); - connect(mCenterShortcut.get(), SIGNAL(activated()), this, SLOT(centerSelection())); + connect(mCenterShortcut, SIGNAL(activated()), this, SLOT(centerSelection())); } OrbitCameraMode::~OrbitCameraMode() diff --git a/apps/opencs/view/render/orbitcameramode.hpp b/apps/opencs/view/render/orbitcameramode.hpp index 4f72de957..312cd1756 100644 --- a/apps/opencs/view/render/orbitcameramode.hpp +++ b/apps/opencs/view/render/orbitcameramode.hpp @@ -32,7 +32,7 @@ namespace CSVRender WorldspaceWidget* mWorldspaceWidget; QAction* mCenterOnSelection; - std::auto_ptr mCenterShortcut; + CSMPrefs::Shortcut* mCenterShortcut; private slots: From f291aabc4bff12d8eafc3428809719924442b368 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Aug 2016 01:02:57 +0200 Subject: [PATCH 14/54] Fix shader compile error for objects without diffuse map --- files/shaders/objects_fragment.glsl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index b3a0150af..5a72b44b3 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -59,7 +59,9 @@ varying vec3 passNormal; void main() { +#if @diffuseMap vec2 adjustedDiffuseUV = diffuseMapUV; +#endif #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); From 0bbd715f65b82022d13a1d621322cab60c028a11 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Aug 2016 01:12:54 +0200 Subject: [PATCH 15/54] Fix the cloned StateSet not being assigned --- apps/openmw/mwrender/animation.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 0412e1e41..3f09dffc5 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1138,7 +1138,10 @@ namespace MWRender if (!node->getStateSet()) writableStateSet = node->getOrCreateStateSet(); else + { writableStateSet = osg::clone(node->getStateSet(), osg::CopyOp::SHALLOW_COPY); + node->setStateSet(writableStateSet); + } writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); From 448c9d5a5bf598b938b6720df1db2fbaa3d360ec Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Tue, 9 Aug 2016 00:17:41 -0400 Subject: [PATCH 16/54] Fix globals not being initialized with a valid type. --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/world/globalcreator.cpp | 26 ++++++++++++++++++++++++ apps/opencs/view/world/globalcreator.hpp | 20 ++++++++++++++++++ apps/opencs/view/world/subviews.cpp | 5 ++++- 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 apps/opencs/view/world/globalcreator.cpp create mode 100644 apps/opencs/view/world/globalcreator.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 8a830f2cb..1f572c3f8 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -66,7 +66,7 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world - table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator + table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator globalcreator cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator diff --git a/apps/opencs/view/world/globalcreator.cpp b/apps/opencs/view/world/globalcreator.cpp new file mode 100644 index 000000000..c7b140e15 --- /dev/null +++ b/apps/opencs/view/world/globalcreator.cpp @@ -0,0 +1,26 @@ +#include "globalcreator.hpp" + +#include + +#include "../../model/world/data.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/columns.hpp" +#include "../../model/world/idtable.hpp" + +namespace CSVWorld +{ + void GlobalCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const + { + CSMWorld::IdTable* table = static_cast(getData().getTableModel(getCollectionId())); + + int index = table->findColumnIndex(CSMWorld::Columns::ColumnId_ValueType); + int type = (int)ESM::VT_Float; + + command.addValue(index, type); + } + + GlobalCreator::GlobalCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) + : GenericCreator (data, undoStack, id, true) + { + } +} diff --git a/apps/opencs/view/world/globalcreator.hpp b/apps/opencs/view/world/globalcreator.hpp new file mode 100644 index 000000000..bc6117f81 --- /dev/null +++ b/apps/opencs/view/world/globalcreator.hpp @@ -0,0 +1,20 @@ +#ifndef CSV_WORLD_GLOBALCREATOR_H +#define CSV_WORLD_GLOBALCREATOR_H + +#include "genericcreator.hpp" + +namespace CSVWorld +{ + class GlobalCreator : public GenericCreator + { + public: + + GlobalCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); + + protected: + + virtual void configureCreateCommand(CSMWorld::CreateCommand& command) const; + }; +} + +#endif diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 69c0c01cc..32661ed93 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -7,6 +7,7 @@ #include "scriptsubview.hpp" #include "regionmapsubview.hpp" #include "genericcreator.hpp" +#include "globalcreator.hpp" #include "cellcreator.hpp" #include "referenceablecreator.hpp" #include "referencecreator.hpp" @@ -32,7 +33,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) static const CSMWorld::UniversalId::Type sTableTypes[] = { - CSMWorld::UniversalId::Type_Globals, CSMWorld::UniversalId::Type_Classes, CSMWorld::UniversalId::Type_Factions, CSMWorld::UniversalId::Type_Races, @@ -78,6 +78,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Pathgrids, new CSVDoc::SubViewFactoryWithCreator >); + manager.add (CSMWorld::UniversalId::Type_Globals, + new CSVDoc::SubViewFactoryWithCreator >); + // Subviews for resources tables manager.add (CSMWorld::UniversalId::Type_Meshes, new CSVDoc::SubViewFactoryWithCreator); From a563a9d336af27639738e40405d54017aff7eda0 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 9 Aug 2016 10:21:37 +0200 Subject: [PATCH 17/54] added missing Q_OBJECT --- apps/opencs/view/world/globalcreator.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/opencs/view/world/globalcreator.hpp b/apps/opencs/view/world/globalcreator.hpp index bc6117f81..8c6cc628c 100644 --- a/apps/opencs/view/world/globalcreator.hpp +++ b/apps/opencs/view/world/globalcreator.hpp @@ -7,6 +7,8 @@ namespace CSVWorld { class GlobalCreator : public GenericCreator { + Q_OBJECT + public: GlobalCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); From dca7b4beb7b970310e87951721838341a47284a7 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sat, 16 Jul 2016 22:47:33 +0900 Subject: [PATCH 18/54] Make non-actors also play spell casting sounds --- apps/openmw/mwmechanics/character.cpp | 35 ++++------------------- apps/openmw/mwmechanics/character.hpp | 2 -- apps/openmw/mwmechanics/spellcasting.cpp | 36 ++++++++++++++++++++++++ apps/openmw/mwmechanics/spellcasting.hpp | 2 ++ 4 files changed, 43 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 07aba2f7d..e65195531 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -44,6 +44,7 @@ #include "creaturestats.hpp" #include "security.hpp" #include "actorutil.hpp" +#include "spellcasting.hpp" namespace { @@ -961,34 +962,6 @@ void CharacterController::updateIdleStormState(bool inwater) } } -void CharacterController::castSpell(const std::string &spellid) -{ - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Spell *spell = store.get().find(spellid); - const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); - - const ESM::MagicEffect *effect; - effect = store.get().find(effectentry.mEffectID); - - const ESM::Static* castStatic; - if (!effect->mCasting.empty()) - castStatic = store.get().find (effect->mCasting); - else - castStatic = store.get().find ("VFX_DefaultCast"); - - mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex); - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!effect->mCastSound.empty()) - sndMgr->playSound3D(mPtr, effect->mCastSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(mPtr, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); -} - bool CharacterController::updateCreatureState() { const MWWorld::Class &cls = mPtr.getClass(); @@ -1020,7 +993,8 @@ bool CharacterController::updateCreatureState() const std::string spellid = stats.getSpells().getSelectedSpell(); if (!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) { - castSpell(spellid); + MWMechanics::CastSpell cast(mPtr, NULL); + cast.playSpellCastingEffects(spellid); if (!mAnimation->hasAnimation("spellcast")) MWBase::Environment::get().getWorld()->castSpell(mPtr); // No "release" text key to use, so cast immediately @@ -1248,7 +1222,8 @@ bool CharacterController::updateWeaponState() if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) { - castSpell(spellid); + MWMechanics::CastSpell cast(mPtr, NULL); + cast.playSpellCastingEffects(spellid); const ESM::Spell *spell = store.get().find(spellid); const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 190e171b3..d5dc5fe28 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -214,8 +214,6 @@ class CharacterController : public MWRender::Animation::TextKeyListener void updateHeadTracking(float duration); - void castSpell(const std::string& spellid); - void updateMagicEffects(); void playDeath(float startpoint, CharacterState death); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 52815816e..e5f221a02 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -836,6 +836,10 @@ namespace MWMechanics if (mCaster == getPlayer() && spellIncreasesSkill(spell)) mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0); + + // A non-actor doesn't have casting animation so it plays its spell casting effects here + if (!mCaster.getClass().isActor()) + playSpellCastingEffects(mId); inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self); @@ -930,6 +934,38 @@ namespace MWMechanics return true; } + void CastSpell::playSpellCastingEffects(const std::string &spellid){ + + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::Spell *spell = store.get().find(spellid); + const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); + + const ESM::MagicEffect *effect; + effect = store.get().find(effectentry.mEffectID); + + if (mCaster.getClass().isActor()) // TODO: Non-actors (except for large statics?) should also create a visual casting effect + { + const ESM::Static* castStatic; + if (!effect->mCasting.empty()) + castStatic = store.get().find (effect->mCasting); + else + castStatic = store.get().find ("VFX_DefaultCast"); + + MWBase::Environment::get().getWorld()->getAnimation(mCaster)->addEffect( + "meshes\\" + castStatic->mModel, effect->mIndex); + } + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!effect->mCastSound.empty()) + sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); + } + int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor) { /* diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index ae20a39d0..aba263b01 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -92,6 +92,8 @@ namespace MWMechanics /// @note Auto detects if spell, ingredient or potion bool cast (const std::string& id); + void playSpellCastingEffects(const std::string &spellid); + /// @note \a target can be any type of object, not just actors. /// @note \a caster can be any type of object, or even an empty object. void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, From 3841a8fb40e4fb1c0fe8c76d966ffe6e1ed5e2f8 Mon Sep 17 00:00:00 2001 From: Allofich Date: Tue, 2 Aug 2016 21:37:39 +0900 Subject: [PATCH 19/54] Make non-actors glow when they cast spells --- apps/openmw/mwmechanics/character.cpp | 5 +- apps/openmw/mwmechanics/spellcasting.cpp | 15 +++++- apps/openmw/mwrender/animation.cpp | 68 ++++++++++++++++++++++-- apps/openmw/mwrender/animation.hpp | 15 +++++- 4 files changed, 93 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index e65195531..e7a211c60 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1531,10 +1531,11 @@ void CharacterController::update(float duration) osg::Vec3f movement(0.f, 0.f, 0.f); float speed = 0.f; - updateMagicEffects(); - if(!cls.isActor()) { + updateMagicEffects(); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mPtr); + animation->updateSpellGlow(duration); if(mAnimQueue.size() > 1) { if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index e5f221a02..d038fb946 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -943,6 +943,8 @@ namespace MWMechanics const ESM::MagicEffect *effect; effect = store.get().find(effectentry.mEffectID); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); + if (mCaster.getClass().isActor()) // TODO: Non-actors (except for large statics?) should also create a visual casting effect { const ESM::Static* castStatic; @@ -951,8 +953,17 @@ namespace MWMechanics else castStatic = store.get().find ("VFX_DefaultCast"); - MWBase::Environment::get().getWorld()->getAnimation(mCaster)->addEffect( - "meshes\\" + castStatic->mModel, effect->mIndex); + animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex); + } + + if (!mCaster.getClass().isActor()) + { + osg::Vec4f glowcolor(1,1,1,1); + glowcolor.x() = effect->mData.mRed / 255.f; + glowcolor.y() = effect->mData.mGreen / 255.f; + glowcolor.z() = effect->mData.mBlue / 255.f; + + animation->addSpellCastGlow(glowcolor); } static const std::string schools[] = { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 3f09dffc5..85c94e37d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -38,6 +38,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority @@ -89,10 +90,11 @@ namespace class GlowUpdater : public SceneUtil::StateSetUpdater { public: - GlowUpdater(int texUnit, osg::Vec4f color, const std::vector >& textures) + GlowUpdater(int texUnit, osg::Vec4f color, const std::vector >& textures, bool hasDuration) : mTexUnit(texUnit) , mColor(color) , mTextures(textures) + , mHasDuration(hasDuration) { } @@ -122,10 +124,26 @@ namespace stateset->setTextureAttribute(mTexUnit, mTextures[index], osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } + bool const getHasDuration() + { + return mHasDuration; + } + + std::vector > const getTextures() + { + return mTextures; + } + + int const getTexUnit() + { + return mTexUnit; + } + private: int mTexUnit; osg::Vec4f mColor; std::vector > mTextures; + bool mHasDuration; }; class NodeMapVisitor : public osg::NodeVisitor @@ -340,6 +358,7 @@ namespace MWRender , mHeadYawRadians(0.f) , mHeadPitchRadians(0.f) , mAlpha(1.f) + , mSpellGlowDuration(0.f) { for(size_t i = 0;i < sNumBlendMasks;i++) mAnimationTimePtr[i].reset(new AnimationTime); @@ -1106,7 +1125,44 @@ namespace MWRender int mLowestUnusedTexUnit; }; - void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor) + void Animation::addSpellCastGlow(osg::Vec4f glowColor){ + addGlow(mObjectRoot, glowColor, true); + MWBase::Environment::get().getMechanicsManager()->add(mPtr); + } + + void Animation::updateSpellGlow(float duration){ + if (mGlowUpdater != NULL && (mGlowUpdater->getHasDuration())) + mSpellGlowDuration += duration; + if (mSpellGlowDuration >= 1.5f) // length of spell glow effect was measured from original game as around 1.5 seconds + removeSpellGlow(); + } + + void Animation::removeSpellGlow() + { + osg::ref_ptr writableStateSet = NULL; + if (!mObjectRoot->getStateSet()) + writableStateSet = mObjectRoot->getOrCreateStateSet(); + else + writableStateSet = osg::clone(mObjectRoot->getStateSet(), osg::CopyOp::SHALLOW_COPY); + + std::vector > Textures = mGlowUpdater->getTextures(); + int TexUnit = mGlowUpdater->getTexUnit(); + mObjectRoot->removeUpdateCallback(mGlowUpdater); + mGlowUpdater = NULL; + + for (size_t index = 0; index < Textures.size(); index++) + { + writableStateSet->setTextureAttribute(TexUnit, Textures[index], osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + writableStateSet->removeTextureAttribute(TexUnit, Textures[index]); + } + + mObjectRoot->setStateSet(writableStateSet); + writableStateSet->removeUniform(mUniform); + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); + mSpellGlowDuration = 0.f; + } + + void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor, bool hasDuration) { std::vector > textures; for (int i=0; i<32; ++i) @@ -1130,8 +1186,9 @@ namespace MWRender FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; node->accept(findLowestUnusedTexUnitVisitor); int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; - osg::ref_ptr glowupdater (new GlowUpdater(texUnit, glowColor, textures)); - node->addUpdateCallback(glowupdater); + mGlowUpdater = new GlowUpdater(texUnit, glowColor, textures, hasDuration); + //osg::ref_ptr glowupdater (new GlowUpdater(texUnit, glowColor, textures)); + node->addUpdateCallback(mGlowUpdater); // set a texture now so that the ShaderVisitor can find it osg::ref_ptr writableStateSet = NULL; @@ -1143,7 +1200,8 @@ namespace MWRender node->setStateSet(writableStateSet); } writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); - writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); + mUniform = new osg::Uniform("envMapColor", glowColor); + writableStateSet->addUniform(mUniform); mResourceSystem->getSceneManager()->recreateShaders(node); } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index ad9d4ab4a..9ca9d9552 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -5,6 +5,11 @@ #include +namespace +{ + class GlowUpdater; +} + namespace ESM { struct Light; @@ -263,6 +268,10 @@ protected: osg::ref_ptr mGlowLight; float mAlpha; + float mSpellGlowDuration; + + osg::ref_ptr mGlowUpdater; + osg::Uniform* mUniform; const NodeMap& getNodeMap() const; @@ -317,7 +326,7 @@ protected: osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const; - void addGlow(osg::ref_ptr node, osg::Vec4f glowColor); + void addGlow(osg::ref_ptr node, osg::Vec4f glowColor, bool hasDuration = false); /// Set the render bin for this animation's object root. May be customized by subclasses. virtual void setRenderBin(); @@ -351,6 +360,10 @@ public: void removeEffect (int effectId); void getLoopingEffects (std::vector& out) const; + void addSpellCastGlow(osg::Vec4f glowColor); + void updateSpellGlow(float duration); + void removeSpellGlow(); + virtual void updatePtr(const MWWorld::Ptr &ptr); bool hasAnimation(const std::string &anim) const; From 57138b416e4816b3b982e081b5b653e8ac0eac47 Mon Sep 17 00:00:00 2001 From: Allofich Date: Tue, 2 Aug 2016 22:15:48 +0900 Subject: [PATCH 20/54] Fix spell glows to only run one at a time --- apps/openmw/mwrender/animation.cpp | 35 ++++++++++++++++++++---------- apps/openmw/mwrender/animation.hpp | 3 +-- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 85c94e37d..736ba8956 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1126,12 +1126,13 @@ namespace MWRender }; void Animation::addSpellCastGlow(osg::Vec4f glowColor){ - addGlow(mObjectRoot, glowColor, true); + if (mSpellGlowUpdater == NULL) // only start a new spell glow if there isn't one already + addGlow(mObjectRoot, glowColor, true); MWBase::Environment::get().getMechanicsManager()->add(mPtr); } void Animation::updateSpellGlow(float duration){ - if (mGlowUpdater != NULL && (mGlowUpdater->getHasDuration())) + if (mSpellGlowUpdater != NULL) mSpellGlowDuration += duration; if (mSpellGlowDuration >= 1.5f) // length of spell glow effect was measured from original game as around 1.5 seconds removeSpellGlow(); @@ -1145,10 +1146,10 @@ namespace MWRender else writableStateSet = osg::clone(mObjectRoot->getStateSet(), osg::CopyOp::SHALLOW_COPY); - std::vector > Textures = mGlowUpdater->getTextures(); - int TexUnit = mGlowUpdater->getTexUnit(); - mObjectRoot->removeUpdateCallback(mGlowUpdater); - mGlowUpdater = NULL; + std::vector > Textures = mSpellGlowUpdater->getTextures(); + int TexUnit = mSpellGlowUpdater->getTexUnit(); + mObjectRoot->removeUpdateCallback(mSpellGlowUpdater); + mSpellGlowUpdater = NULL; for (size_t index = 0; index < Textures.size(); index++) { @@ -1157,7 +1158,10 @@ namespace MWRender } mObjectRoot->setStateSet(writableStateSet); - writableStateSet->removeUniform(mUniform); + if (writableStateSet->getUniform("envMapColor2")) // if we added a second uniform, remove that one instead of the original + writableStateSet->removeUniform("envMapColor2"); + else + writableStateSet->removeUniform("envMapColor"); mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); mSpellGlowDuration = 0.f; } @@ -1186,9 +1190,13 @@ namespace MWRender FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; node->accept(findLowestUnusedTexUnitVisitor); int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; - mGlowUpdater = new GlowUpdater(texUnit, glowColor, textures, hasDuration); - //osg::ref_ptr glowupdater (new GlowUpdater(texUnit, glowColor, textures)); - node->addUpdateCallback(mGlowUpdater); + + osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, hasDuration); + + if (hasDuration) // store the glowUpdater for later removal and checking if this is a spell glow + mSpellGlowUpdater = glowUpdater; + + node->addUpdateCallback(glowUpdater); // set a texture now so that the ShaderVisitor can find it osg::ref_ptr writableStateSet = NULL; @@ -1200,8 +1208,11 @@ namespace MWRender node->setStateSet(writableStateSet); } writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); - mUniform = new osg::Uniform("envMapColor", glowColor); - writableStateSet->addUniform(mUniform); + + if (hasDuration && writableStateSet->getUniform("envMapColor")) // if we are adding a spell glow to something with an enchantment glow + writableStateSet->addUniform(new osg::Uniform("envMapColor2", glowColor)); + else + writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); mResourceSystem->getSceneManager()->recreateShaders(node); } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 9ca9d9552..30e2de601 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -270,8 +270,7 @@ protected: float mAlpha; float mSpellGlowDuration; - osg::ref_ptr mGlowUpdater; - osg::Uniform* mUniform; + osg::ref_ptr mSpellGlowUpdater; const NodeMap& getNodeMap() const; From 123c626f2d6bf32326e3a2f843ca904d9cdfc0d5 Mon Sep 17 00:00:00 2001 From: Allofich Date: Wed, 3 Aug 2016 02:45:42 +0900 Subject: [PATCH 21/54] Add glow when using telekinesis on doors --- apps/openmw/mwclass/door.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index a54b64897..effdcf547 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -25,6 +25,7 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "../mwrender/animation.hpp" #include "../mwmechanics/actorutil.hpp" @@ -112,6 +113,24 @@ namespace MWClass bool hasKey = false; std::string keyName; + if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr() && // assuming player is using telekinesis + MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > + MWBase::Environment::get().getWorld()->getMaxActivationDistance()) + { + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); + + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::MagicEffect *effect; + effect = store.get().find(59); + + osg::Vec4f glowcolor(1,1,1,1); + glowcolor.x() = effect->mData.mRed / 255.f; + glowcolor.y() = effect->mData.mGreen / 255.f; + glowcolor.z() = effect->mData.mBlue / 255.f; + + animation->addSpellCastGlow(glowcolor); // TODO: Telekinesis glow should only be as long as the door animation + } + // make key id lowercase std::string keyId = ptr.getCellRef().getKey(); Misc::StringUtils::lowerCaseInPlace(keyId); From e132b52a694e9b1e0053d2b1d012315ab63ba58f Mon Sep 17 00:00:00 2001 From: Allofich Date: Sun, 7 Aug 2016 03:08:46 +0900 Subject: [PATCH 22/54] Handle spell glows within updatecallback --- apps/openmw/mwclass/door.cpp | 3 +- apps/openmw/mwmechanics/character.cpp | 5 +- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- apps/openmw/mwrender/animation.cpp | 165 +++++++++++++++-------- apps/openmw/mwrender/animation.hpp | 12 +- 5 files changed, 113 insertions(+), 74 deletions(-) diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index effdcf547..01520a331 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -113,7 +113,8 @@ namespace MWClass bool hasKey = false; std::string keyName; - if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr() && // assuming player is using telekinesis + // make door glow if player activates it with telekinesis + if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr() && MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > MWBase::Environment::get().getWorld()->getMaxActivationDistance()) { diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index e7a211c60..e65195531 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1531,11 +1531,10 @@ void CharacterController::update(float duration) osg::Vec3f movement(0.f, 0.f, 0.f); float speed = 0.f; + updateMagicEffects(); + if(!cls.isActor()) { - updateMagicEffects(); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mPtr); - animation->updateSpellGlow(duration); if(mAnimQueue.size() > 1) { if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index d038fb946..bef720764 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -837,7 +837,7 @@ namespace MWMechanics mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0); - // A non-actor doesn't have casting animation so it plays its spell casting effects here + // A non-actor doesn't play its effects from a character controller, so play spell casting effects here if (!mCaster.getClass().isActor()) playSpellCastingEffects(mId); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 736ba8956..71b569285 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -38,7 +38,6 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority @@ -90,11 +89,18 @@ namespace class GlowUpdater : public SceneUtil::StateSetUpdater { public: - GlowUpdater(int texUnit, osg::Vec4f color, const std::vector >& textures, bool hasDuration) + GlowUpdater(int texUnit, osg::Vec4f color, const std::vector >& textures, + osg::ref_ptr node, float maxduration, Resource::ResourceSystem* resourcesystem, osg::Uniform* uniform) : mTexUnit(texUnit) , mColor(color) , mTextures(textures) - , mHasDuration(hasDuration) + , mNode(node) + , mMaxDuration(maxduration) + , mStartingTime(0) + , mResourceSystem(resourcesystem) + , mDone(false) + , mUniform(uniform) + , mWatchedSpellGlow(NULL) { } @@ -115,35 +121,99 @@ namespace texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_COLOR); stateset->setTextureAttributeAndModes(mTexUnit, texEnv, osg::StateAttribute::ON); + + // Reduce the texture list back down by one when a temporary glow finishes + // to allow FindLowestUnusedTexUnitVisitor to choose the same texunit again. + if (mDone) + stateset->getTextureAttributeList().resize(stateset->getTextureAttributeList().size() - 1); } virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) { + if (mDone) + return; + + // If there is a permanent enchantment glow already on this object, give the + // permanent glow a pointer to a temporary glow added in a nested update callback + // The permanent glow will remove the temporary glow when it finishes, allowing + // the permanent glow to display its glow texture cycling properly. + if (mWatchedSpellGlow != NULL && mWatchedSpellGlow->isDone()) + { + mNode->removeUpdateCallback(mWatchedSpellGlow); + mWatchedSpellGlow = NULL; + } + + // Set the starting time to measure glow duration from if this is a temporary glow + if ((mMaxDuration >= 0) && mStartingTime == 0) + mStartingTime = nv->getFrameStamp()->getSimulationTime(); + float time = nv->getFrameStamp()->getSimulationTime(); int index = (int)(time*16) % mTextures.size(); stateset->setTextureAttribute(mTexUnit, mTextures[index], osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + if ((mMaxDuration >= 0) && (time - mStartingTime > mMaxDuration)) // If this is a temporary glow and it has finished its duration + { + osg::ref_ptr writableStateSet = NULL; + if (!mNode->getStateSet()) + writableStateSet = mNode->getOrCreateStateSet(); + else + writableStateSet = osg::clone(mNode->getStateSet(), osg::CopyOp::SHALLOW_COPY); + + for (size_t index = 0; index < mTextures.size(); index++) + { + writableStateSet->removeTextureAttribute(mTexUnit, mTextures[index]); + } + + writableStateSet->removeUniform(mUniform); // remove the uniform given to the temporary glow in addglow() + mNode->setStateSet(writableStateSet); + + this->reset(); // without this a texture from the glow will continue to show on the object + mResourceSystem->getSceneManager()->recreateShaders(mNode); + mDone = true; + } } - bool const getHasDuration() + bool isSpellGlowUpdater() { - return mHasDuration; + return (mMaxDuration >= 0); } - std::vector > const getTextures() + bool isEnchantmentGlowUpdater() { - return mTextures; + return (mMaxDuration < 0); } - int const getTexUnit() + bool isDone() + { + return mDone; + } + + int getTexUnit() { return mTexUnit; } + void setWatchedSpellGlow(osg::ref_ptr watched) + { + mWatchedSpellGlow = watched; + } + + osg::ref_ptr getWatchedSpellGlow() + { + return mWatchedSpellGlow; + } + private: int mTexUnit; osg::Vec4f mColor; std::vector > mTextures; - bool mHasDuration; + osg::ref_ptr mNode; + float mMaxDuration; + float mStartingTime; + Resource::ResourceSystem* mResourceSystem; + bool mDone; + osg::Uniform* mUniform; + osg::ref_ptr mWatchedSpellGlow; }; class NodeMapVisitor : public osg::NodeVisitor @@ -358,7 +428,6 @@ namespace MWRender , mHeadYawRadians(0.f) , mHeadPitchRadians(0.f) , mAlpha(1.f) - , mSpellGlowDuration(0.f) { for(size_t i = 0;i < sNumBlendMasks;i++) mAnimationTimePtr[i].reset(new AnimationTime); @@ -1126,47 +1195,18 @@ namespace MWRender }; void Animation::addSpellCastGlow(osg::Vec4f glowColor){ - if (mSpellGlowUpdater == NULL) // only start a new spell glow if there isn't one already - addGlow(mObjectRoot, glowColor, true); - MWBase::Environment::get().getMechanicsManager()->add(mPtr); + if (!mObjectRoot->getUpdateCallback()) // If there is no glow on object + addGlow(mObjectRoot, glowColor, 1.5); // Glow length measured from in-game as about 1.5 seconds + + else if (dynamic_cast (mObjectRoot->getUpdateCallback())->isDone() == true) // If there was a temporary glow on object and it finished + addGlow(mObjectRoot, glowColor, 1.5); + + else if (dynamic_cast (mObjectRoot->getUpdateCallback())->isEnchantmentGlowUpdater() == true + && dynamic_cast (mObjectRoot->getUpdateCallback())->getWatchedSpellGlow() == NULL) // If there is a permanent enchantment glow on object and no temporary glow is running + addGlow(mObjectRoot, glowColor, 1.5); } - void Animation::updateSpellGlow(float duration){ - if (mSpellGlowUpdater != NULL) - mSpellGlowDuration += duration; - if (mSpellGlowDuration >= 1.5f) // length of spell glow effect was measured from original game as around 1.5 seconds - removeSpellGlow(); - } - - void Animation::removeSpellGlow() - { - osg::ref_ptr writableStateSet = NULL; - if (!mObjectRoot->getStateSet()) - writableStateSet = mObjectRoot->getOrCreateStateSet(); - else - writableStateSet = osg::clone(mObjectRoot->getStateSet(), osg::CopyOp::SHALLOW_COPY); - - std::vector > Textures = mSpellGlowUpdater->getTextures(); - int TexUnit = mSpellGlowUpdater->getTexUnit(); - mObjectRoot->removeUpdateCallback(mSpellGlowUpdater); - mSpellGlowUpdater = NULL; - - for (size_t index = 0; index < Textures.size(); index++) - { - writableStateSet->setTextureAttribute(TexUnit, Textures[index], osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); - writableStateSet->removeTextureAttribute(TexUnit, Textures[index]); - } - - mObjectRoot->setStateSet(writableStateSet); - if (writableStateSet->getUniform("envMapColor2")) // if we added a second uniform, remove that one instead of the original - writableStateSet->removeUniform("envMapColor2"); - else - writableStateSet->removeUniform("envMapColor"); - mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); - mSpellGlowDuration = 0.f; - } - - void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor, bool hasDuration) + void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor, float glowDuration) { std::vector > textures; for (int i=0; i<32; ++i) @@ -1187,16 +1227,29 @@ namespace MWRender textures.push_back(tex); } + int texUnit; + + // If we have a spell glow updater left over from this object prevously casting a spell, + // and there was no permanent glow updater on the object to watch it and remove it, we + // remove it here. + if (node->getUpdateCallback() && + dynamic_cast (node->getUpdateCallback())->isSpellGlowUpdater() == true) + node->removeUpdateCallback(node->getUpdateCallback()); + FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; node->accept(findLowestUnusedTexUnitVisitor); - int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; + texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; + + osg::Uniform* uniform = new osg::Uniform("envMapColor", glowColor); - osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, hasDuration); - if (hasDuration) // store the glowUpdater for later removal and checking if this is a spell glow - mSpellGlowUpdater = glowUpdater; + osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, mResourceSystem, uniform); node->addUpdateCallback(glowUpdater); + if (node->getUpdateCallback() && + dynamic_cast (node->getUpdateCallback())->isEnchantmentGlowUpdater() == true) + if (glowDuration >= 0) + dynamic_cast (node->getUpdateCallback())->setWatchedSpellGlow(glowUpdater); // set a texture now so that the ShaderVisitor can find it osg::ref_ptr writableStateSet = NULL; @@ -1208,11 +1261,7 @@ namespace MWRender node->setStateSet(writableStateSet); } writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); - - if (hasDuration && writableStateSet->getUniform("envMapColor")) // if we are adding a spell glow to something with an enchantment glow - writableStateSet->addUniform(new osg::Uniform("envMapColor2", glowColor)); - else - writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); + writableStateSet->addUniform(uniform); mResourceSystem->getSceneManager()->recreateShaders(node); } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 30e2de601..96bd0c03e 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -5,11 +5,6 @@ #include -namespace -{ - class GlowUpdater; -} - namespace ESM { struct Light; @@ -268,9 +263,6 @@ protected: osg::ref_ptr mGlowLight; float mAlpha; - float mSpellGlowDuration; - - osg::ref_ptr mSpellGlowUpdater; const NodeMap& getNodeMap() const; @@ -325,7 +317,7 @@ protected: osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const; - void addGlow(osg::ref_ptr node, osg::Vec4f glowColor, bool hasDuration = false); + void addGlow(osg::ref_ptr node, osg::Vec4f glowColor, float glowDuration = -1); /// Set the render bin for this animation's object root. May be customized by subclasses. virtual void setRenderBin(); @@ -360,8 +352,6 @@ public: void getLoopingEffects (std::vector& out) const; void addSpellCastGlow(osg::Vec4f glowColor); - void updateSpellGlow(float duration); - void removeSpellGlow(); virtual void updatePtr(const MWWorld::Ptr &ptr); From 35c14bb9bb2a1940fc708ad8e12275ca3fb94355 Mon Sep 17 00:00:00 2001 From: Allofich Date: Mon, 8 Aug 2016 21:55:56 +0900 Subject: [PATCH 23/54] Minor rewrite, make "open" spells play glow effect --- apps/openmw/mwclass/door.cpp | 10 +++------- apps/openmw/mwmechanics/spellcasting.cpp | 19 +++++++++---------- apps/openmw/mwrender/animation.cpp | 12 +++++++++--- apps/openmw/mwrender/animation.hpp | 3 ++- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 01520a331..255f38dd9 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -122,14 +122,10 @@ namespace MWClass const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const ESM::MagicEffect *effect; - effect = store.get().find(59); + int index = effect->effectStringToId("sEffectTelekinesis"); + effect = store.get().find(index); - osg::Vec4f glowcolor(1,1,1,1); - glowcolor.x() = effect->mData.mRed / 255.f; - glowcolor.y() = effect->mData.mGreen / 255.f; - glowcolor.z() = effect->mData.mBlue / 255.f; - - animation->addSpellCastGlow(glowcolor); // TODO: Telekinesis glow should only be as long as the door animation + animation->addSpellCastGlow(effect); // TODO: Telekinesis glow should only be as long as the door animation } // make key id lowercase diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index bef720764..4c6a4c02d 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -584,6 +584,12 @@ namespace MWMechanics } else if (effectId == ESM::MagicEffect::Open) { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::MagicEffect *magiceffect; + magiceffect = store.get().find(effectId); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); + animation->addSpellCastGlow(magiceffect); + if (target.getCellRef().getLockLevel() <= magnitude) { if (target.getCellRef().getLockLevel() > 0) @@ -837,7 +843,7 @@ namespace MWMechanics mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0); - // A non-actor doesn't play its effects from a character controller, so play spell casting effects here + // A non-actor doesn't play its spell cast effects from a character controller, so play them here if (!mCaster.getClass().isActor()) playSpellCastingEffects(mId); @@ -945,7 +951,7 @@ namespace MWMechanics MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); - if (mCaster.getClass().isActor()) // TODO: Non-actors (except for large statics?) should also create a visual casting effect + if (mCaster.getClass().isActor()) // TODO: Non-actors (except for large statics?) should also create a spell cast vfx { const ESM::Static* castStatic; if (!effect->mCasting.empty()) @@ -957,14 +963,7 @@ namespace MWMechanics } if (!mCaster.getClass().isActor()) - { - osg::Vec4f glowcolor(1,1,1,1); - glowcolor.x() = effect->mData.mRed / 255.f; - glowcolor.y() = effect->mData.mGreen / 255.f; - glowcolor.z() = effect->mData.mBlue / 255.f; - - animation->addSpellCastGlow(glowcolor); - } + animation->addSpellCastGlow(effect); static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 71b569285..0470cfe8c 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -159,9 +159,9 @@ namespace else writableStateSet = osg::clone(mNode->getStateSet(), osg::CopyOp::SHALLOW_COPY); - for (size_t index = 0; index < mTextures.size(); index++) + for (size_t i = 0; i < mTextures.size(); i++) { - writableStateSet->removeTextureAttribute(mTexUnit, mTextures[index]); + writableStateSet->removeTextureAttribute(mTexUnit, mTextures[i]); } writableStateSet->removeUniform(mUniform); // remove the uniform given to the temporary glow in addglow() @@ -1194,7 +1194,13 @@ namespace MWRender int mLowestUnusedTexUnit; }; - void Animation::addSpellCastGlow(osg::Vec4f glowColor){ + void Animation::addSpellCastGlow(const ESM::MagicEffect *effect){ + + osg::Vec4f glowColor = {1,1,1,1}; + glowColor.x() = effect->mData.mRed / 255.f; + glowColor.y() = effect->mData.mGreen / 255.f; + glowColor.z() = effect->mData.mBlue / 255.f; + if (!mObjectRoot->getUpdateCallback()) // If there is no glow on object addGlow(mObjectRoot, glowColor, 1.5); // Glow length measured from in-game as about 1.5 seconds diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 96bd0c03e..2fddc55c3 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -8,6 +8,7 @@ namespace ESM { struct Light; + struct MagicEffect; } namespace Resource @@ -351,7 +352,7 @@ public: void removeEffect (int effectId); void getLoopingEffects (std::vector& out) const; - void addSpellCastGlow(osg::Vec4f glowColor); + void addSpellCastGlow(const ESM::MagicEffect *effect); virtual void updatePtr(const MWWorld::Ptr &ptr); From 1910128e9cbc23309dcdad92d86f2d53f3079dd9 Mon Sep 17 00:00:00 2001 From: Allofich Date: Tue, 9 Aug 2016 00:32:55 +0900 Subject: [PATCH 24/54] Don't remove uniform when spell glow ends --- apps/openmw/mwrender/animation.cpp | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 0470cfe8c..d2f88250d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -90,7 +90,7 @@ namespace { public: GlowUpdater(int texUnit, osg::Vec4f color, const std::vector >& textures, - osg::ref_ptr node, float maxduration, Resource::ResourceSystem* resourcesystem, osg::Uniform* uniform) + osg::ref_ptr node, float maxduration, Resource::ResourceSystem* resourcesystem) : mTexUnit(texUnit) , mColor(color) , mTextures(textures) @@ -99,7 +99,6 @@ namespace , mStartingTime(0) , mResourceSystem(resourcesystem) , mDone(false) - , mUniform(uniform) , mWatchedSpellGlow(NULL) { } @@ -139,8 +138,8 @@ namespace // the permanent glow to display its glow texture cycling properly. if (mWatchedSpellGlow != NULL && mWatchedSpellGlow->isDone()) { - mNode->removeUpdateCallback(mWatchedSpellGlow); - mWatchedSpellGlow = NULL; + mNode->removeUpdateCallback(mWatchedSpellGlow); + mWatchedSpellGlow = NULL; } // Set the starting time to measure glow duration from if this is a temporary glow @@ -160,15 +159,11 @@ namespace writableStateSet = osg::clone(mNode->getStateSet(), osg::CopyOp::SHALLOW_COPY); for (size_t i = 0; i < mTextures.size(); i++) - { writableStateSet->removeTextureAttribute(mTexUnit, mTextures[i]); - } - writableStateSet->removeUniform(mUniform); // remove the uniform given to the temporary glow in addglow() mNode->setStateSet(writableStateSet); - this->reset(); // without this a texture from the glow will continue to show on the object - mResourceSystem->getSceneManager()->recreateShaders(mNode); + mResourceSystem->getSceneManager()->recreateShaders(mNode); mDone = true; } } @@ -212,7 +207,6 @@ namespace float mStartingTime; Resource::ResourceSystem* mResourceSystem; bool mDone; - osg::Uniform* mUniform; osg::ref_ptr mWatchedSpellGlow; }; @@ -1246,10 +1240,7 @@ namespace MWRender node->accept(findLowestUnusedTexUnitVisitor); texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; - osg::Uniform* uniform = new osg::Uniform("envMapColor", glowColor); - - - osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, mResourceSystem, uniform); + osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, mResourceSystem); node->addUpdateCallback(glowUpdater); if (node->getUpdateCallback() && @@ -1267,7 +1258,7 @@ namespace MWRender node->setStateSet(writableStateSet); } writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); - writableStateSet->addUniform(uniform); + writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); mResourceSystem->getSceneManager()->recreateShaders(node); } From cad41599cf7245d7ad596f708a216e7032b1a5b3 Mon Sep 17 00:00:00 2001 From: Allofich Date: Tue, 9 Aug 2016 02:03:13 +0900 Subject: [PATCH 25/54] Fix travis build error and warnings --- apps/openmw/mwclass/door.cpp | 7 +++---- apps/openmw/mwrender/animation.cpp | 7 +------ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 255f38dd9..8ed6bdca8 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -120,10 +120,9 @@ namespace MWClass { MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::MagicEffect *effect; - int index = effect->effectStringToId("sEffectTelekinesis"); - effect = store.get().find(index); + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + int index = ESM::MagicEffect::effectStringToId("sEffectTelekinesis"); + const ESM::MagicEffect *effect = store.get().find(index); animation->addSpellCastGlow(effect); // TODO: Telekinesis glow should only be as long as the door animation } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index d2f88250d..7ed521935 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -183,11 +183,6 @@ namespace return mDone; } - int getTexUnit() - { - return mTexUnit; - } - void setWatchedSpellGlow(osg::ref_ptr watched) { mWatchedSpellGlow = watched; @@ -1190,7 +1185,7 @@ namespace MWRender void Animation::addSpellCastGlow(const ESM::MagicEffect *effect){ - osg::Vec4f glowColor = {1,1,1,1}; + osg::Vec4f glowColor(1,1,1,1); glowColor.x() = effect->mData.mRed / 255.f; glowColor.y() = effect->mData.mGreen / 255.f; glowColor.z() = effect->mData.mBlue / 255.f; From 775162ccdf9d16c8d7647fe7f62954a03a3ae7b8 Mon Sep 17 00:00:00 2001 From: Allofich Date: Tue, 9 Aug 2016 22:41:03 +0900 Subject: [PATCH 26/54] Rewrite spell glow implementation --- apps/openmw/mwmechanics/spellcasting.cpp | 11 +- apps/openmw/mwrender/animation.cpp | 277 +++++++++++------------ apps/openmw/mwrender/animation.hpp | 2 + 3 files changed, 142 insertions(+), 148 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 4c6a4c02d..84467f953 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -572,6 +572,11 @@ namespace MWMechanics short effectId = effect.mId; if (target.getClass().canLock(target)) { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::MagicEffect *magiceffect; + magiceffect = store.get().find(effectId); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); + animation->addSpellCastGlow(magiceffect); if (effectId == ESM::MagicEffect::Lock) { if (target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude @@ -584,12 +589,6 @@ namespace MWMechanics } else if (effectId == ESM::MagicEffect::Open) { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::MagicEffect *magiceffect; - magiceffect = store.get().find(effectId); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); - animation->addSpellCastGlow(magiceffect); - if (target.getCellRef().getLockLevel() <= magnitude) { if (target.getCellRef().getLockLevel() > 0) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 7ed521935..96fa1b53b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -86,125 +86,6 @@ namespace std::vector > mToRemove; }; - class GlowUpdater : public SceneUtil::StateSetUpdater - { - public: - GlowUpdater(int texUnit, osg::Vec4f color, const std::vector >& textures, - osg::ref_ptr node, float maxduration, Resource::ResourceSystem* resourcesystem) - : mTexUnit(texUnit) - , mColor(color) - , mTextures(textures) - , mNode(node) - , mMaxDuration(maxduration) - , mStartingTime(0) - , mResourceSystem(resourcesystem) - , mDone(false) - , mWatchedSpellGlow(NULL) - { - } - - virtual void setDefaults(osg::StateSet *stateset) - { - stateset->setTextureMode(mTexUnit, GL_TEXTURE_2D, osg::StateAttribute::ON); - - osg::TexGen* texGen = new osg::TexGen; - texGen->setMode(osg::TexGen::SPHERE_MAP); - - stateset->setTextureAttributeAndModes(mTexUnit, texGen, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; - texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); - texEnv->setConstantColor(mColor); - texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); - texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); - texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_COLOR); - - stateset->setTextureAttributeAndModes(mTexUnit, texEnv, osg::StateAttribute::ON); - - // Reduce the texture list back down by one when a temporary glow finishes - // to allow FindLowestUnusedTexUnitVisitor to choose the same texunit again. - if (mDone) - stateset->getTextureAttributeList().resize(stateset->getTextureAttributeList().size() - 1); - } - - virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) - { - if (mDone) - return; - - // If there is a permanent enchantment glow already on this object, give the - // permanent glow a pointer to a temporary glow added in a nested update callback - // The permanent glow will remove the temporary glow when it finishes, allowing - // the permanent glow to display its glow texture cycling properly. - if (mWatchedSpellGlow != NULL && mWatchedSpellGlow->isDone()) - { - mNode->removeUpdateCallback(mWatchedSpellGlow); - mWatchedSpellGlow = NULL; - } - - // Set the starting time to measure glow duration from if this is a temporary glow - if ((mMaxDuration >= 0) && mStartingTime == 0) - mStartingTime = nv->getFrameStamp()->getSimulationTime(); - - float time = nv->getFrameStamp()->getSimulationTime(); - int index = (int)(time*16) % mTextures.size(); - stateset->setTextureAttribute(mTexUnit, mTextures[index], osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - if ((mMaxDuration >= 0) && (time - mStartingTime > mMaxDuration)) // If this is a temporary glow and it has finished its duration - { - osg::ref_ptr writableStateSet = NULL; - if (!mNode->getStateSet()) - writableStateSet = mNode->getOrCreateStateSet(); - else - writableStateSet = osg::clone(mNode->getStateSet(), osg::CopyOp::SHALLOW_COPY); - - for (size_t i = 0; i < mTextures.size(); i++) - writableStateSet->removeTextureAttribute(mTexUnit, mTextures[i]); - - mNode->setStateSet(writableStateSet); - this->reset(); // without this a texture from the glow will continue to show on the object - mResourceSystem->getSceneManager()->recreateShaders(mNode); - mDone = true; - } - } - - bool isSpellGlowUpdater() - { - return (mMaxDuration >= 0); - } - - bool isEnchantmentGlowUpdater() - { - return (mMaxDuration < 0); - } - - bool isDone() - { - return mDone; - } - - void setWatchedSpellGlow(osg::ref_ptr watched) - { - mWatchedSpellGlow = watched; - } - - osg::ref_ptr getWatchedSpellGlow() - { - return mWatchedSpellGlow; - } - - private: - int mTexUnit; - osg::Vec4f mColor; - std::vector > mTextures; - osg::ref_ptr mNode; - float mMaxDuration; - float mStartingTime; - Resource::ResourceSystem* mResourceSystem; - bool mDone; - osg::ref_ptr mWatchedSpellGlow; - }; - class NodeMapVisitor : public osg::NodeVisitor { public: @@ -366,6 +247,124 @@ namespace namespace MWRender { + class GlowUpdater : public SceneUtil::StateSetUpdater + { + public: + GlowUpdater(int texUnit, osg::Vec4f color, const std::vector >& textures, + osg::ref_ptr node, float duration, Resource::ResourceSystem* resourcesystem) + : mTexUnit(texUnit) + , mColor(color) + , mOriginalColor(color) + , mTextures(textures) + , mNode(node) + , mDuration(duration) + , mOriginalDuration(duration) + , mStartingTime(0) + , mResourceSystem(resourcesystem) + , mColorChanged(false) + , mDone(false) + { + } + + virtual void setDefaults(osg::StateSet *stateset) + { + stateset->setTextureMode(mTexUnit, GL_TEXTURE_2D, osg::StateAttribute::ON); + + osg::TexGen* texGen = new osg::TexGen; + texGen->setMode(osg::TexGen::SPHERE_MAP); + + stateset->setTextureAttributeAndModes(mTexUnit, texGen, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); + texEnv->setConstantColor(mColor); + texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); + texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); + texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_COLOR); + + stateset->setTextureAttributeAndModes(mTexUnit, texEnv, osg::StateAttribute::ON); + + // Reduce the texture list back down by one when a temporary glow finishes + // to allow FindLowestUnusedTexUnitVisitor to choose the same texunit again. + if (mDone) + stateset->getTextureAttributeList().resize(stateset->getTextureAttributeList().size() - 1); + } + + virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) + { + if (mColorChanged){ + this->reset(); + setDefaults(stateset); + mResourceSystem->getSceneManager()->recreateShaders(mNode); + mColorChanged = false; + } + if (mDone) + return; + + // Set the starting time to measure glow duration from if this is a temporary glow + if ((mDuration >= 0) && mStartingTime == 0) + mStartingTime = nv->getFrameStamp()->getSimulationTime(); + + float time = nv->getFrameStamp()->getSimulationTime(); + int index = (int)(time*16) % mTextures.size(); + stateset->setTextureAttribute(mTexUnit, mTextures[index], osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + if ((mDuration >= 0) && (time - mStartingTime > mDuration)) // If this is a temporary glow and it has finished its duration + { + if (mOriginalDuration >= 0) // if this glowupdater was a temporary glow since its creation + { + for (size_t i = 0; i < mTextures.size(); i++) + stateset->removeTextureAttribute(mTexUnit, mTextures[i]); + this->reset(); + mDone = true; + } + if (mOriginalDuration < 0) // if this glowupdater was originally a permanent glow + { + mDuration = mOriginalDuration; + mStartingTime = 0; + mColor = mOriginalColor; + this->reset(); + setDefaults(stateset); + stateset->addUniform(new osg::Uniform("envMapColor", mColor)); + } + mResourceSystem->getSceneManager()->recreateShaders(mNode); + } + } + + bool isPermanentGlowUpdater() + { + return (mDuration < 0); + } + + bool isDone() + { + return mDone; + } + + void setColor(osg::Vec4f color) + { + mColor = color; + mColorChanged = true; + } + + void setDuration(float duration) + { + mDuration = duration; + } + + private: + int mTexUnit; + osg::Vec4f mColor; + osg::Vec4f mOriginalColor; // for restoring the color of a permanent glow after a temporary glow on the object finishes + std::vector > mTextures; + osg::ref_ptr mNode; + float mDuration; + float mOriginalDuration; // for recording that this is originally a permanent glow if it is changed to a temporary one + float mStartingTime; + Resource::ResourceSystem* mResourceSystem; + bool mColorChanged; + bool mDone; + }; struct Animation::AnimSource { @@ -1190,14 +1189,10 @@ namespace MWRender glowColor.y() = effect->mData.mGreen / 255.f; glowColor.z() = effect->mData.mBlue / 255.f; - if (!mObjectRoot->getUpdateCallback()) // If there is no glow on object + if (!mGlowUpdater) // If there is no glow on object addGlow(mObjectRoot, glowColor, 1.5); // Glow length measured from in-game as about 1.5 seconds - else if (dynamic_cast (mObjectRoot->getUpdateCallback())->isDone() == true) // If there was a temporary glow on object and it finished - addGlow(mObjectRoot, glowColor, 1.5); - - else if (dynamic_cast (mObjectRoot->getUpdateCallback())->isEnchantmentGlowUpdater() == true - && dynamic_cast (mObjectRoot->getUpdateCallback())->getWatchedSpellGlow() == NULL) // If there is a permanent enchantment glow on object and no temporary glow is running + else if (mGlowUpdater->isDone() || (mGlowUpdater->isPermanentGlowUpdater() == true)) addGlow(mObjectRoot, glowColor, 1.5); } @@ -1220,28 +1215,26 @@ namespace MWRender tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); mResourceSystem->getSceneManager()->applyFilterSettings(tex); textures.push_back(tex); - } + } - int texUnit; - - // If we have a spell glow updater left over from this object prevously casting a spell, - // and there was no permanent glow updater on the object to watch it and remove it, we - // remove it here. - if (node->getUpdateCallback() && - dynamic_cast (node->getUpdateCallback())->isSpellGlowUpdater() == true) - node->removeUpdateCallback(node->getUpdateCallback()); + if (mGlowUpdater && mGlowUpdater->isDone()) + node->removeUpdateCallback(mGlowUpdater); FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; node->accept(findLowestUnusedTexUnitVisitor); - texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; - - osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, mResourceSystem); + int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; - node->addUpdateCallback(glowUpdater); - if (node->getUpdateCallback() && - dynamic_cast (node->getUpdateCallback())->isEnchantmentGlowUpdater() == true) - if (glowDuration >= 0) - dynamic_cast (node->getUpdateCallback())->setWatchedSpellGlow(glowUpdater); + if (mGlowUpdater && mGlowUpdater->isPermanentGlowUpdater()) + { + mGlowUpdater->setColor(glowColor); + mGlowUpdater->setDuration(glowDuration); + } + else + { + osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, mResourceSystem); + mGlowUpdater = glowUpdater; + node->addUpdateCallback(glowUpdater); + } // set a texture now so that the ShaderVisitor can find it osg::ref_ptr writableStateSet = NULL; diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 2fddc55c3..a837a26ae 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -33,6 +33,7 @@ namespace MWRender class ResetAccumRootCallback; class RotateController; +class GlowUpdater; class EffectAnimationTime : public SceneUtil::ControllerSource { @@ -262,6 +263,7 @@ protected: float mHeadPitchRadians; osg::ref_ptr mGlowLight; + osg::ref_ptr mGlowUpdater; float mAlpha; From 9b2cb2fb8c9d341abe906eb4be9e928411646342 Mon Sep 17 00:00:00 2001 From: Allofich Date: Tue, 9 Aug 2016 23:50:24 +0900 Subject: [PATCH 27/54] Cleanups --- apps/openmw/mwmechanics/spellcasting.cpp | 15 +++++--- apps/openmw/mwrender/animation.cpp | 48 ++++++++++++------------ 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 84467f953..edaae221a 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -571,14 +571,13 @@ namespace MWMechanics { short effectId = effect.mId; if (target.getClass().canLock(target)) - { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::MagicEffect *magiceffect; - magiceffect = store.get().find(effectId); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); - animation->addSpellCastGlow(magiceffect); + { if (effectId == ESM::MagicEffect::Lock) { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::MagicEffect *magiceffect = store.get().find(effectId); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); + animation->addSpellCastGlow(magiceffect); if (target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude { if (caster == getPlayer()) @@ -589,6 +588,10 @@ namespace MWMechanics } else if (effectId == ESM::MagicEffect::Open) { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::MagicEffect *magiceffect = store.get().find(effectId); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); + animation->addSpellCastGlow(magiceffect); if (target.getCellRef().getLockLevel() <= magnitude) { if (target.getCellRef().getLockLevel() > 0) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 96fa1b53b..bfd5c3647 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -268,26 +268,29 @@ namespace MWRender virtual void setDefaults(osg::StateSet *stateset) { - stateset->setTextureMode(mTexUnit, GL_TEXTURE_2D, osg::StateAttribute::ON); - - osg::TexGen* texGen = new osg::TexGen; - texGen->setMode(osg::TexGen::SPHERE_MAP); - - stateset->setTextureAttributeAndModes(mTexUnit, texGen, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; - texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); - texEnv->setConstantColor(mColor); - texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); - texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); - texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_COLOR); - - stateset->setTextureAttributeAndModes(mTexUnit, texEnv, osg::StateAttribute::ON); - - // Reduce the texture list back down by one when a temporary glow finishes - // to allow FindLowestUnusedTexUnitVisitor to choose the same texunit again. if (mDone) - stateset->getTextureAttributeList().resize(stateset->getTextureAttributeList().size() - 1); + { + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXTURE); + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXGEN); + } + else + { + stateset->setTextureMode(mTexUnit, GL_TEXTURE_2D, osg::StateAttribute::ON); + osg::TexGen* texGen = new osg::TexGen; + texGen->setMode(osg::TexGen::SPHERE_MAP); + + stateset->setTextureAttributeAndModes(mTexUnit, texGen, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); + texEnv->setConstantColor(mColor); + texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); + texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); + texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_COLOR); + + stateset->setTextureAttributeAndModes(mTexUnit, texEnv, osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("envMapColor", mColor)); + } } virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) @@ -295,7 +298,6 @@ namespace MWRender if (mColorChanged){ this->reset(); setDefaults(stateset); - mResourceSystem->getSceneManager()->recreateShaders(mNode); mColorChanged = false; } if (mDone) @@ -313,10 +315,10 @@ namespace MWRender { if (mOriginalDuration >= 0) // if this glowupdater was a temporary glow since its creation { - for (size_t i = 0; i < mTextures.size(); i++) - stateset->removeTextureAttribute(mTexUnit, mTextures[i]); + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXTURE); this->reset(); mDone = true; + mResourceSystem->getSceneManager()->recreateShaders(mNode); } if (mOriginalDuration < 0) // if this glowupdater was originally a permanent glow { @@ -325,9 +327,7 @@ namespace MWRender mColor = mOriginalColor; this->reset(); setDefaults(stateset); - stateset->addUniform(new osg::Uniform("envMapColor", mColor)); } - mResourceSystem->getSceneManager()->recreateShaders(mNode); } } From 4b9aff7a03d0117fc0f096d6989282ac26f67b53 Mon Sep 17 00:00:00 2001 From: Allofich Date: Wed, 10 Aug 2016 00:43:14 +0900 Subject: [PATCH 28/54] Glowupdater fix when using shaders --- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- apps/openmw/mwrender/animation.cpp | 74 ++++++++++++------------ 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index edaae221a..c55ae67f2 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -571,7 +571,7 @@ namespace MWMechanics { short effectId = effect.mId; if (target.getClass().canLock(target)) - { + { if (effectId == ESM::MagicEffect::Lock) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index bfd5c3647..4996ad2a8 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1182,8 +1182,8 @@ namespace MWRender int mLowestUnusedTexUnit; }; - void Animation::addSpellCastGlow(const ESM::MagicEffect *effect){ - + void Animation::addSpellCastGlow(const ESM::MagicEffect *effect) + { osg::Vec4f glowColor(1,1,1,1); glowColor.x() = effect->mData.mRed / 255.f; glowColor.y() = effect->mData.mGreen / 255.f; @@ -1197,28 +1197,9 @@ namespace MWRender } void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor, float glowDuration) - { - std::vector > textures; - for (int i=0; i<32; ++i) - { - std::stringstream stream; - stream << "textures/magicitem/caust"; - stream << std::setw(2); - stream << std::setfill('0'); - stream << i; - stream << ".dds"; - - osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(stream.str()); - osg::ref_ptr tex (new osg::Texture2D(image)); - tex->setName("envMap"); - tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); - tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); - mResourceSystem->getSceneManager()->applyFilterSettings(tex); - textures.push_back(tex); - } - + { if (mGlowUpdater && mGlowUpdater->isDone()) - node->removeUpdateCallback(mGlowUpdater); + node->removeUpdateCallback(mGlowUpdater); FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; node->accept(findLowestUnusedTexUnitVisitor); @@ -1230,25 +1211,44 @@ namespace MWRender mGlowUpdater->setDuration(glowDuration); } else - { + { + std::vector > textures; + for (int i=0; i<32; ++i) + { + std::stringstream stream; + stream << "textures/magicitem/caust"; + stream << std::setw(2); + stream << std::setfill('0'); + stream << i; + stream << ".dds"; + + osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(stream.str()); + osg::ref_ptr tex (new osg::Texture2D(image)); + tex->setName("envMap"); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); + mResourceSystem->getSceneManager()->applyFilterSettings(tex); + textures.push_back(tex); + } + osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, mResourceSystem); mGlowUpdater = glowUpdater; node->addUpdateCallback(glowUpdater); - } + - // set a texture now so that the ShaderVisitor can find it - osg::ref_ptr writableStateSet = NULL; - if (!node->getStateSet()) - writableStateSet = node->getOrCreateStateSet(); - else - { - writableStateSet = osg::clone(node->getStateSet(), osg::CopyOp::SHALLOW_COPY); - node->setStateSet(writableStateSet); + // set a texture now so that the ShaderVisitor can find it + osg::ref_ptr writableStateSet = NULL; + if (!node->getStateSet()) + writableStateSet = node->getOrCreateStateSet(); + else + { + writableStateSet = osg::clone(node->getStateSet(), osg::CopyOp::SHALLOW_COPY); + node->setStateSet(writableStateSet); + } + writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); + writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); + mResourceSystem->getSceneManager()->recreateShaders(node); } - writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); - writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); - - mResourceSystem->getSceneManager()->recreateShaders(node); } // TODO: Should not be here From 67f31d948e54fc1f3e9b7d9895eb1a20f575fb38 Mon Sep 17 00:00:00 2001 From: Allofich Date: Wed, 10 Aug 2016 01:11:14 +0900 Subject: [PATCH 29/54] Add removeTexture method --- apps/openmw/mwrender/animation.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 4996ad2a8..77375b410 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -269,10 +269,7 @@ namespace MWRender virtual void setDefaults(osg::StateSet *stateset) { if (mDone) - { - stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXTURE); - stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXGEN); - } + removeTexture(stateset); else { stateset->setTextureMode(mTexUnit, GL_TEXTURE_2D, osg::StateAttribute::ON); @@ -293,6 +290,19 @@ namespace MWRender } } + void removeTexture(osg::StateSet* stateset) + { + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXTURE); + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXGEN); + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXENV); + stateset->removeTextureMode(mTexUnit, GL_TEXTURE_2D); + stateset->removeUniform("envMapColor"); + + osg::StateSet::TextureAttributeList& list = stateset->getTextureAttributeList(); + while (list.size() && list.rbegin()->empty()) + list.pop_back(); + } + virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) { if (mColorChanged){ @@ -315,7 +325,7 @@ namespace MWRender { if (mOriginalDuration >= 0) // if this glowupdater was a temporary glow since its creation { - stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXTURE); + removeTexture(stateset); this->reset(); mDone = true; mResourceSystem->getSceneManager()->recreateShaders(mNode); From 1c76c93ed80f7bd7504f2ceee96ad2feb04addb7 Mon Sep 17 00:00:00 2001 From: Allofich Date: Wed, 10 Aug 2016 01:35:22 +0900 Subject: [PATCH 30/54] Use raw pointer to node in glowupdater --- apps/openmw/mwrender/animation.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 77375b410..6329b5f5d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -251,7 +251,7 @@ namespace MWRender { public: GlowUpdater(int texUnit, osg::Vec4f color, const std::vector >& textures, - osg::ref_ptr node, float duration, Resource::ResourceSystem* resourcesystem) + osg::Node* node, float duration, Resource::ResourceSystem* resourcesystem) : mTexUnit(texUnit) , mColor(color) , mOriginalColor(color) @@ -367,7 +367,7 @@ namespace MWRender osg::Vec4f mColor; osg::Vec4f mOriginalColor; // for restoring the color of a permanent glow after a temporary glow on the object finishes std::vector > mTextures; - osg::ref_ptr mNode; + osg::Node* mNode; float mDuration; float mOriginalDuration; // for recording that this is originally a permanent glow if it is changed to a temporary one float mStartingTime; @@ -1200,7 +1200,7 @@ namespace MWRender glowColor.z() = effect->mData.mBlue / 255.f; if (!mGlowUpdater) // If there is no glow on object - addGlow(mObjectRoot, glowColor, 1.5); // Glow length measured from in-game as about 1.5 seconds + addGlow(mObjectRoot, glowColor, 1.5); // Glow length measured from original engine as about 1.5 seconds else if (mGlowUpdater->isDone() || (mGlowUpdater->isPermanentGlowUpdater() == true)) addGlow(mObjectRoot, glowColor, 1.5); From 83b715734f280ef7a0d8b9abb7de476fd8f881d6 Mon Sep 17 00:00:00 2001 From: Allofich Date: Wed, 10 Aug 2016 02:50:30 +0900 Subject: [PATCH 31/54] Move part of addGlow() to addSpellCastGlow() --- apps/openmw/mwrender/animation.cpp | 94 ++++++++++++++---------------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6329b5f5d..e5614f3f8 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1199,66 +1199,62 @@ namespace MWRender glowColor.y() = effect->mData.mGreen / 255.f; glowColor.z() = effect->mData.mBlue / 255.f; - if (!mGlowUpdater) // If there is no glow on object - addGlow(mObjectRoot, glowColor, 1.5); // Glow length measured from original engine as about 1.5 seconds + if (!mGlowUpdater || (mGlowUpdater->isDone() || (mGlowUpdater->isPermanentGlowUpdater() == true))) + { + if (mGlowUpdater && mGlowUpdater->isDone()) + mObjectRoot->removeUpdateCallback(mGlowUpdater); - else if (mGlowUpdater->isDone() || (mGlowUpdater->isPermanentGlowUpdater() == true)) - addGlow(mObjectRoot, glowColor, 1.5); + if (mGlowUpdater && mGlowUpdater->isPermanentGlowUpdater()) + { + mGlowUpdater->setColor(glowColor); + mGlowUpdater->setDuration(1.5); // Glow length measured from original engine as about 1.5 seconds + } + else + addGlow(mObjectRoot, glowColor, 1.5); + } } void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor, float glowDuration) - { - if (mGlowUpdater && mGlowUpdater->isDone()) - node->removeUpdateCallback(mGlowUpdater); + { + std::vector > textures; + for (int i=0; i<32; ++i) + { + std::stringstream stream; + stream << "textures/magicitem/caust"; + stream << std::setw(2); + stream << std::setfill('0'); + stream << i; + stream << ".dds"; + + osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(stream.str()); + osg::ref_ptr tex (new osg::Texture2D(image)); + tex->setName("envMap"); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); + mResourceSystem->getSceneManager()->applyFilterSettings(tex); + textures.push_back(tex); + } FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; node->accept(findLowestUnusedTexUnitVisitor); int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; - if (mGlowUpdater && mGlowUpdater->isPermanentGlowUpdater()) - { - mGlowUpdater->setColor(glowColor); - mGlowUpdater->setDuration(glowDuration); - } - else - { - std::vector > textures; - for (int i=0; i<32; ++i) - { - std::stringstream stream; - stream << "textures/magicitem/caust"; - stream << std::setw(2); - stream << std::setfill('0'); - stream << i; - stream << ".dds"; - - osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(stream.str()); - osg::ref_ptr tex (new osg::Texture2D(image)); - tex->setName("envMap"); - tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); - tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); - mResourceSystem->getSceneManager()->applyFilterSettings(tex); - textures.push_back(tex); - } - - osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, mResourceSystem); - mGlowUpdater = glowUpdater; - node->addUpdateCallback(glowUpdater); + osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, mResourceSystem); + mGlowUpdater = glowUpdater; + node->addUpdateCallback(glowUpdater); - - // set a texture now so that the ShaderVisitor can find it - osg::ref_ptr writableStateSet = NULL; - if (!node->getStateSet()) - writableStateSet = node->getOrCreateStateSet(); - else - { - writableStateSet = osg::clone(node->getStateSet(), osg::CopyOp::SHALLOW_COPY); - node->setStateSet(writableStateSet); - } - writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); - writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); - mResourceSystem->getSceneManager()->recreateShaders(node); + // set a texture now so that the ShaderVisitor can find it + osg::ref_ptr writableStateSet = NULL; + if (!node->getStateSet()) + writableStateSet = node->getOrCreateStateSet(); + else + { + writableStateSet = osg::clone(node->getStateSet(), osg::CopyOp::SHALLOW_COPY); + node->setStateSet(writableStateSet); } + writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); + writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); + mResourceSystem->getSceneManager()->recreateShaders(node); } // TODO: Should not be here From cb9bb92b26b25a1a29917dc3d845f62c9b3306a9 Mon Sep 17 00:00:00 2001 From: Allofich Date: Wed, 10 Aug 2016 21:02:17 +0900 Subject: [PATCH 32/54] Play locked sounds on locked objects instead of actors --- apps/openmw/mwclass/container.cpp | 2 +- apps/openmw/mwclass/door.cpp | 2 +- apps/openmw/mwworld/failedaction.cpp | 4 ++-- apps/openmw/mwworld/failedaction.hpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 09891652a..47e24f0d6 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -190,7 +190,7 @@ namespace MWClass } else { - boost::shared_ptr action(new MWWorld::FailedAction); + boost::shared_ptr action(new MWWorld::FailedAction(std::string(), ptr)); action->setSound(lockedSound); return action; } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 8ed6bdca8..99d4a6011 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -220,7 +220,7 @@ namespace MWClass else { // locked, and we can't open. - boost::shared_ptr action(new MWWorld::FailedAction); + boost::shared_ptr action(new MWWorld::FailedAction(std::string(), ptr)); action->setSound(lockedSound); return action; } diff --git a/apps/openmw/mwworld/failedaction.cpp b/apps/openmw/mwworld/failedaction.cpp index 49ca9dae0..45df75a32 100644 --- a/apps/openmw/mwworld/failedaction.cpp +++ b/apps/openmw/mwworld/failedaction.cpp @@ -8,8 +8,8 @@ namespace MWWorld { - FailedAction::FailedAction(const std::string &msg) - : Action(false), mMessage(msg) + FailedAction::FailedAction(const std::string &msg, const Ptr& target) + : Action(false, target), mMessage(msg) { } void FailedAction::executeImp(const Ptr &actor) diff --git a/apps/openmw/mwworld/failedaction.hpp b/apps/openmw/mwworld/failedaction.hpp index f248ee3bd..bafbb6f2d 100644 --- a/apps/openmw/mwworld/failedaction.hpp +++ b/apps/openmw/mwworld/failedaction.hpp @@ -13,7 +13,7 @@ namespace MWWorld virtual void executeImp(const Ptr &actor); public: - FailedAction(const std::string &message = std::string()); + FailedAction(const std::string &message = std::string(), const Ptr& target = Ptr()); }; } From ddbfe0c9442530d1e76fd94d1221866ced9d5640 Mon Sep 17 00:00:00 2001 From: MiroslavR Date: Fri, 12 Aug 2016 03:14:36 +0200 Subject: [PATCH 33/54] Fix disabled door markers appearing on the map (Fixes #3348) --- apps/openmw/mwgui/mapwindow.cpp | 134 +++++++++++++++++--------------- apps/openmw/mwgui/mapwindow.hpp | 4 + 2 files changed, 77 insertions(+), 61 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 7c52f09cb..d51af87d9 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -163,7 +163,6 @@ namespace MWGui , mInterior(false) , mLocalMap(NULL) , mCompass(NULL) - , mPrefix() , mChanged(true) , mFogOfWarToggled(true) , mFogOfWarEnabled(fogOfWarEnabled) @@ -174,6 +173,7 @@ namespace MWGui , mMarkerUpdateTimer(0.0f) , mLastDirectionX(0.0f) , mLastDirectionY(0.0f) + , mNeedDoorMarkersUpdate(false) { mCustomMarkers.eventMarkersChanged += MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } @@ -368,12 +368,6 @@ namespace MWGui applyFogOfWar(); - - // clear all previous door markers - for (std::vector::iterator it = mDoorMarkerWidgets.begin(); it != mDoorMarkerWidgets.end(); ++it) - MyGUI::Gui::getInstance().destroyWidget(*it); - mDoorMarkerWidgets.clear(); - // Update the map textures TextureVector textures; for (int mx=0; mx doors; - if (interior) - { - MWWorld::CellStore* cell = world->getInterior (mPrefix); - world->getDoorMarkers(cell, doors); - } - else - { - for (int dX=-mCellDistance; dX<=mCellDistance; ++dX) - { - for (int dY=-mCellDistance; dY<=mCellDistance; ++dY) - { - MWWorld::CellStore* cell = world->getExterior (mCurX+dX, mCurY+dY); - world->getDoorMarkers(cell, doors); - } - } - } - - // Create a widget for each marker - int counter = 0; - for (std::vector::iterator it = doors.begin(); it != doors.end(); ++it) - { - MWBase::World::DoorMarker marker = *it; - - std::vector destNotes; - CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(marker.dest); - for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it) - destNotes.push_back(it->second.mNote); - - MarkerUserData data (mLocalMapRender); - data.notes = destNotes; - data.caption = marker.name; - MyGUI::IntPoint widgetPos = getMarkerPosition(marker.x, marker.y, data); - MyGUI::IntCoord widgetCoord(widgetPos.left - 4, - widgetPos.top - 4, - 8, 8); - ++counter; - MarkerWidget* markerWidget = mLocalMap->createWidget("MarkerButton", - widgetCoord, MyGUI::Align::Default); - markerWidget->setNormalColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); - markerWidget->setHoverColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal_over}"))); - markerWidget->setDepth(Local_MarkerLayer); - markerWidget->setNeedMouseFocus(true); - // Used by tooltips to not show the tooltip if marker is hidden by fog of war - markerWidget->setUserString("ToolTipType", "MapMarker"); - - markerWidget->setUserData(data); - doorMarkerCreated(markerWidget); - - mDoorMarkerWidgets.push_back(markerWidget); - } + // Delay the door markers update until scripts have been given a chance to run. + // If we don't do this, door markers that should be disabled will still appear on the map. + mNeedDoorMarkersUpdate = true; updateMagicMarkers(); updateCustomMarkers(); @@ -565,6 +508,12 @@ namespace MWGui void LocalMapBase::onFrame(float dt) { + if (mNeedDoorMarkersUpdate) + { + updateDoorMarkers(); + mNeedDoorMarkersUpdate = false; + } + mMarkerUpdateTimer += dt; if (mMarkerUpdateTimer >= 0.25) @@ -574,6 +523,69 @@ namespace MWGui } } + void LocalMapBase::updateDoorMarkers() + { + // clear all previous door markers + for (std::vector::iterator it = mDoorMarkerWidgets.begin(); it != mDoorMarkerWidgets.end(); ++it) + MyGUI::Gui::getInstance().destroyWidget(*it); + mDoorMarkerWidgets.clear(); + + MWBase::World* world = MWBase::Environment::get().getWorld(); + + // Retrieve the door markers we want to show + std::vector doors; + if (mInterior) + { + MWWorld::CellStore* cell = world->getInterior (mPrefix); + world->getDoorMarkers(cell, doors); + } + else + { + for (int dX=-mCellDistance; dX<=mCellDistance; ++dX) + { + for (int dY=-mCellDistance; dY<=mCellDistance; ++dY) + { + MWWorld::CellStore* cell = world->getExterior (mCurX+dX, mCurY+dY); + world->getDoorMarkers(cell, doors); + } + } + } + + // Create a widget for each marker + int counter = 0; + for (std::vector::iterator it = doors.begin(); it != doors.end(); ++it) + { + MWBase::World::DoorMarker marker = *it; + + std::vector destNotes; + CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(marker.dest); + for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it) + destNotes.push_back(it->second.mNote); + + MarkerUserData data (mLocalMapRender); + data.notes = destNotes; + data.caption = marker.name; + MyGUI::IntPoint widgetPos = getMarkerPosition(marker.x, marker.y, data); + MyGUI::IntCoord widgetCoord(widgetPos.left - 4, + widgetPos.top - 4, + 8, 8); + ++counter; + MarkerWidget* markerWidget = mLocalMap->createWidget("MarkerButton", + widgetCoord, MyGUI::Align::Default); + markerWidget->setNormalColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); + markerWidget->setHoverColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal_over}"))); + markerWidget->setDepth(Local_MarkerLayer); + markerWidget->setNeedMouseFocus(true); + // Used by tooltips to not show the tooltip if marker is hidden by fog of war + markerWidget->setUserString("ToolTipType", "MapMarker"); + + markerWidget->setUserData(data); + doorMarkerCreated(markerWidget); + + mDoorMarkerWidgets.push_back(markerWidget); + } + } + void LocalMapBase::updateMagicMarkers() { // clear all previous markers diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 773522903..977773179 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -155,6 +155,10 @@ namespace MWGui float mLastDirectionX; float mLastDirectionY; + + private: + void updateDoorMarkers(); + bool mNeedDoorMarkersUpdate; }; class EditNoteDialog : public MWGui::WindowModal From d617651307cfa90503d641f2ca9c2c8b58606e25 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sat, 13 Aug 2016 09:57:03 +0900 Subject: [PATCH 34/54] Don't allow re-casting of bound equipment spells --- apps/openmw/mwmechanics/spellcasting.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index c55ae67f2..61365be97 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -356,6 +356,14 @@ namespace MWMechanics bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); + ActiveSpells targetSpells; + if (target.getClass().isActor()) + targetSpells = target.getClass().getCreatureStats(target).getActiveSpells(); + + bool canCastAnEffect = false; // For bound equipment.If this remains false + // throughout the iteration of this spell's + // effects, we display a "can't re-cast" message + for (std::vector::const_iterator effectIt (effects.mList.begin()); effectIt!=effects.mList.end(); ++effectIt) { @@ -366,6 +374,16 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getStore().get().find ( effectIt->mEffectID); + // Re-casting a bound equipment effect has no effect if the spell is still active + if (magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable && targetSpells.isSpellActive(mId)) + { + if (effectIt == (effects.mList.end() - 1) && !canCastAnEffect && castByPlayer) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}"); + continue; + } + else + canCastAnEffect = true; + if (!checkEffectTarget(effectIt->mEffectID, target, castByPlayer)) continue; From ee2702087cdff72b12309cbc599ffac582d54b09 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sun, 14 Aug 2016 17:23:27 +0900 Subject: [PATCH 35/54] Make zero-weight boots play light boot footsteps --- apps/openmw/mwclass/npc.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 52cd562d1..98fdd7671 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1161,6 +1161,7 @@ namespace MWClass switch(boots->getClass().getEquipmentSkill(*boots)) { + case ESM::Skill::Unarmored: case ESM::Skill::LightArmor: return (name == "left") ? "FootLightLeft" : "FootLightRight"; case ESM::Skill::MediumArmor: From cf7278f8aea58d0a9ced8214aef322fe8f679ff8 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sun, 14 Aug 2016 18:36:46 +0900 Subject: [PATCH 36/54] Don't show weight tooltip for 0-weight equipment --- apps/openmw/mwclass/armor.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 2e4667016..867e14bd9 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -226,8 +226,10 @@ namespace MWClass typeText = "#{sLight}"; else if (armorType == ESM::Skill::MediumArmor) typeText = "#{sMedium}"; - else + else if (armorType == ESM::Skill::HeavyArmor) typeText = "#{sHeavy}"; + else // if (armorType == ESM::Skill::Unarmored) + typeText = ""; text += "\n#{sArmorRating}: " + MWGui::ToolTips::toString(getEffectiveArmorRating(ptr, MWMechanics::getPlayer())); @@ -236,7 +238,9 @@ namespace MWClass text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/" + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); - text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight) + " (" + typeText + ")"; + if (typeText != "") + text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight) + " (" + typeText + ")"; + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { From caf025f93d8fcf66fd27d0bd8037968a847d9d17 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sun, 14 Aug 2016 19:07:42 +0900 Subject: [PATCH 37/54] Don't show 0 weight tooltips except for potions --- apps/openmw/mwclass/apparatus.cpp | 2 +- apps/openmw/mwclass/book.cpp | 2 +- apps/openmw/mwclass/clothing.cpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 2 +- apps/openmw/mwclass/probe.cpp | 2 +- apps/openmw/mwclass/repair.cpp | 2 +- apps/openmw/mwclass/weapon.cpp | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 8907a5f8d..970ed8980 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -112,7 +112,7 @@ namespace MWClass std::string text; text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); - text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index a7d8ed8f4..cdf6b2a18 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -126,7 +126,7 @@ namespace MWClass std::string text; - text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 20ebab314..30344296e 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -175,7 +175,7 @@ namespace MWClass std::string text; - text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 99a50a67a..eabaf09c9 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -125,7 +125,7 @@ namespace MWClass std::string text; - text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index d3889d2fc..9b97c6e12 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -126,7 +126,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); - text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 79d33ba60..cf28c8873 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -126,7 +126,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); - text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 271f52bde..799c7a3e8 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -129,7 +129,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); - text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index f3f840921..50017ff18 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -318,7 +318,7 @@ namespace MWClass + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); } - text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); info.enchant = ref->mBase->mEnchant; From 4f5d676b4e76cd4a2cd41e6ecb337e883da7940c Mon Sep 17 00:00:00 2001 From: Allofich Date: Sun, 14 Aug 2016 19:15:28 +0900 Subject: [PATCH 38/54] Allow weight tooltips for gold and keys if not 0 --- apps/openmw/mwclass/misc.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 2f41fca30..1d1bfb047 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -159,11 +159,9 @@ namespace MWClass std::string text; + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); if (!gold && !ref->mBase->mData.mIsKey) - { - text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); - } if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); From 502a26a7ff6398880293fd6f4dacef26c46d577d Mon Sep 17 00:00:00 2001 From: Allofich Date: Sun, 14 Aug 2016 19:37:22 +0900 Subject: [PATCH 39/54] Use getWeightString() to avoid casting to int --- apps/openmw/mwclass/apparatus.cpp | 2 +- apps/openmw/mwclass/book.cpp | 2 +- apps/openmw/mwclass/clothing.cpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 2 +- apps/openmw/mwclass/misc.cpp | 2 +- apps/openmw/mwclass/probe.cpp | 2 +- apps/openmw/mwclass/repair.cpp | 2 +- apps/openmw/mwclass/weapon.cpp | 2 +- apps/openmw/mwgui/tooltips.cpp | 8 ++++++++ apps/openmw/mwgui/tooltips.hpp | 1 + 11 files changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 970ed8980..3642a22ed 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -112,7 +112,7 @@ namespace MWClass std::string text; text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); + text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index cdf6b2a18..f2c97d770 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -126,7 +126,7 @@ namespace MWClass std::string text; - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); + text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 30344296e..1d99ee8e2 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -175,7 +175,7 @@ namespace MWClass std::string text; - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); + text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index eabaf09c9..a9a995f2f 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -125,7 +125,7 @@ namespace MWClass std::string text; - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); + text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 9b97c6e12..a335415ac 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -126,7 +126,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); + text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 1d1bfb047..3e1d750ad 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -159,7 +159,7 @@ namespace MWClass std::string text; - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); + text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); if (!gold && !ref->mBase->mData.mIsKey) text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index cf28c8873..185aa66cb 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -126,7 +126,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); + text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 799c7a3e8..cbdfade2b 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -129,7 +129,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); + text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 50017ff18..470c7040d 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -318,7 +318,7 @@ namespace MWClass + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); } - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mWeight, "#{sWeight}"); + text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); info.enchant = ref->mBase->mEnchant; diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 9e26cb0f9..4bcf273cd 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -586,6 +586,14 @@ namespace MWGui return stream.str(); } + std::string ToolTips::getWeightString(const float weight, const std::string& prefix) + { + if (weight == 0) + return ""; + else + return "\n" + prefix + ": " + toString(weight); + } + std::string ToolTips::getValueString(const int value, const std::string& prefix) { if (value == 0) diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index 1fc736bff..de5b89b7f 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -62,6 +62,7 @@ namespace MWGui void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y); ///< set the screen-space position of the tooltip for focused object + static std::string getWeightString(const float weight, const std::string& prefix); static std::string getValueString(const int value, const std::string& prefix); ///< @return "prefix: value" or "" if value is 0 From 7bf04b5014889c947a3a06c350caf2f07722b0c1 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sun, 14 Aug 2016 20:29:20 +0900 Subject: [PATCH 40/54] Allow showing value of zero-weight lights --- apps/openmw/mwclass/light.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index c7ebc184f..335c0453a 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -159,11 +159,9 @@ namespace MWClass if (Settings::Manager::getBool("show effect duration","Game")) text += "\n#{sDuration}: " + MWGui::ToolTips::toString(ptr.getClass().getRemainingUsageTime(ptr)); - if (ref->mBase->mData.mWeight != 0) - { - text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); - } + + text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); From a381a15b192ef2b71ec38d5693cd1ec9dda680d5 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sun, 14 Aug 2016 21:52:48 +0900 Subject: [PATCH 41/54] Make loopgroup loop correct number of times --- apps/openmw/mwscript/animationextensions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index 44c4612ec..347bbca56 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -89,7 +89,7 @@ namespace MWScript throw std::runtime_error ("animation mode out of range"); } - MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, loops, true); + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, loops + 1, true); } }; From 2f6075329b8cae8fd49687d628644846aa12b524 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Aug 2016 15:10:50 +0200 Subject: [PATCH 42/54] Clang warning fixes --- apps/opencs/view/render/instancemode.cpp | 2 -- apps/openmw/mwgui/levelupdialog.cpp | 21 --------------------- components/files/escape.hpp | 2 +- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 4eb9ea388..8b644edbe 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -370,8 +370,6 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event) selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); paged->setCellSelection (selection); } - - noCell = false; } } else if (CSVRender::PagedWorldspaceWidget *paged = diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index e64eb510e..1434455b2 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -135,27 +135,6 @@ namespace MWGui MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats(player); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); - const ESM::NPC *playerData = player.get()->mBase; - - // set class image - const ESM::Class *cls = - world->getStore().get().find(playerData->mClass); - - if(world->getStore().get().isDynamic(cls->mId)) - { - // Choosing Stealth specialization and Speed/Agility as attributes, if possible. Otherwise fall back to first class found. - MWWorld::SharedIterator it = world->getStore().get().begin(); - for(; it != world->getStore().get().end(); ++it) - { - if(it->mData.mIsPlayable && it->mData.mSpecialization == 2 && it->mData.mAttribute[0] == 4 && it->mData.mAttribute[1] == 3) - break; - } - if (it == world->getStore().get().end()) - it = world->getStore().get().begin(); - if (it != world->getStore().get().end()) - cls = &*it; - } - setClassImage(mClassImage, getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(0), pcStats.getSkillIncreasesForSpecialization(1), pcStats.getSkillIncreasesForSpecialization(2))); diff --git a/components/files/escape.hpp b/components/files/escape.hpp index 3b016a12d..2017c2ed2 100644 --- a/components/files/escape.hpp +++ b/components/files/escape.hpp @@ -193,4 +193,4 @@ namespace Files std::istream & operator>> (std::istream & istream, EscapePath & escapePath); } /* namespace Files */ -#endif /* COMPONENTS_FILES_ESCAPE_HPP */ \ No newline at end of file +#endif /* COMPONENTS_FILES_ESCAPE_HPP */ From 0d439750a2533cb9d8dc5e3f92b0340fc00b7be3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Aug 2016 15:20:52 +0200 Subject: [PATCH 43/54] Clang warning fix --- extern/osgQt/GraphicsWindowQt | 10 --- extern/osgQt/GraphicsWindowQt.cpp | 123 ------------------------------ 2 files changed, 133 deletions(-) diff --git a/extern/osgQt/GraphicsWindowQt b/extern/osgQt/GraphicsWindowQt index 3be1da051..1e34fc9db 100644 --- a/extern/osgQt/GraphicsWindowQt +++ b/extern/osgQt/GraphicsWindowQt @@ -37,16 +37,6 @@ namespace osgQt // forward declarations class GraphicsWindowQt; -/** The function sets the viewer that will be used after entering - * the Qt main loop (QCoreApplication::exec()). - * - * The function also initializes internal structures required for proper - * scene rendering. - * - * The method must be called from main thread. */ -void setViewer( osgViewer::ViewerBase *viewer ); - - class GLWidget : public QGLWidget { typedef QGLWidget inherited; diff --git a/extern/osgQt/GraphicsWindowQt.cpp b/extern/osgQt/GraphicsWindowQt.cpp index 17150bed4..9e5f6e55e 100644 --- a/extern/osgQt/GraphicsWindowQt.cpp +++ b/extern/osgQt/GraphicsWindowQt.cpp @@ -26,28 +26,6 @@ using namespace osgQt; -/// The object responsible for the scene re-rendering. -class HeartBeat : public QObject { -public: - int _timerId; - osg::Timer _lastFrameStartTime; - osg::observer_ptr< osgViewer::ViewerBase > _viewer; - - virtual ~HeartBeat(); - - void init( osgViewer::ViewerBase *viewer ); - void stopTimer(); - void timerEvent( QTimerEvent *event ); - - static HeartBeat* instance(); -private: - HeartBeat(); - - static QPointer heartBeat; -}; - -QPointer HeartBeat::heartBeat; - #if (QT_VERSION < QT_VERSION_CHECK(5, 2, 0)) #define GETDEVICEPIXELRATIO() 1.0 #else @@ -637,104 +615,3 @@ private: QtWindowingSystem& operator=( const QtWindowingSystem& ); }; - -void osgQt::setViewer( osgViewer::ViewerBase *viewer ) -{ - HeartBeat::instance()->init( viewer ); -} - - -/// Constructor. Must be called from main thread. -HeartBeat::HeartBeat() : _timerId( 0 ) -{ -} - - -/// Destructor. Must be called from main thread. -HeartBeat::~HeartBeat() -{ - stopTimer(); -} - -HeartBeat* HeartBeat::instance() -{ - if (!heartBeat) - { - heartBeat = new HeartBeat(); - } - return heartBeat; -} - -void HeartBeat::stopTimer() -{ - if ( _timerId != 0 ) - { - killTimer( _timerId ); - _timerId = 0; - } -} - - -/// Initializes the loop for viewer. Must be called from main thread. -void HeartBeat::init( osgViewer::ViewerBase *viewer ) -{ - if( _viewer == viewer ) - return; - - stopTimer(); - - _viewer = viewer; - - if( viewer ) - { - _timerId = startTimer( 0 ); - _lastFrameStartTime.setStartTick( 0 ); - } -} - - -void HeartBeat::timerEvent( QTimerEvent * /*event*/ ) -{ - osg::ref_ptr< osgViewer::ViewerBase > viewer; - if( !_viewer.lock( viewer ) ) - { - // viewer has been deleted -> stop timer - stopTimer(); - return; - } - - // limit the frame rate - if( viewer->getRunMaxFrameRate() > 0.0) - { - double dt = _lastFrameStartTime.time_s(); - double minFrameTime = 1.0 / viewer->getRunMaxFrameRate(); - if (dt < minFrameTime) - OpenThreads::Thread::microSleep(static_cast(1000000.0*(minFrameTime-dt))); - } - else - { - // avoid excessive CPU loading when no frame is required in ON_DEMAND mode - if( viewer->getRunFrameScheme() == osgViewer::ViewerBase::ON_DEMAND ) - { - double dt = _lastFrameStartTime.time_s(); - if (dt < 0.01) - OpenThreads::Thread::microSleep(static_cast(1000000.0*(0.01-dt))); - } - - // record start frame time - _lastFrameStartTime.setStartTick(); - - // make frame - if( viewer->getRunFrameScheme() == osgViewer::ViewerBase::ON_DEMAND ) - { - if( viewer->checkNeedToDoFrame() ) - { - viewer->frame(); - } - } - else - { - viewer->frame(); - } - } -} From 0547d4b937dc48b0084a19ce806456b1b406e39c Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sun, 14 Aug 2016 16:17:09 +0200 Subject: [PATCH 44/54] clang says that these must be in the MWWorld namespace --- apps/openmw/mwworld/weather.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 1cfba0daf..46991c440 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -96,8 +96,8 @@ T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettin -template class TimeOfDayInterpolator; -template class TimeOfDayInterpolator; +template class MWWorld::TimeOfDayInterpolator; +template class MWWorld::TimeOfDayInterpolator; Weather::Weather(const std::string& name, const Fallback::Map& fallback, From 0fbc0d0da775523b1d9e62d3d2166c6c352a2d4e Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Aug 2016 16:41:17 +0200 Subject: [PATCH 45/54] Attempt to silence warning --- apps/openmw/mwgui/windowbase.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index c9d1b0617..99b74529e 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -79,12 +79,13 @@ void WindowModal::close() NoDrop::NoDrop(DragAndDrop *drag, MyGUI::Widget *widget) : mWidget(widget), mDrag(drag), mTransparent(false) { - if (!mWidget) - throw std::runtime_error("NoDrop needs a non-NULL widget!"); } void NoDrop::onFrame(float dt) { + if (!mWidget) + return; + MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance().getMousePosition(); if (mDrag->mIsOnDragAndDrop) @@ -113,5 +114,6 @@ void NoDrop::onFrame(float dt) void NoDrop::setAlpha(float alpha) { - mWidget->setAlpha(alpha); + if (mWidget) + mWidget->setAlpha(alpha); } From 33d27a2285b4fde84083e7229809fa878b0ee2fe Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Aug 2016 16:41:33 +0200 Subject: [PATCH 46/54] Use ref_ptr --- components/nifosg/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 5a3a7ac59..8d0c73bff 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -239,7 +239,7 @@ UVController::UVController(const UVController& copy, const osg::CopyOp& copyop) void UVController::setDefaults(osg::StateSet *stateset) { - osg::TexMat* texMat = new osg::TexMat; + osg::ref_ptr texMat (new osg::TexMat); for (std::set::const_iterator it = mTextureUnits.begin(); it != mTextureUnits.end(); ++it) stateset->setTextureAttributeAndModes(*it, texMat, osg::StateAttribute::ON); } From c65492193c0643687e985f02e8a28d738566fee3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Aug 2016 16:41:44 +0200 Subject: [PATCH 47/54] Use osg::clone --- apps/openmw/mwrender/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index 876ec285f..cb1953c61 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -22,7 +22,7 @@ void overrideTexture(const std::string &texture, Resource::ResourceSystem *resou osg::ref_ptr stateset; if (node->getStateSet()) - stateset = static_cast(node->getStateSet()->clone(osg::CopyOp::SHALLOW_COPY)); + stateset = osg::clone(node->getStateSet(), osg::CopyOp::SHALLOW_COPY); else stateset = new osg::StateSet; From 30b549124e8c01c550b2c4b520c3f15d6e40189b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Aug 2016 16:42:29 +0200 Subject: [PATCH 48/54] Fix use-after-free --- apps/openmw/mwmechanics/aiwander.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 7afbedb12..1ec9444aa 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -803,9 +803,6 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); actor.getClass().adjustPosition(actor, false); - - // may have changed cell - storage.mPopulateAvailableNodes = true; } int AiWander::OffsetToPreventOvercrowding() From 513e1da8a3c699c7bb3f3c3112ae46fe832b8b06 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Aug 2016 16:44:11 +0200 Subject: [PATCH 49/54] Remove unneeded casts --- components/nifosg/nifloader.cpp | 5 ++--- components/sceneutil/statesetupdater.cpp | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 3e67fbb3c..309dac88b 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1110,7 +1110,7 @@ namespace NifOsg geometry->setDataVariance(osg::Object::STATIC); osg::ref_ptr frameswitch = new FrameSwitch; - osg::ref_ptr geom2 = static_cast(osg::clone(geometry.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES)); + osg::ref_ptr geom2 = osg::clone(geometry.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES); frameswitch->addChild(geometry); frameswitch->addChild(geom2); @@ -1224,8 +1224,7 @@ namespace NifOsg osg::ref_ptr frameswitch = new FrameSwitch; - SceneUtil::RigGeometry* rig2 = static_cast(osg::clone(rig.get(), osg::CopyOp::DEEP_COPY_NODES| - osg::CopyOp::DEEP_COPY_DRAWABLES)); + SceneUtil::RigGeometry* rig2 = osg::clone(rig.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES); frameswitch->addChild(rig); frameswitch->addChild(rig2); diff --git a/components/sceneutil/statesetupdater.cpp b/components/sceneutil/statesetupdater.cpp index 0e325082e..31d42d342 100644 --- a/components/sceneutil/statesetupdater.cpp +++ b/components/sceneutil/statesetupdater.cpp @@ -15,7 +15,7 @@ namespace SceneUtil for (int i=0; i<2; ++i) // Using SHALLOW_COPY for StateAttributes, if users want to modify it is their responsibility to set a non-shared one first // This can be done conveniently in user implementations of the setDefaults() method { - mStateSets[i] = static_cast(osg::clone(src, osg::CopyOp::SHALLOW_COPY)); + mStateSets[i] = osg::clone(src, osg::CopyOp::SHALLOW_COPY); setDefaults(mStateSets[i]); } } @@ -65,7 +65,7 @@ namespace SceneUtil : StateSetUpdater(copy, copyop) { for (unsigned int i=0; i(osg::clone(copy.mCtrls[i].get(), copyop))); + mCtrls.push_back(osg::clone(copy.mCtrls[i].get(), copyop)); } unsigned int CompositeStateSetUpdater::getNumControllers() From 3732979eecb095a16e71180f52143d545c2c8ca5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Aug 2016 18:02:13 +0200 Subject: [PATCH 50/54] Revert "Merge pull request #993 from mrcheko/pathfinding" This reverts commit 5190275b3718a05cc20f0c9b6ccbf519a8e10379, reversing changes made to d7845012bf341878974e29c43c3cfe673d715e63. --- apps/openmw/mwmechanics/aiactivate.cpp | 22 +- apps/openmw/mwmechanics/aicombat.cpp | 364 +++++++++++++++++---- apps/openmw/mwmechanics/aicombat.hpp | 9 +- apps/openmw/mwmechanics/aicombataction.cpp | 67 +--- apps/openmw/mwmechanics/aicombataction.hpp | 13 +- apps/openmw/mwmechanics/aifollow.cpp | 35 +- apps/openmw/mwmechanics/aipackage.cpp | 199 +++-------- apps/openmw/mwmechanics/aipackage.hpp | 26 +- apps/openmw/mwmechanics/aipursue.cpp | 9 +- apps/openmw/mwmechanics/aisequence.cpp | 1 - apps/openmw/mwmechanics/aiwander.cpp | 76 +++-- apps/openmw/mwmechanics/aiwander.hpp | 4 +- apps/openmw/mwmechanics/obstacle.cpp | 5 - apps/openmw/mwmechanics/obstacle.hpp | 1 - apps/openmw/mwmechanics/pathfinding.cpp | 71 +--- apps/openmw/mwmechanics/pathfinding.hpp | 26 +- 16 files changed, 490 insertions(+), 438 deletions(-) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 745a01c8b..a79adbc8b 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -23,29 +23,33 @@ namespace MWMechanics return new AiActivate(*this); } - bool AiActivate::execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) + bool AiActivate::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, 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 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 + 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 + ) + return true; //Target doesn't exist - //Set the target destination for the actor + //Set the target desition from 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 + if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < MWBase::Environment::get().getWorld()->getMaxActivationDistance()) { //Stop when you get in activation range + 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; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index f5c64d4ab..ce66b045a 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -27,6 +27,46 @@ namespace osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength); + + float getZAngleToDir(const osg::Vec3f& dir) + { + return std::atan2(dir.x(), dir.y()); + } + + float getXAngleToDir(const osg::Vec3f& dir) + { + return -std::asin(dir.z() / dir.length()); + } + + const float REACTION_INTERVAL = 0.25f; + + const float PATHFIND_Z_REACH = 50.0f; + // distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid + const float PATHFIND_CAUTION_DIST = 500.0f; + // distance after which actor (failed previously to shortcut) will try again + const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; + + // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; + // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH + bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY) + { + if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z() - to.z()) <= PATHFIND_Z_REACH) + { + osg::Vec3f dir = to - from; + dir.z() = 0; + dir.normalize(); + float verticalOffset = 200; // instead of '200' here we want the height of the actor + osg::Vec3f _from = from + dir*offsetXY + osg::Vec3f(0,0,1) * verticalOffset; + + // cast up-down ray and find height in world space of hit + float h = _from.z() - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, osg::Vec3f(0,0,-1), verticalOffset + PATHFIND_Z_REACH + 1); + + if(std::abs(from.z() - h) <= PATHFIND_Z_REACH) + return true; + } + + return false; + } } namespace MWMechanics @@ -40,7 +80,7 @@ namespace MWMechanics float mTimerCombatMove; bool mReadyToAttack; bool mAttack; - float mAttackRange; + bool mFollowTarget; bool mCombatMove; osg::Vec3f mLastTargetPos; const MWWorld::CellStore* mCell; @@ -49,15 +89,16 @@ namespace MWMechanics float mStrength; bool mForceNoShortcut; ESM::Position mShortcutFailPos; + osg::Vec3f mLastActorPos; MWMechanics::Movement mMovement; AiCombatStorage(): mAttackCooldown(0), - mTimerReact(AI_REACTION_TIME), + mTimerReact(0), mTimerCombatMove(0), mReadyToAttack(false), mAttack(false), - mAttackRange(0), + mFollowTarget(false), mCombatMove(false), mLastTargetPos(0,0,0), mCell(NULL), @@ -66,8 +107,8 @@ namespace MWMechanics mStrength(), mForceNoShortcut(false), mShortcutFailPos(), - mMovement() - {} + mLastActorPos(0,0,0), + mMovement(){} void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack); void updateCombatMove(float duration); @@ -138,7 +179,6 @@ namespace MWMechanics * Use the Observer Pattern to co-ordinate attacks, provide intelligence on * whether the target was hit, etc. */ - bool AiCombat::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { // get or create temporary storage @@ -157,38 +197,34 @@ namespace MWMechanics || target.getClass().getCreatureStats(target).isDead()) return true; - if (storage.mCurrentAction.get()) // need to wait to init action with it's attack range - { - //Update every frame - bool is_target_reached = pathTo(actor, target.getRefData().getPosition().pos, duration, storage.mAttackRange); - if (is_target_reached) storage.mReadyToAttack = true; - } - + //Update every frame storage.updateCombatMove(duration); - if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage); + updateActorsMovement(actor, duration, storage.mMovement); storage.updateAttack(characterController); storage.mActionCooldown -= duration; - + float& timerReact = storage.mTimerReact; - if (timerReact < AI_REACTION_TIME) + if(timerReact < REACTION_INTERVAL) { timerReact += duration; + return false; } else { timerReact = 0; - attack(actor, target, storage, characterController); + return reactionTimeActions(actor, characterController, storage, target); } - - return false; } - void AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController) + bool AiCombat::reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController, + AiCombatStorage& storage, MWWorld::Ptr target) { + MWMechanics::Movement& movement = storage.mMovement; + if (isTargetMagicallyHidden(target)) { storage.stopAttack(); - return; // TODO: run away instead of doing nothing + return false; // TODO: run away instead of doing nothing } const MWWorld::CellStore*& currentCell = storage.mCell; @@ -203,9 +239,10 @@ namespace MWMechanics float& actionCooldown = storage.mActionCooldown; if (actionCooldown > 0) - return; + return false; - float &rangeAttack = storage.mAttackRange; + float rangeAttack = 0; + float rangeFollow = 0; boost::shared_ptr& currentAction = storage.mCurrentAction; if (characterController.readyToPrepareAttack()) { @@ -213,14 +250,97 @@ namespace MWMechanics actionCooldown = currentAction->getActionCooldown(); } - const ESM::Weapon *weapon = NULL; - bool isRangedCombat = false; 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 + MWBase::World* world = MWBase::Environment::get().getWorld(); + static const float fCombatDistance = world->getStore().get().find("fCombatDistance")->getFloat(); + if (actorClass.hasInventoryStore(actor)) { - rangeAttack = currentAction->getCombatRange(isRangedCombat); - // Get weapon characteristics - weapon = currentAction->getWeapon(); + //Get weapon range + MWWorld::ContainerStoreIterator weaponSlot = + MWMechanics::getActiveWeapon(actorClass.getCreatureStats(actor), actorClass.getInventoryStore(actor), &weaptype); + + if (weaptype == WeapType_HandToHand) + { + static float fHandToHandReach = + world->getStore().get().find("fHandToHandReach")->getFloat(); + weapRange = fHandToHandReach; + } + else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell && weaptype != WeapType_None) + { + // All other WeapTypes are actually weapons, so get is safe. + weapon = weaponSlot->get()->mBase; + weapRange = weapon->mData.mReach; + } + weapRange *= fCombatDistance; } + else //is creature + { + weaptype = actorClass.getCreatureStats(actor).getDrawState() == DrawState_Spell ? WeapType_Spell : WeapType_HandToHand; + weapRange = fCombatDistance; + } + + 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); + } + + + bool& readyToAttack = storage.mReadyToAttack; + // start new attack + storage.startAttackIfReady(actor, characterController, weapon, distantCombat); + + /* + * 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(); osg::Vec3f vActorPos(pos.asVec3()); @@ -228,52 +348,155 @@ namespace MWMechanics osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); - - storage.mReadyToAttack = (distToTarget <= rangeAttack); + osg::Vec3f& 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)) * REACTION_INTERVAL / 2) + isStuck = true; + + lastActorPos = vActorPos; + + // check if actor can move along z-axis + bool canMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor)) + || world->isFlying(actor); + // can't fight if attacker can't go where target is. E.g. A fish can't attack person on land. - if (distToTarget > rangeAttack + if (distToTarget >= rangeAttack && !actorClass.isNpc() && !MWMechanics::isEnvironmentCompatible(actor, target)) { // TODO: start fleeing? storage.stopAttack(); - return; + return false; } - if (storage.mReadyToAttack) + // for distant combat we should know if target is in LOS even if distToTarget < rangeAttack + bool inLOS = distantCombat ? world->getLOS(actor, target) : true; + + // (within attack dist) || (not quite attack dist while following) + if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && followTarget && !isStuck))) { - storage.startCombatMove(actorClass.isNpc(), isRangedCombat, distToTarget, rangeAttack); - // start new attack - storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); - - if (isRangedCombat) + mPathFinder.clearPath(); + //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) { - // rotate actor taking into account target movement direction and projectile speed osg::Vec3f& lastTargetPos = storage.mLastTargetPos; - vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); + vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, REACTION_INTERVAL, weaptype, + storage.mStrength); lastTargetPos = vTargetPos; - - storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); - storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir); + movement.mRotation[0] = getXAngleToDir(vAimDir); + movement.mRotation[2] = getZAngleToDir(vAimDir); } else { - storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); - storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated + movement.mRotation[0] = getXAngleToDir(vAimDir); + movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated + } + + // (not quite attack dist while following) + if (followTarget && distToTarget > rangeAttack) + { + //Close-up combat: just run up on target + storage.stopCombatMove(); + movement.mPosition[1] = 1; + } + else // (within attack dist) + { + storage.startCombatMove(actorClass.isNpc(), distantCombat, distToTarget, rangeAttack); + + 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 || (shortcutFailPos.asVec3() - 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 = REACTION_INTERVAL * speed + speed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability + preferShortcut = checkWayIsClear(vActorPos, vTargetPos, osg::Vec3f(vAimDir.x(), vAimDir.y(), 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); + } + + // don't use pathgrid when actor can move in 3 dimensions + if (canMoveByZ) + { + preferShortcut = true; + movement.mRotation[0] = getXAngleToDir(vAimDir); + } + + if(preferShortcut) + { + movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); + 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); + + // should always return a path (even if it's just go straight on target.) + assert(mPathFinder.isPathConstructed()); + } + + if (readyToAttack) + { + // to stop possible sideway moving after target moved out of attack range + storage.stopCombatMove(); + readyToAttack = false; + } + movement.mPosition[1] = 1; + } + + return false; } - void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage) + void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, MWMechanics::Movement& desiredMovement) { - // apply combat movement MWMechanics::Movement& actorMovementSettings = actor.getClass().getMovementSettings(actor); - actorMovementSettings.mPosition[0] = storage.mMovement.mPosition[0]; - actorMovementSettings.mPosition[1] = storage.mMovement.mPosition[1]; - actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2]; - - rotateActorOnAxis(actor, 2, actorMovementSettings, storage.mMovement); - rotateActorOnAxis(actor, 0, actorMovementSettings, storage.mMovement); + if (mPathFinder.isPathConstructed()) + { + const ESM::Position& pos = actor.getRefData().getPosition(); + if (mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) + { + actorMovementSettings.mPosition[1] = 0; + } + else + { + evadeObstacles(actor, duration, pos); + } + } + else + { + actorMovementSettings = desiredMovement; + rotateActorOnAxis(actor, 2, actorMovementSettings, desiredMovement); + rotateActorOnAxis(actor, 0, actorMovementSettings, desiredMovement); + } } void AiCombat::rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, @@ -291,6 +514,35 @@ namespace MWMechanics } } + bool AiCombat::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) + { + if (!mPathFinder.getPath().empty()) + { + osg::Vec3f currPathTarget(PathFinder::MakeOsgVec3(mPathFinder.getPath().back())); + osg::Vec3f newPathTarget = PathFinder::MakeOsgVec3(dest); + float dist = (newPathTarget - currPathTarget).length(); + float targetPosThreshold = (cell->isExterior()) ? 300.0f : 100.0f; + return dist > targetPosThreshold; + } + else + { + // necessarily construct a new path + return true; + } + } + + void AiCombat::buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target) + { + ESM::Pathgrid::Point newPathTarget = PathFinder::MakePathgridPoint(target.getRefData().getPosition()); + + //construct new path only if target has moved away more than on [targetPosThreshold] + if (doesPathNeedRecalc(newPathTarget, actor.getCell()->getCell())) + { + ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actor.getRefData().getPosition())); + mPathFinder.buildSyncedPath(start, newPathTarget, actor.getCell(), false); + } + } + int AiCombat::getTypeId() const { return TypeIdCombat; @@ -330,13 +582,13 @@ namespace MWMechanics mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); mCombatMove = true; } - // dodge movements (for NPCs only) + // only NPCs are smart enough to use dodge movements else if (isNpc && (!isDistantCombat || (distToTarget < rangeAttack / 2))) { //apply sideway movement (kind of dodging) with some probability if (Misc::Rng::rollClosedProbability() < 0.25) { - mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right + mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; mTimerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability(); mCombatMove = true; } @@ -399,7 +651,7 @@ namespace MWMechanics mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(), baseDelay + 0.9); } else - mAttackCooldown -= AI_REACTION_TIME; + mAttackCooldown -= REACTION_INTERVAL; } } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 4be2ac9da..1cfac5806 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -55,14 +55,19 @@ namespace MWMechanics virtual bool canCancel() const { return false; } virtual bool shouldCancelPreviousAi() const { return false; } + protected: + virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); + private: int mTargetActorId; - void attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController); + void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + bool reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController, + AiCombatStorage& storage, MWWorld::Ptr target); /// Transfer desired movement (from AiCombatStorage) to Actor - void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage); + void updateActorsMovement(const MWWorld::Ptr& actor, float duration, MWMechanics::Movement& movement); void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement); }; diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index a70410035..39c11c678 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -40,21 +40,23 @@ int getRangeTypes (const ESM::EffectList& effects) return types; } -float suggestCombatRange(int rangeTypes) +void suggestCombatRange(int rangeTypes, float& rangeAttack, float& rangeFollow) { if (rangeTypes & Touch) { - static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->getFloat(); - return fCombatDistance; + rangeAttack = 100.f; + rangeFollow = 300.f; } else if (rangeTypes & Target) { - return 1000.f; + rangeAttack = 1000.f; + rangeFollow = 0.f; } else { // For Self spells, distance doesn't matter, so back away slightly to avoid enemy hits - return 600.f; + rangeAttack = 600.f; + rangeFollow = 0.f; } } @@ -425,13 +427,11 @@ namespace MWMechanics } } - float ActionSpell::getCombatRange (bool& isRanged) const + void ActionSpell::getCombatRange(float& rangeAttack, float& rangeFollow) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); int types = getRangeTypes(spell->mEffects); - - isRanged = (types & Target); - return suggestCombatRange(types); + suggestCombatRange(types, rangeAttack, rangeFollow); } void ActionEnchantedItem::prepare(const MWWorld::Ptr &actor) @@ -441,17 +441,18 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Spell); } - float ActionEnchantedItem::getCombatRange(bool& isRanged) const + void ActionEnchantedItem::getCombatRange(float& rangeAttack, float& rangeFollow) { const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(mItem->getClass().getEnchantment(*mItem)); int types = getRangeTypes(enchantment->mEffects); - return suggestCombatRange(types); + suggestCombatRange(types, rangeAttack, rangeFollow); } - float ActionPotion::getCombatRange(bool& isRanged) const + void ActionPotion::getCombatRange(float& rangeAttack, float& rangeFollow) { // distance doesn't matter, so back away slightly to avoid enemy hits - return 600.f; + rangeAttack = 600.f; + rangeFollow = 0.f; } void ActionPotion::prepare(const MWWorld::Ptr &actor) @@ -462,8 +463,6 @@ namespace MWMechanics void ActionWeapon::prepare(const MWWorld::Ptr &actor) { - mIsNpc = actor.getClass().isNpc(); - if (actor.getClass().hasInventoryStore(actor)) { if (mWeapon.isEmpty()) @@ -483,43 +482,9 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Weapon); } - float ActionWeapon::getCombatRange(bool& isRanged) const + void ActionWeapon::getCombatRange(float& rangeAttack, float& rangeFollow) { - isRanged = false; - - static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->getFloat(); - - if (mWeapon.isEmpty()) - { - if (!mIsNpc) - { - return fCombatDistance; - } - else - { - static float fHandToHandReach = - MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->getFloat(); - - return fHandToHandReach * fCombatDistance; - } - } - - const ESM::Weapon* weapon = mWeapon.get()->mBase; - - if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) - { - isRanged = true; - return 1000.f; - } - else - return weapon->mData.mReach * fCombatDistance; - } - - const ESM::Weapon* ActionWeapon::getWeapon() const - { - if (mWeapon.isEmpty()) - return NULL; - return mWeapon.get()->mBase; + // Already done in AiCombat itself } boost::shared_ptr prepareNextAction(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index e4ce44346..bc635ceb2 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -16,9 +16,8 @@ namespace MWMechanics public: virtual ~Action() {} virtual void prepare(const MWWorld::Ptr& actor) = 0; - virtual float getCombatRange (bool& isRanged) const = 0; + virtual void getCombatRange (float& rangeAttack, float& rangeFollow) = 0; virtual float getActionCooldown() { return 0.f; } - virtual const ESM::Weapon* getWeapon() const { return NULL; }; }; class ActionSpell : public Action @@ -29,7 +28,7 @@ namespace MWMechanics /// Sets the given spell as selected on the actor's spell list. virtual void prepare(const MWWorld::Ptr& actor); - virtual float getCombatRange (bool& isRanged) const; + virtual void getCombatRange (float& rangeAttack, float& rangeFollow); }; class ActionEnchantedItem : public Action @@ -39,7 +38,7 @@ namespace MWMechanics MWWorld::ContainerStoreIterator mItem; /// Sets the given item as selected enchanted item in the actor's InventoryStore. virtual void prepare(const MWWorld::Ptr& actor); - virtual float getCombatRange (bool& isRanged) const; + virtual void getCombatRange (float& rangeAttack, float& rangeFollow); /// Since this action has no animation, apply a small cool down for using it virtual float getActionCooldown() { return 1.f; } @@ -52,7 +51,7 @@ namespace MWMechanics MWWorld::Ptr mPotion; /// Drinks the given potion. virtual void prepare(const MWWorld::Ptr& actor); - virtual float getCombatRange (bool& isRanged) const; + virtual void getCombatRange (float& rangeAttack, float& rangeFollow); /// Since this action has no animation, apply a small cool down for using it virtual float getActionCooldown() { return 1.f; } @@ -63,7 +62,6 @@ namespace MWMechanics private: MWWorld::Ptr mAmmunition; MWWorld::Ptr mWeapon; - bool mIsNpc; public: /// \a weapon may be empty for hand-to-hand combat @@ -71,8 +69,7 @@ namespace MWMechanics : mAmmunition(ammo), mWeapon(weapon) {} /// Equips the given weapon. virtual void prepare(const MWWorld::Ptr& actor); - virtual float getCombatRange (bool& isRanged) const; - virtual const ESM::Weapon* getWeapon() const; + virtual void getCombatRange (float& rangeAttack, float& rangeFollow); }; float rateSpell (const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 196498bad..1b843f850 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -137,24 +137,35 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte //Set the target destination from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if (!storage.mMoving) + float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]); + + if (storage.mMoving) //Stop when you get close + storage.mMoving = (dist > followDistance); + else { - const float threshold = 10; // to avoid constant switching between moving/stopping - followDistance += threshold; + const float threshold = 10; + storage.mMoving = (dist > followDistance + threshold); } - storage.mMoving = !pathTo(actor, dest, duration, followDistance); // Go to the destination - - if (storage.mMoving) + if(!storage.mMoving) { - //Check if you're far away - float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]); + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - 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 + // 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; } diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 1131b5e6f..34cf9b921 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -21,13 +21,6 @@ MWMechanics::AiPackage::~AiPackage() {} -MWMechanics::AiPackage::AiPackage() : - mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild - mIsShortcutting(false), - mShortcutProhibited(false), mShortcutFailPos() -{ -} - MWWorld::Ptr MWMechanics::AiPackage::getTarget() const { return MWWorld::Ptr(); @@ -58,20 +51,14 @@ bool MWMechanics::AiPackage::getRepeat() const return false; } -void MWMechanics::AiPackage::reset() -{ - // reset all members - mTimer = AI_REACTION_TIME + 1.0f; - mIsShortcutting = false; - mShortcutProhibited = false; - mShortcutFailPos = ESM::Pathgrid::Point(); +MWMechanics::AiPackage::AiPackage() : mTimer(0.26f) { //mTimer starts at .26 to force initial pathbuild - mPathFinder.clearPath(); - mObstacleCheck.clear(); } -bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance) + +bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration) { + //Update various Timers mTimer += duration; //Update timer ESM::Position pos = actor.getRefData().getPosition(); //position of the actor @@ -86,72 +73,42 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr 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) + //*********************** + /// 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) { - bool wasShortcutting = mIsShortcutting; - bool destInLOS = false; - if (getTypeId() != TypeIdWander) // prohibit shortcuts for AiWander - mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS); // try to shortcut first + const ESM::Cell *cell = actor.getCell()->getCell(); + if (doesPathNeedRecalc(dest, cell)) { //Only rebuild path if it's moved + mPathFinder.buildSyncedPath(pos.pos, dest, actor.getCell(), true); //Rebuild path, in case the target has moved + mPrevDest = dest; + } - if (!mIsShortcutting) + if(!mPathFinder.getPath().empty()) //Path has points in it { - if (wasShortcutting || doesPathNeedRecalc(dest)) // if need to rebuild path - { - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path - // 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::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 - } + 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 + //************************ + /// Checks if you aren't moving; attempts to unstick you + //************************ + if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1])) //Path finished? { - // turn to destination point - zTurn(actor, getZAngleToPoint(start, dest)); - smoothTurn(actor, getXAngleToPoint(start, dest), 0); + // Reset mTimer so that path will be built right away when a package is repeated + mTimer = 0.26f; return true; } else { - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // run to target - // 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; } @@ -160,106 +117,30 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur 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()) + if (mObstacleCheck.check(actor, duration)) { - // 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) + // first check if we're walking into a door + MWWorld::Ptr door = getNearbyDoor(actor); + if (door != MWWorld::Ptr()) // NOTE: checks interior cells only { - MWBase::Environment::get().getWorld()->activateDoor(door, 1); + 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 // any other obstacle (NPC, crate, etc.) - { - mObstacleCheck.takeEvasiveAction(movement); + else { //Not stuck, so reset things + movement.mPosition[1] = 1; //Just run forward } } -bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS) +bool MWMechanics::AiPackage::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) { - const MWWorld::Class& actorClass = actor.getClass(); - MWBase::World* world = MWBase::Environment::get().getWorld(); - - // check if actor can move along z-axis - bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) - || world->isFlying(actor); - - // don't use pathgrid when actor can move in 3 dimensions - bool isPathClear = actorCanMoveByZ; - - if (!isPathClear - && (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) - { - // check if target is clearly visible - isPathClear = !MWBase::Environment::get().getWorld()->castRay( - static_cast(startPoint.mX), static_cast(startPoint.mY), static_cast(startPoint.mZ), - static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ)); - - if (destInLOS != NULL) *destInLOS = isPathClear; - - if (!isPathClear) - return false; - - // check if an actor can move along the shortcut path - isPathClear = checkWayIsClearForActor(startPoint, endPoint, actor); - } - - if (isPathClear) // can shortcut the path - { - mPathFinder.clearPath(); - mPathFinder.addPointToPath(endPoint); - return true; - } - - return false; -} - -bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor) -{ - bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) - || MWBase::Environment::get().getWorld()->isFlying(actor); - - if (actorCanMoveByZ) - return true; - - float actorSpeed = actor.getClass().getSpeed(actor); - float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability - osg::Vec3f::value_type distToTarget = osg::Vec3f(static_cast(endPoint.mX), static_cast(endPoint.mY), 0).length(); - - float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2; - - bool isClear = checkWayIsClear(PathFinder::MakeOsgVec3(startPoint), PathFinder::MakeOsgVec3(endPoint), offsetXY); - - // update shortcut prohibit state - if (isClear) - { - if (mShortcutProhibited) - { - mShortcutProhibited = false; - mShortcutFailPos = ESM::Pathgrid::Point(); - } - } - if (!isClear) - { - if (mShortcutFailPos.mX == 0 && mShortcutFailPos.mY == 0 && mShortcutFailPos.mZ == 0) - { - mShortcutProhibited = true; - mShortcutFailPos = startPoint; - } - } - - return isClear; -} - -bool MWMechanics::AiPackage::doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest) -{ - return mPathFinder.getPath().empty() || (distance(mPathFinder.getPath().back(), newDest) > 10); + return mPathFinder.getPath().empty() || (distance(mPrevDest, dest) > 10); } bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target) diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 4feb13fe0..637d4f066 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -24,7 +24,6 @@ namespace ESM namespace MWMechanics { - const float AI_REACTION_TIME = 0.25f; class CharacterController; @@ -92,26 +91,14 @@ namespace MWMechanics /// Return true if this package should repeat. Currently only used for Wander packages. virtual bool getRepeat() const; - /// Reset pathfinding state - void reset(); - bool isTargetMagicallyHidden(const MWWorld::Ptr& target); protected: - /// Handles path building and shortcutting with obstacles avoiding + /// Causes the actor to attempt to walk to the specified location /** \return If the actor has arrived at his destination **/ - bool pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance = 0.0f); + bool pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration); - /// Check if there aren't any obstacles along the path to make shortcut possible - /// If a shortcut is possible then path will be cleared and filled with the destination point. - /// \param destInLOS If not NULL function will return ray cast check result - /// \return If can shortcut the path - bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS); - - /// Check if the way to the destination is clear, taking into account actor speed - bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor); - - virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest); + virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); @@ -121,14 +108,11 @@ namespace MWMechanics float mTimer; - osg::Vec3f mLastActorPos; - - bool mIsShortcutting; // if shortcutting at the moment - bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt - ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail + ESM::Pathgrid::Point mPrevDest; private: bool isNearInactiveCell(const ESM::Position& actorPos); + }; } diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 2b218de03..be16f49a2 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -33,6 +33,7 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte if(actor.getClass().getCreatureStats(actor).isDead()) return true; + 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() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered @@ -51,10 +52,14 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte //Set the target desition from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if (pathTo(actor, dest, duration, 100)) { - target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached + 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 diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index b03586c3b..f05725cc2 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -234,7 +234,6 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac // Put repeating noncombat AI packages on the end of the stack so they can be used again if (isActualAiPackage(packageTypeId) && (mRepeat || package->getRepeat())) { - package->reset(); mPackages.push_back(package->clone()); } // To account for the rare case where AiPackage::execute() queued another AI package diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 1ec9444aa..87fb3b9b7 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -29,6 +29,7 @@ namespace MWMechanics { static const int COUNT_BEFORE_RESET = 10; static const float DOOR_CHECK_INTERVAL = 1.5f; + static const float REACTION_INTERVAL = 0.25f; static const int GREETING_SHOULD_START = 4; //how many reaction intervals should pass before NPC can greet player static const int GREETING_SHOULD_END = 10; @@ -73,6 +74,8 @@ namespace MWMechanics unsigned short mIdleAnimation; std::vector mBadIdles; // Idle animations that when called cause errors + PathFinder mPathFinder; + // do we need to calculate allowed nodes based on mDistance bool mPopulateAvailableNodes; @@ -83,6 +86,8 @@ namespace MWMechanics ESM::Pathgrid::Point mCurrentNode; bool mTrimCurrentNode; + ObstacleCheck mObstacleCheck; + float mDoorCheckDuration; int mStuckCount; @@ -113,7 +118,7 @@ namespace MWMechanics AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), - mRepeat(repeat), mStoredInitialActorPosition(false), mIsWanderDestReady(false) + mRepeat(repeat), mStoredInitialActorPosition(false) { mIdle.resize(8, 0); init(); @@ -218,7 +223,7 @@ namespace MWMechanics float& lastReaction = storage.mReaction; lastReaction += duration; - if (AI_REACTION_TIME <= lastReaction) + if (REACTION_INTERVAL <= lastReaction) { lastReaction = 0; return reactionTimeActions(actor, storage, currentCell, cellChange, pos, duration); @@ -268,7 +273,7 @@ namespace MWMechanics } // If Wandering manually and hit an obstacle, stop - if (storage.mIsWanderingManually && mObstacleCheck.check(actor, duration, 2.0f)) { + if (storage.mIsWanderingManually && storage.mObstacleCheck.check(actor, duration, 2.0f)) { completeManualWalking(actor, storage); } @@ -295,14 +300,14 @@ namespace MWMechanics if ((wanderState == Wander_MoveNow) && storage.mCanWanderAlongPathGrid) { // Construct a new path if there isn't one - if(!mPathFinder.isPathConstructed()) + if(!storage.mPathFinder.isPathConstructed()) { if (!storage.mAllowedNodes.empty()) { setPathToAnAllowedNode(actor, storage, pos); } } - } else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { + } else if (storage.mIsWanderingManually && storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { completeManualWalking(actor, storage); } @@ -332,7 +337,7 @@ namespace MWMechanics void AiWander::returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos) { - if (!mPathFinder.isPathConstructed()) + if (!storage.mPathFinder.isPathConstructed()) { ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition)); @@ -340,11 +345,10 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); // don't take shortcuts for wandering - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false); - if (mPathFinder.isPathConstructed()) + if (storage.mPathFinder.isPathConstructed()) { - mIsWanderDestReady = true; storage.setState(Wander_Walking); } } @@ -375,14 +379,9 @@ namespace MWMechanics // Check if land creature will walk onto water or if water creature will swim onto land if ((!isWaterCreature && !destinationIsAtWater(actor, destination)) || (isWaterCreature && !destinationThroughGround(currentPositionVec3f, destination))) { - mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell()); - mPathFinder.addPointToPath(destinationPosition); - - if (mPathFinder.isPathConstructed()) - { - mIsWanderDestReady = true; - storage.setState(Wander_Walking, true); - } + storage.mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), true); + storage.mPathFinder.addPointToPath(destinationPosition); + storage.setState(Wander_Walking, true); return; } } while (--attempts); @@ -408,7 +407,7 @@ namespace MWMechanics void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) { stopWalking(actor, storage); - mObstacleCheck.clear(); + storage.mObstacleCheck.clear(); storage.setState(Wander_IdleNow); } @@ -476,7 +475,7 @@ namespace MWMechanics float duration, AiWanderStorage& storage, ESM::Position& pos) { // Are we there yet? - if (mIsWanderDestReady && pathTo(actor, mPathFinder.getPath().back(), duration, DESTINATION_TOLERANCE)) + if (storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { stopWalking(actor, storage); storage.setState(Wander_ChooseAction); @@ -518,27 +517,40 @@ namespace MWMechanics void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos) { - if (mObstacleCheck.isEvading()) + // turn towards the next point in mPath + zTurn(actor, storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); + + MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); + if (storage.mObstacleCheck.check(actor, duration)) { // first check if we're walking into a door if (proximityToDoor(actor)) // NOTE: checks interior cells only { // remove allowed points then select another random destination storage.mTrimCurrentNode = true; - trimAllowedNodes(storage.mAllowedNodes, mPathFinder); - mObstacleCheck.clear(); - mPathFinder.clearPath(); + trimAllowedNodes(storage.mAllowedNodes, storage.mPathFinder); + storage.mObstacleCheck.clear(); + storage.mPathFinder.clearPath(); storage.setState(Wander_MoveNow); } - - storage.mStuckCount++; // TODO: maybe no longer needed + else // probably walking into another NPC + { + // TODO: diagonal should have same animation as walk forward + // but doesn't seem to do that? + storage.mObstacleCheck.takeEvasiveAction(movement); + } + storage.mStuckCount++; // TODO: maybe no longer needed + } + else + { + movement.mPosition[1] = 1; } // if stuck for sufficiently long, act like current location was the destination if (storage.mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset { //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; - mObstacleCheck.clear(); + storage.mObstacleCheck.clear(); stopWalking(actor, storage); storage.setState(Wander_ChooseAction); @@ -615,7 +627,7 @@ namespace MWMechanics if (storage.mState == Wander_Walking) { stopWalking(actor, storage); - mObstacleCheck.clear(); + storage.mObstacleCheck.clear(); storage.setState(Wander_IdleNow); } @@ -655,12 +667,10 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos)); // don't take shortcuts for wandering - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false); - if (mPathFinder.isPathConstructed()) + if (storage.mPathFinder.isPathConstructed()) { - mIsWanderDestReady = true; - // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode]; storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode); @@ -716,8 +726,7 @@ namespace MWMechanics void AiWander::stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage) { - mPathFinder.clearPath(); - mIsWanderDestReady = false; + storage.mPathFinder.clearPath(); actor.getClass().getMovementSettings(actor).mPosition[1] = 0; } @@ -950,7 +959,6 @@ namespace MWMechanics , mTimeOfDay(wander->mData.mTimeOfDay) , mRepeat(wander->mData.mShouldRepeat != 0) , mStoredInitialActorPosition(wander->mStoredInitialActorPosition) - , mIsWanderDestReady(false) { if (mStoredInitialActorPosition) mInitialActorPosition = wander->mInitialActorPosition; diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index a46b3cbad..64a54a66b 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -108,10 +108,8 @@ namespace MWMechanics osg::Vec3f mInitialActorPosition; bool mStoredInitialActorPosition; - bool mIsWanderDestReady; - void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage); - + void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); // constants for converting idleSelect values into groupNames diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 5d99fe723..4394168a5 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -93,11 +93,6 @@ namespace MWMechanics return mWalkState == State_Norm; } - bool ObstacleCheck::isEvading() const - { - return mWalkState == State_Evade; - } - /* * input - actor, duration (time since last check) * output - true if evasive action needs to be taken diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index d2e1cfc2e..c8c83d68f 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -33,7 +33,6 @@ namespace MWMechanics void clear(); bool isNormalState() const; - bool isEvading() const; // Returns true if there is an obstacle and an evasive action // should be taken diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index ef60a85a4..51127de2a 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -82,42 +82,6 @@ namespace MWMechanics return sqrt(x * x + y * y + z * z); } - float getZAngleToDir(const osg::Vec3f& dir) - { - return std::atan2(dir.x(), dir.y()); - } - - float getXAngleToDir(const osg::Vec3f& dir) - { - return -std::asin(dir.z() / dir.length()); - } - - float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) - { - osg::Vec3f dir = PathFinder::MakeOsgVec3(dest) - PathFinder::MakeOsgVec3(origin); - return getZAngleToDir(dir); - } - - float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) - { - osg::Vec3f dir = PathFinder::MakeOsgVec3(dest) - PathFinder::MakeOsgVec3(origin); - return getXAngleToDir(dir); - } - - bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY) - { - osg::Vec3f dir = to - from; - dir.z() = 0; - dir.normalize(); - float verticalOffset = 200; // instead of '200' here we want the height of the actor - osg::Vec3f _from = from + dir*offsetXY + osg::Z_AXIS * verticalOffset; - - // cast up-down ray and find height of hit in world space - float h = _from.z() - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -osg::Z_AXIS, verticalOffset + PATHFIND_Z_REACH + 1); - - return (std::abs(from.z() - h) <= PATHFIND_Z_REACH); - } - PathFinder::PathFinder() : mPathgrid(NULL), mCell(NULL) @@ -168,10 +132,23 @@ namespace MWMechanics */ void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell) + const MWWorld::CellStore* cell, + bool allowShortcuts) { mPath.clear(); + if(allowShortcuts) + { + // if there's a ray cast hit, can't take a direct path + if (!MWBase::Environment::get().getWorld()->castRay( + static_cast(startPoint.mX), static_cast(startPoint.mY), static_cast(startPoint.mZ), + static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ))) + { + mPath.push_back(endPoint); + return; + } + } + if(mCell != cell || !mPathgrid) { mCell = cell; @@ -266,19 +243,6 @@ namespace MWMechanics return std::atan2(directionX, directionY); } - float PathFinder::getXAngleToNext(float x, float y, float z) const - { - // This should never happen (programmers should have an if statement checking - // isPathConstructed that prevents this call if otherwise). - if(mPath.empty()) - return 0.; - - const ESM::Pathgrid::Point &nextPoint = *mPath.begin(); - osg::Vec3f dir = MakeOsgVec3(nextPoint) - osg::Vec3f(x,y,z); - - return -std::asin(dir.z() / dir.length()); - } - bool PathFinder::checkPathCompleted(float x, float y, float tolerance) { if(mPath.empty()) @@ -300,18 +264,19 @@ namespace MWMechanics // see header for the rationale void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell) + const MWWorld::CellStore* cell, + bool allowShortcuts) { if (mPath.size() < 2) { // if path has one point, then it's the destination. // don't need to worry about bad path for this case - buildPath(startPoint, endPoint, cell); + buildPath(startPoint, endPoint, cell, allowShortcuts); } else { const ESM::Pathgrid::Point oldStart(*getPath().begin()); - buildPath(startPoint, endPoint, cell); + buildPath(startPoint, endPoint, cell, allowShortcuts); if (mPath.size() >= 2) { // if 2nd waypoint of new path == 1st waypoint of old, diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 64608979b..00a8fe0e4 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -16,20 +16,6 @@ namespace MWMechanics { float distance(const ESM::Pathgrid::Point& point, float x, float y, float); float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b); - float getZAngleToDir(const osg::Vec3f& dir); - float getXAngleToDir(const osg::Vec3f& dir); - float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest); - float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest); - - const float PATHFIND_Z_REACH = 50.0f; - //static const float sMaxSlope = 49.0f; // duplicate as in physicssystem - // distance after which actor (failed previously to shortcut) will try again - const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; - - // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; - // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH - bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY); - class PathFinder { public: @@ -53,17 +39,12 @@ namespace MWMechanics void clearPath(); - void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell); - bool checkPathCompleted(float x, float y, float tolerance = PathTolerance); ///< \Returns true if we are within \a tolerance units of the last path point. /// In radians float getZAngleToNext(float x, float y) const; - float getXAngleToNext(float x, float y, float z) const; - bool isPathConstructed() const { return !mPath.empty(); @@ -87,9 +68,9 @@ namespace MWMechanics Which results in NPC "running in a circle" back to the just passed waypoint. */ void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell); + const MWWorld::CellStore* cell, bool allowShortcuts = true); - void addPointToPath(const ESM::Pathgrid::Point &point) + void addPointToPath(ESM::Pathgrid::Point &point) { mPath.push_back(point); } @@ -149,6 +130,9 @@ namespace MWMechanics } private: + void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, + const MWWorld::CellStore* cell, bool allowShortcuts = true); + std::list mPath; const ESM::Pathgrid *mPathgrid; From 71f786ff843e4a441c8bb29eb67155ccd0de79ec Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Aug 2016 18:10:29 +0200 Subject: [PATCH 51/54] Delete the WorkQueue first Fixes a potential crash on exit. --- apps/openmw/mwrender/renderingmanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ab76b86f1..8f73036e9 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -258,6 +258,8 @@ namespace MWRender RenderingManager::~RenderingManager() { + // let background loading thread finish before we delete anything else + mWorkQueue = NULL; } MWRender::Objects& RenderingManager::getObjects() From e0e69a8d8ac70ccc3d60946ae7129a46c3209952 Mon Sep 17 00:00:00 2001 From: Roman Proskuryakov Date: Sun, 12 Jun 2016 23:33:43 +0300 Subject: [PATCH 52/54] Rewrites FindMyGUI with LibFindMacros --- cmake/FindMyGUI.cmake | 183 +++++++++--------------------------------- 1 file changed, 39 insertions(+), 144 deletions(-) diff --git a/cmake/FindMyGUI.cmake b/cmake/FindMyGUI.cmake index 0b3ac730e..473f543ba 100644 --- a/cmake/FindMyGUI.cmake +++ b/cmake/FindMyGUI.cmake @@ -1,158 +1,53 @@ # - Find MyGUI includes and library # +# This module accepts the following env variables +# MYGUI_HOME - Can be set to MyGUI install path or Windows build path +# # This module defines -# MYGUI_INCLUDE_DIRS -# MYGUI_LIBRARIES, the libraries to link against to use MYGUI. -# MYGUI_LIB_DIR, the location of the libraries -# MYGUI_FOUND, If false, do not try to use MYGUI +# MyGUI_INCLUDE_DIRS +# MyGUI_LIBRARIES, the libraries to link against to use MyGUI. +# MyGUI_FOUND, If false, do not try to use MyGUI # # Copyright © 2007, Matt Williams # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. -CMAKE_POLICY(PUSH) -include(FindPkgMacros) -include(PreprocessorUtils) -# IF (MYGUI_LIBRARIES AND MYGUI_INCLUDE_DIRS) - # SET(MYGUI_FIND_QUIETLY TRUE) -# ENDIF (MYGUI_LIBRARIES AND MYGUI_INCLUDE_DIRS) +include(LibFindMacros) -IF (WIN32) #Windows +if (MYGUI_STATIC) + set(MYGUI_STATIC_SUFFIX "Static") +else() + set(MYGUI_STATIC_SUFFIX "") +endif() - MESSAGE(STATUS "Looking for MyGUI") +libfind_pkg_detect(MyGUI_Debug MyGUI${MYGUI_STATIC_SUFFIX} MYGUI${MYGUI_STATIC_SUFFIX} + FIND_LIBRARY MyGUIEngine_d${MYGUI_STATIC_SUFFIX} + HINTS $ENV{MYGUI_HOME}/lib + PATH_SUFFIXES "" debug +) +set(MyGUI_Debug_FIND_QUIETLY TRUE) +libfind_process(MyGUI_Debug) - IF(MINGW) +libfind_pkg_detect(MyGUI MyGUI${MYGUI_STATIC_SUFFIX} MYGUI${MYGUI_STATIC_SUFFIX} + FIND_PATH MyGUI.h + HINTS $ENV{MYGUI_HOME}/include + PATH_SUFFIXES MYGUI MyGUI + FIND_LIBRARY MyGUIEngine${MYGUI_STATIC_SUFFIX} + HINTS $ENV{MYGUI_HOME}/lib + PATH_SUFFIXES "" release relwithdebinfo minsizerel +) +if (MYGUI_STATIC AND (APPLE OR ANDROID)) + # we need explicit Freetype libs only on OS X and ANDROID for static build + libfind_package(MyGUI Freetype) +endif() - FIND_PATH ( MYGUI_INCLUDE_DIRS MyGUI.h PATH_SUFFIXES MYGUI) - FIND_LIBRARY ( MYGUI_LIBRARIES_REL NAMES - libMyGUIEngine${CMAKE_SHARED_LIBRARY_SUFFIX} - HINTS - ${MYGUI_LIB_DIR} - PATH_SUFFIXES "" release relwithdebinfo minsizerel ) +libfind_version_n_header(MyGUI + NAMES MyGUI_Prerequest.h + DEFINES MYGUI_VERSION_MAJOR MYGUI_VERSION_MINOR MYGUI_VERSION_PATCH +) +libfind_process(MyGUI) - FIND_LIBRARY ( MYGUI_LIBRARIES_DBG NAMES - libMyGUIEngine_d${CMAKE_SHARED_LIBRARY_SUFFIX} - HINTS - ${MYGUI_LIB_DIR} - PATH_SUFFIXES "" debug ) - - make_library_set ( MYGUI_LIBRARIES ) - - MESSAGE ("${MYGUI_LIBRARIES}") - ENDIF(MINGW) - - SET(MYGUISDK $ENV{MYGUI_HOME}) - IF (MYGUISDK) - findpkg_begin ( "MYGUI" ) - MESSAGE(STATUS "Using MyGUI in MyGUI SDK") - STRING(REGEX REPLACE "[\\]" "/" MYGUISDK "${MYGUISDK}" ) - - find_path ( MYGUI_INCLUDE_DIRS MyGUI.h "${MYGUISDK}/MyGUIEngine/include" NO_DEFAULT_PATH ) - - SET ( MYGUI_LIB_DIR ${MYGUISDK}/lib ${MYGUISDK}/*/lib ) - - if ( MYGUI_STATIC ) - set(LIB_SUFFIX "Static") - find_package(Freetype) - endif ( MYGUI_STATIC ) - - find_library ( MYGUI_LIBRARIES_REL NAMES MyGUIEngine${LIB_SUFFIX}.lib HINTS ${MYGUI_LIB_DIR} PATH_SUFFIXES "" release relwithdebinfo minsizerel ) - find_library ( MYGUI_LIBRARIES_DBG NAMES MyGUIEngine${LIB_SUFFIX}_d.lib HINTS ${MYGUI_LIB_DIR} PATH_SUFFIXES "" debug ) - - make_library_set ( MYGUI_LIBRARIES ) - - MESSAGE ("${MYGUI_LIBRARIES}") - - #findpkg_finish ( "MYGUI" ) - ENDIF (MYGUISDK) -ELSE (WIN32) #Unix - CMAKE_MINIMUM_REQUIRED(VERSION 2.4.7 FATAL_ERROR) - FIND_PACKAGE(PkgConfig) - IF(MYGUI_STATIC) - # don't use pkgconfig on OS X, find freetype & append it's libs to resulting MYGUI_LIBRARIES - IF (NOT APPLE AND NOT ANDROID) - PKG_SEARCH_MODULE(MYGUI MYGUIStatic MyGUIStatic) - IF (MYGUI_INCLUDE_DIRS) - SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS}) - SET(MYGUI_LIB_DIR ${MYGUI_LIBDIR}) - SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") - ELSE (MYGUI_INCLUDE_DIRS) - FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI) - FIND_LIBRARY(MYGUI_LIBRARIES myguistatic PATHS /usr/lib /usr/local/lib) - SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES}) - STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") - STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") - ENDIF (MYGUI_INCLUDE_DIRS) - ELSE (NOT APPLE AND NOT ANDROID) - SET(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${MYGUI_DEPENDENCIES_DIR}) - FIND_PACKAGE(Freetype REQUIRED) - FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI) - FIND_LIBRARY(MYGUI_LIBRARIES MyGUIEngineStatic PATHS /usr/lib /usr/local/lib ${OPENMW_DEPENDENCIES_DIR}) - SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES}) - STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") - STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") - ENDIF (NOT APPLE AND NOT ANDROID) - ELSE(MYGUI_STATIC) - PKG_SEARCH_MODULE(MYGUI MYGUI MyGUI) - IF (MYGUI_INCLUDE_DIRS) - SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS}) - SET(MYGUI_LIB_DIR ${MYGUI_LIBDIR}) - SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") - ELSE (MYGUI_INCLUDE_DIRS) - FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI) - FIND_LIBRARY(MYGUI_LIBRARIES MyGUIEngine PATHS /usr/local/lib /usr/lib) - SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES}) - STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") - STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") - ENDIF (MYGUI_INCLUDE_DIRS) - ENDIF(MYGUI_STATIC) -ENDIF (WIN32) - - -#Do some preparation -IF (NOT WIN32) # This does not work on Windows for paths with spaces in them - SEPARATE_ARGUMENTS(MYGUI_INCLUDE_DIRS) - SEPARATE_ARGUMENTS(MYGUI_LIBRARIES) -ENDIF (NOT WIN32) - -SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} ${Freetype_LIBRARIES}) - -SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS} CACHE PATH "") -SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") -SET(MYGUI_LIB_DIR ${MYGUI_LIB_DIR} CACHE PATH "") - -IF (NOT APPLE OR NOT MYGUI_STATIC) # we need explicit freetype libs only on OS X for static build, for other cases just make it TRUE - SET(Freetype_LIBRARIES TRUE) -ENDIF (NOT APPLE OR NOT MYGUI_STATIC) - -IF (MYGUI_INCLUDE_DIRS AND MYGUI_LIBRARIES AND Freetype_LIBRARIES) - SET(MYGUI_FOUND TRUE) -ENDIF (MYGUI_INCLUDE_DIRS AND MYGUI_LIBRARIES AND Freetype_LIBRARIES) - -IF (MYGUI_FOUND) - MARK_AS_ADVANCED(MYGUI_LIB_DIR) - IF (NOT MYGUI_FIND_QUIETLY) - MESSAGE(STATUS " libraries : ${MYGUI_LIBRARIES} from ${MYGUI_LIB_DIR}") - MESSAGE(STATUS " includes : ${MYGUI_INCLUDE_DIRS}") - ENDIF (NOT MYGUI_FIND_QUIETLY) - - find_file(MYGUI_PREQUEST_FILE NAMES MyGUI_Prerequest.h PATHS ${MYGUI_INCLUDE_DIRS}) - file(READ ${MYGUI_PREQUEST_FILE} MYGUI_TEMP_VERSION_CONTENT) - get_preprocessor_entry(MYGUI_TEMP_VERSION_CONTENT MYGUI_VERSION_MAJOR MYGUI_VERSION_MAJOR) - get_preprocessor_entry(MYGUI_TEMP_VERSION_CONTENT MYGUI_VERSION_MINOR MYGUI_VERSION_MINOR) - get_preprocessor_entry(MYGUI_TEMP_VERSION_CONTENT MYGUI_VERSION_PATCH MYGUI_VERSION_PATCH) - set(MYGUI_VERSION "${MYGUI_VERSION_MAJOR}.${MYGUI_VERSION_MINOR}.${MYGUI_VERSION_PATCH}") - - IF (NOT MYGUI_FIND_QUIETLY) - MESSAGE(STATUS "MyGUI version: ${MYGUI_VERSION}") - ENDIF (NOT MYGUI_FIND_QUIETLY) -ENDIF (MYGUI_FOUND) - -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(MyGUI DEFAULT_MSG - MYGUI_INCLUDE_DIRS - Freetype_LIBRARIES - MYGUI_LIBRARIES) - -CMAKE_POLICY(POP) +if (MyGUI_Debug_FOUND) + set(MyGUI_LIBRARIES optimized ${MyGUI_LIBRARIES} debug ${MyGUI_Debug_LIBRARIES}) +endif() From 4cffdb67d8855be826997b0f4777c076f11383ee Mon Sep 17 00:00:00 2001 From: Roman Proskuryakov Date: Sun, 12 Jun 2016 23:34:53 +0300 Subject: [PATCH 53/54] Uses case-sensitive MyGUI_* variables --- CI/before_script.msvc.sh | 6 +----- CMakeLists.txt | 10 +++------- apps/openmw/CMakeLists.txt | 2 +- components/CMakeLists.txt | 2 +- plugins/mygui_resource_plugin/CMakeLists.txt | 1 - 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index c0ac12d04..6cd39fd0e 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -428,11 +428,7 @@ printf "MyGUI 3.2.2... " mv MyGUI-3.2.2-win$BITS MyGUI fi - MYGUI_SDK="`real_pwd`/MyGUI" - - add_cmake_opts -DMYGUISDK="$MYGUI_SDK" \ - -DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \ - -DMYGUI_PREQUEST_FILE="$MYGUI_SDK/include/MYGUI/MyGUI_Prerequest.h" + export MYGUI_HOME="`real_pwd`/MyGUI" if [ $CONFIGURATION == "Debug" ]; then SUFFIX="_d" diff --git a/CMakeLists.txt b/CMakeLists.txt index 14a1c15c8..d1ccb0c66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,11 +271,7 @@ if(QT_STATIC) endif() endif() -find_package(MyGUI REQUIRED) -if (${MYGUI_VERSION} VERSION_LESS "3.2.1") - message(FATAL_ERROR "OpenMW requires MyGUI 3.2.1 or later, please install the latest version from http://mygui.info") -endif() - +find_package(MyGUI 3.2.1 REQUIRED) find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) find_package(SDL2 REQUIRED) find_package(OpenAL REQUIRED) @@ -285,12 +281,12 @@ include_directories("." SYSTEM ${SDL2_INCLUDE_DIR} ${Boost_INCLUDE_DIR} - ${MYGUI_INCLUDE_DIRS} + ${MyGUI_INCLUDE_DIRS} ${OPENAL_INCLUDE_DIR} ${Bullet_INCLUDE_DIRS} ) -link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${MYGUI_LIB_DIR}) +link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS}) if(MYGUI_STATIC) add_definitions(-DMYGUI_STATIC) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index f7a9fea3c..18434c9dd 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -132,7 +132,7 @@ target_link_libraries(openmw ${Boost_PROGRAM_OPTIONS_LIBRARY} ${OPENAL_LIBRARY} ${FFmpeg_LIBRARIES} - ${MYGUI_LIBRARIES} + ${MyGUI_LIBRARIES} ${SDL2_LIBRARY} "osg-ffmpeg-videoplayer" "oics" diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7a07cd59e..5a5378906 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -209,7 +209,7 @@ target_link_libraries(components ${SDL2_LIBRARY} # For MyGUI platform ${GL_LIB} - ${MYGUI_LIBRARIES} + ${MyGUI_LIBRARIES} ) if (WIN32) diff --git a/plugins/mygui_resource_plugin/CMakeLists.txt b/plugins/mygui_resource_plugin/CMakeLists.txt index 72965c917..be834b17d 100644 --- a/plugins/mygui_resource_plugin/CMakeLists.txt +++ b/plugins/mygui_resource_plugin/CMakeLists.txt @@ -27,6 +27,5 @@ set_target_properties(${MYGUI_RESOURCE_PLUGIN_LIBRARY} PROPERTIES PREFIX "") target_link_libraries(${MYGUI_RESOURCE_PLUGIN_LIBRARY} ${OGRE_LIBRARIES} - ${MYGUI_LIBRARIES} components ) From 641005b317b7c1420ee868fc8f5ff5e91e47ea73 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Aug 2016 18:11:36 +0200 Subject: [PATCH 54/54] Remove Camera's children before removing the Camera Should work around OSG race condition ( http://forum.openscenegraph.org/viewtopic.php?t=16077 ) --- apps/openmw/mwrender/characterpreview.cpp | 1 + apps/openmw/mwrender/globalmap.cpp | 13 ++++++++++++- apps/openmw/mwrender/globalmap.hpp | 2 ++ apps/openmw/mwrender/localmap.cpp | 16 +++++++++------- apps/openmw/mwrender/localmap.hpp | 2 ++ apps/openmw/mwrender/water.cpp | 4 ++++ components/myguiplatform/myguirendermanager.cpp | 2 ++ 7 files changed, 32 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index c858b57b7..566a8d6ba 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -147,6 +147,7 @@ namespace MWRender CharacterPreview::~CharacterPreview () { + mCamera->removeChildren(0, mCamera->getNumChildren()); mViewer->getSceneData()->asGroup()->removeChild(mCamera); } diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 4bc24c594..d5102b153 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -107,6 +107,10 @@ namespace MWRender GlobalMap::~GlobalMap() { + for (CameraVector::iterator it = mCamerasPendingRemoval.begin(); it != mCamerasPendingRemoval.end(); ++it) + removeCamera(*it); + for (CameraVector::iterator it = mActiveCameras.begin(); it != mActiveCameras.end(); ++it) + removeCamera(*it); } void GlobalMap::render (Loading::Listener* loadingListener) @@ -507,7 +511,8 @@ namespace MWRender void GlobalMap::cleanupCameras() { for (CameraVector::iterator it = mCamerasPendingRemoval.begin(); it != mCamerasPendingRemoval.end(); ++it) - mRoot->removeChild(*it); + removeCamera(*it); + mCamerasPendingRemoval.clear(); for (ImageDestVector::iterator it = mPendingImageDest.begin(); it != mPendingImageDest.end();) @@ -524,4 +529,10 @@ namespace MWRender it = mPendingImageDest.erase(it); } } + + void GlobalMap::removeCamera(osg::Camera *cam) + { + cam->removeChildren(0, cam->getNumChildren()); + mRoot->removeChild(cam); + } } diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index 07ae7cdae..df8aa9962 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -56,6 +56,8 @@ namespace MWRender */ void cleanupCameras(); + void removeCamera(osg::Camera* cam); + /** * Mark a camera for cleanup in the next update. For internal use only. */ diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 8340ab78a..833e2717b 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -88,9 +88,9 @@ LocalMap::LocalMap(osgViewer::Viewer* viewer) LocalMap::~LocalMap() { for (CameraVector::iterator it = mActiveCameras.begin(); it != mActiveCameras.end(); ++it) - mRoot->removeChild(*it); + removeCamera(*it); for (CameraVector::iterator it = mCamerasPendingRemoval.begin(); it != mCamerasPendingRemoval.end(); ++it) - mRoot->removeChild(*it); + removeCamera(*it); } const osg::Vec2f LocalMap::rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle) @@ -275,6 +275,12 @@ osg::ref_ptr LocalMap::getFogOfWarTexture(int x, int y) return found->second.mFogOfWarTexture; } +void LocalMap::removeCamera(osg::Camera *cam) +{ + cam->removeChildren(0, cam->getNumChildren()); + mRoot->removeChild(cam); +} + void LocalMap::markForRemoval(osg::Camera *cam) { CameraVector::iterator found = std::find(mActiveCameras.begin(), mActiveCameras.end(), cam); @@ -293,11 +299,7 @@ void LocalMap::cleanupCameras() return; for (CameraVector::iterator it = mCamerasPendingRemoval.begin(); it != mCamerasPendingRemoval.end(); ++it) - { - (*it)->removeChildren(0, (*it)->getNumChildren()); - (*it)->setGraphicsContext(NULL); - mRoot->removeChild(*it); - } + removeCamera(*it); mCamerasPendingRemoval.clear(); } diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index d946f4c2b..2516c063e 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -63,6 +63,8 @@ namespace MWRender osg::ref_ptr getFogOfWarTexture (int x, int y); + void removeCamera(osg::Camera* cam); + /** * Indicates a camera has been queued for rendering and can be cleaned up in the next frame. For internal use only. */ diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 532e5cedc..f6b7a0d86 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -443,11 +443,13 @@ void Water::updateWaterMaterial() { if (mReflection) { + mReflection->removeChildren(0, mReflection->getNumChildren()); mParent->removeChild(mReflection); mReflection = NULL; } if (mRefraction) { + mRefraction->removeChildren(0, mRefraction->getNumChildren()); mParent->removeChild(mRefraction); mRefraction = NULL; } @@ -572,11 +574,13 @@ Water::~Water() if (mReflection) { + mReflection->removeChildren(0, mReflection->getNumChildren()); mParent->removeChild(mReflection); mReflection = NULL; } if (mRefraction) { + mRefraction->removeChildren(0, mRefraction->getNumChildren()); mParent->removeChild(mRefraction); mRefraction = NULL; } diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index 7654a2821..9d1f7100f 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -412,6 +412,8 @@ void RenderManager::initialise() void RenderManager::shutdown() { + mGuiRoot->removeChildren(0, mGuiRoot->getNumChildren()); + mSceneRoot->removeChild(mGuiRoot); } MyGUI::IVertexBuffer* RenderManager::createVertexBuffer()