diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 1f84c5a87..2a40c4e18 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -191,6 +191,7 @@ void CSMPrefs::State::declare() setTooltip ("Acceleration factor during drag operations while holding down shift"). setRange (0.001, 100.0); declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1); + declareDouble ("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0,1); declareCategory ("Tooltips"); declareBool ("scene", "Show Tooltips in 3D scenes", true); diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 522057097..df7283b1a 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -21,6 +22,7 @@ #include "../../model/world/universalid.hpp" #include "../../model/world/commandmacro.hpp" #include "../../model/world/cellcoordinates.hpp" +#include "../../model/prefs/state.hpp" #include #include @@ -220,7 +222,7 @@ osg::ref_ptr CSVRender::Object::makeMoveOrScaleMarker (int axis) for (int i=0; i<2; ++i) { - float length = i ? shaftLength : 0; + float length = i ? shaftLength : MarkerShaftWidth; vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, -MarkerShaftWidth/2, length, axis)); vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, MarkerShaftWidth/2, length, axis)); @@ -285,15 +287,15 @@ osg::ref_ptr CSVRender::Object::makeMoveOrScaleMarker (int axis) for (int i=0; i<8; ++i) colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f, - axis==2 ? 1.0f : 0.2f, 1.0f)); + axis==2 ? 1.0f : 0.2f, mMarkerTransparency)); for (int i=8; i<8+4+1; ++i) colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.0f, axis==1 ? 1.0f : 0.0f, - axis==2 ? 1.0f : 0.0f, 1.0f)); + axis==2 ? 1.0f : 0.0f, mMarkerTransparency)); geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); - geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + setupCommonMarkerState(geometry); osg::ref_ptr geode (new osg::Geode); geode->addDrawable (geometry); @@ -305,11 +307,11 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) { const float Pi = 3.14159265f; - const float InnerRadius = mBaseNode->getBound().radius(); + const float InnerRadius = std::max(MarkerShaftBaseLength, mBaseNode->getBound().radius()); const float OuterRadius = InnerRadius + MarkerShaftWidth; const float SegmentDistance = 100.f; - const size_t SegmentCount = std::min(64, std::max(8, (int)(OuterRadius * 2 * Pi / SegmentDistance))); + const size_t SegmentCount = std::min(64, std::max(24, (int)(OuterRadius * 2 * Pi / SegmentDistance))); const size_t VerticesPerSegment = 4; const size_t IndicesPerSegment = 24; @@ -334,6 +336,9 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) osg::ref_ptr primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, IndexCount); + // prevent some depth collision issues from overlaps + osg::Vec3f offset = getMarkerPosition(0, MarkerShaftWidth/4, 0, axis); + for (size_t i = 0; i < SegmentCount; ++i) { size_t index = i * VerticesPerSegment; @@ -344,13 +349,17 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) float outerX = OuterRadius * std::cos(i * Angle); float outerY = OuterRadius * std::sin(i * Angle); - vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis); - vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis); - vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis); - vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis); + vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis) + offset; + vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis) + offset; + vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis) + offset; + vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis) + offset; } - colors->at(0) = osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f, axis==2 ? 1.0f : 0.2f, 1.0f); + colors->at(0) = osg::Vec4f ( + axis==0 ? 1.0f : 0.2f, + axis==1 ? 1.0f : 0.2f, + axis==2 ? 1.0f : 0.2f, + mMarkerTransparency); for (size_t i = 0; i < SegmentCount; ++i) { @@ -374,7 +383,7 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) geometry->setColorArray(colors, osg::Array::BIND_OVERALL); geometry->addPrimitiveSet(primitives); - geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + setupCommonMarkerState(geometry); osg::ref_ptr geode = new osg::Geode(); geode->addDrawable (geometry); @@ -382,6 +391,15 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) return geode; } +void CSVRender::Object::setupCommonMarkerState(osg::ref_ptr geometry) +{ + osg::ref_ptr state = geometry->getOrCreateStateSet(); + state->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + state->setMode(GL_BLEND, osg::StateAttribute::ON); + + state->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); +} + osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int axis) { switch (axis) @@ -399,7 +417,7 @@ osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, const std::string& id, bool referenceable, bool forceBaseToZero) : mData (data), mBaseNode(0), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero), - mScaleOverride (1), mOverrideFlags (0), mSubMode (-1) + mScaleOverride (1), mOverrideFlags (0), mSubMode (-1), mMarkerTransparency(0.5f) { mRootNode = new osg::PositionAttitudeTransform; @@ -453,6 +471,7 @@ void CSVRender::Object::setSelected(bool selected) else mRootNode->addChild(mBaseNode); + mMarkerTransparency = CSMPrefs::get()["3D Scene Input"]["object-marker-alpha"].toDouble(); updateMarker(); } @@ -629,6 +648,12 @@ void CSVRender::Object::setScale (float scale) adjustTransform(); } +void CSVRender::Object::setMarkerTransparency(float value) +{ + mMarkerTransparency = value; + updateMarker(); +} + void CSVRender::Object::apply (CSMWorld::CommandMacro& commands) { const CSMWorld::RefCollection& collection = mData.getReferences(); diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index e14697e62..3e54093d3 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -96,6 +97,7 @@ namespace CSVRender int mOverrideFlags; osg::ref_ptr mMarker[3]; int mSubMode; + float mMarkerTransparency; /// Not implemented Object (const Object&); @@ -121,6 +123,9 @@ namespace CSVRender osg::ref_ptr makeMoveOrScaleMarker (int axis); osg::ref_ptr makeRotateMarker (int axis); + /// Sets up a stateset with properties common to all marker types. + void setupCommonMarkerState(osg::ref_ptr geometry); + osg::Vec3f getMarkerPosition (float x, float y, float z, int axis); public: @@ -179,6 +184,8 @@ namespace CSVRender /// Set override scale void setScale (float scale); + void setMarkerTransparency(float value); + /// Apply override changes via command and end edit mode void apply (CSMWorld::CommandMacro& commands); diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 325fa5f6d..a80a61a79 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -51,6 +51,7 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg , mToolTipPos (-1, -1) , mShowToolTips(false) , mToolTipDelay(0) + , mInConstructor(true) { setAcceptDrops(true); @@ -114,6 +115,8 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this); connect(abortShortcut, SIGNAL(activated()), this, SLOT(abortDrag())); + + mInConstructor = false; } CSVRender::WorldspaceWidget::~WorldspaceWidget () @@ -128,6 +131,17 @@ void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setti mDragWheelFactor = setting->toDouble(); else if (*setting=="3D Scene Input/drag-shift-factor") mDragShiftFactor = setting->toDouble(); + else if (*setting=="3D Scene Input/object-marker-alpha" && !mInConstructor) + { + float alpha = setting->toDouble(); + // getSelection is virtual, thus this can not be called from the constructor + auto selection = getSelection(Mask_Reference); + for (osg::ref_ptr tag : selection) + { + if (auto objTag = dynamic_cast(tag.get())) + objTag->mObject->setMarkerTransparency(alpha); + } + } else if (*setting=="Tooltips/scene-delay") mToolTipDelay = setting->toInt(); else if (*setting=="Tooltips/scene") diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 08b97e1be..9160ca47e 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -65,6 +65,7 @@ namespace CSVRender QPoint mToolTipPos; bool mShowToolTips; int mToolTipDelay; + bool mInConstructor; public: diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 01b1edff0..7ca0be7e3 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -201,6 +201,9 @@ namespace MWBase virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& objects) = 0; virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector &objects) = 0; + /// Check if there are actors in selected range + virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius) = 0; + ///Returns the list of actors which are siding with the given actor in fights /**ie AiFollow or AiEscort is active and the target is the actor **/ virtual std::list getActorsSidingWith(const MWWorld::Ptr& actor) = 0; diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index 92f29e3ef..c1867541b 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -8,7 +8,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" + #include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" #include "widgets.hpp" @@ -70,8 +72,14 @@ namespace MWGui updateBirths(); updateSpells(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mBirthList); - } + // Show the current birthsign by default + const std::string &signId = + MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); + + if (!signId.empty()) + setBirthId(signId); + } void BirthDialog::setBirthId(const std::string &birthId) { diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index 33daa0ad1..4d2a15c82 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -7,6 +7,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" + +#include "../mwmechanics/actorutil.hpp" + #include "../mwworld/esmstore.hpp" #include "tooltips.hpp" @@ -131,8 +134,16 @@ namespace MWGui updateClasses(); updateStats(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mClassList); - } + // Show the current class by default + MWWorld::Ptr player = MWMechanics::getPlayer(); + + const std::string &classId = + player.get()->mBase->mClass; + + if (!classId.empty()) + setClassId(classId); + } void PickClassDialog::setClassId(const std::string &classId) { diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index f8b188194..9ee01acc6 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -244,7 +244,10 @@ namespace MWInput if (mControlSwitch["playercontrols"]) { if (action == A_Use) - mPlayer->setAttackingOrSpell(currentValue != 0); + { + MWMechanics::DrawState_ state = MWBase::Environment::get().getWorld()->getPlayer().getDrawState(); + mPlayer->setAttackingOrSpell(currentValue != 0 && state != MWMechanics::DrawState_Nothing); + } else if (action == A_Jump) mAttemptJump = (currentValue == 1.0 && previousValue == 0.0); } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 7bce03bfe..db2c134b6 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1847,6 +1847,17 @@ namespace MWMechanics } } + bool Actors::isAnyObjectInRange(const osg::Vec3f& position, float radius) + { + for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) + { + if ((iter->first.getRefData().getPosition().asVec3() - position).length2() <= radius*radius) + return true; + } + + return false; + } + std::list Actors::getActorsSidingWith(const MWWorld::Ptr& actor) { std::list list; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 36f200647..0c1d9dc6c 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -125,15 +125,17 @@ namespace MWMechanics bool isRunning(const MWWorld::Ptr& ptr); bool isSneaking(const MWWorld::Ptr& ptr); - void forceStateUpdate(const MWWorld::Ptr &ptr); + void forceStateUpdate(const MWWorld::Ptr &ptr); - bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false); - void skipAnimation(const MWWorld::Ptr& ptr); - bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName); - void persistAnimationStates(); + bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false); + void skipAnimation(const MWWorld::Ptr& ptr); + bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName); + void persistAnimationStates(); void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out); + bool isAnyObjectInRange(const osg::Vec3f& position, float radius); + void cleanupSummonedCreature (CreatureStats& casterStats, int creatureActorId); ///Returns the list of actors which are siding with the given actor in fights diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 352ce6a9b..6dd98905a 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -26,6 +26,7 @@ #include "../mwrender/animation.hpp" +#include "pathgrid.hpp" #include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" @@ -440,7 +441,7 @@ namespace MWMechanics int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, localPos); for (int i = 0; i < static_cast(pathgrid->mPoints.size()); i++) { - if (i != closestPointIndex && storage.mCell->isPointConnected(closestPointIndex, i)) + if (i != closestPointIndex && getPathGridGraph(storage.mCell).isPointConnected(closestPointIndex, i)) { points.push_back(pathgrid->mPoints[static_cast(i)]); } diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index f837ad2ee..398e84448 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -14,6 +14,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "pathgrid.hpp" #include "creaturestats.hpp" #include "movement.hpp" #include "steering.hpp" @@ -100,14 +101,23 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr { bool wasShortcutting = mIsShortcutting; bool destInLOS = false; - if (getTypeId() != TypeIdWander) // prohibit shortcuts for AiWander - mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS); // try to shortcut first + + 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); + + // Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions. + if (actorCanMoveByZ || getTypeId() != TypeIdWander) + mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first if (!mIsShortcutting) { if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path { - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); mRotateOnTheRunChecks = 3; // give priority to go directly on target if there is minimal opportunity @@ -220,20 +230,23 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur } } -bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS) +const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const MWWorld::CellStore *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; + const ESM::CellId& id = cell->getCell()->getCellId(); + // static cache is OK for now, pathgrids can never change during runtime + typedef std::map > CacheMap; + static CacheMap cache; + CacheMap::iterator found = cache.find(id); + if (found == cache.end()) + { + cache.insert(std::make_pair(id, std::unique_ptr(new MWMechanics::PathgridGraph(cell)))); + } + return *cache[id].get(); +} - if (!isPathClear - && (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) +bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear) +{ + if (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST) { // check if target is clearly visible isPathClear = !MWBase::Environment::get().getWorld()->castRay( diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index acbd87908..06b4adf61 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -27,6 +27,7 @@ namespace MWMechanics const float AI_REACTION_TIME = 0.25f; class CharacterController; + class PathgridGraph; /// \brief Base class for AI packages class AiPackage @@ -110,7 +111,7 @@ namespace MWMechanics /// 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); + bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear); /// 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); @@ -119,6 +120,8 @@ namespace MWMechanics void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); + const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell); + // TODO: all this does not belong here, move into temporary storage PathFinder mPathFinder; ObstacleCheck mObstacleCheck; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 255874d88..8199170dc 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -17,6 +17,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "pathgrid.hpp" #include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" @@ -217,7 +218,7 @@ namespace MWMechanics ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination)); ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); if (mPathFinder.isPathConstructed()) storage.setState(Wander_Walking); @@ -264,9 +265,20 @@ namespace MWMechanics getAllowedNodes(actor, currentCell->getCell(), storage); } + bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || MWBase::Environment::get().getWorld()->isFlying(actor); + + if(actorCanMoveByZ && mDistance > 0) { + // Typically want to idle for a short time before the next wander + if (Misc::Rng::rollDice(100) >= 92 && storage.mState != Wander_Walking) { + wanderNearStart(actor, storage, mDistance); + } + + storage.mCanWanderAlongPathGrid = false; + } // If the package has a wander distance but no pathgrid is available, // randomly idle or wander near spawn point - if(storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) { + else if(storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) { // Typically want to idle for a short time before the next wander if (Misc::Rng::rollDice(100) >= 96) { wanderNearStart(actor, storage, mDistance); @@ -349,7 +361,7 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); // don't take shortcuts for wandering - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); if (mPathFinder.isPathConstructed()) { @@ -372,7 +384,7 @@ namespace MWMechanics do { // Determine a random location within radius of original position const float pi = 3.14159265359f; - const float wanderRadius = Misc::Rng::rollClosedProbability() * wanderDistance; + const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance; const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * pi; const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection); const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection); @@ -383,7 +395,7 @@ namespace MWMechanics // Check if land creature will walk onto water or if water creature will swim onto land if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) || (isWaterCreature && !destinationThroughGround(currentPositionVec3f, mDestination))) { - mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell()); + mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), getPathGridGraph(actor.getCell())); mPathFinder.addPointToPath(destinationPosition); if (mPathFinder.isPathConstructed()) @@ -660,13 +672,14 @@ namespace MWMechanics { unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size()); ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]); + ToWorldCoordinates(dest, storage.mCell->getCell()); // actor position is already in world coordinates ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos)); // don't take shortcuts for wandering - mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); if (mPathFinder.isPathConstructed()) { @@ -799,20 +812,80 @@ namespace MWMechanics int index = Misc::Rng::rollDice(storage.mAllowedNodes.size()); ESM::Pathgrid::Point dest = storage.mAllowedNodes[index]; - state.moveIn(new AiWanderStorage()); + ESM::Pathgrid::Point worldDest = dest; + ToWorldCoordinates(worldDest, actor.getCell()->getCell()); + + bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60); + + // add offset only if the selected pathgrid is occupied by another actor + if (isPathGridOccupied) + { + ESM::Pathgrid::PointList points; + getNeighbouringNodes(dest, actor.getCell(), points); + + // there are no neighbouring nodes, nowhere to move + if (points.empty()) + return; + + int initialSize = points.size(); + bool isOccupied = false; + // AI will try to move the NPC towards every neighboring node until suitable place will be found + for (int i = 0; i < initialSize; i++) + { + int randomIndex = Misc::Rng::rollDice(points.size()); + ESM::Pathgrid::Point connDest = points[randomIndex]; + + // add an offset towards random neighboring node + osg::Vec3f dir = PathFinder::MakeOsgVec3(connDest) - PathFinder::MakeOsgVec3(dest); + float length = dir.length(); + dir.normalize(); + + for (int j = 1; j <= 3; j++) + { + // move for 5-15% towards random neighboring node + dest = PathFinder::MakePathgridPoint(PathFinder::MakeOsgVec3(dest) + dir * (j * 5 * length / 100.f)); + worldDest = dest; + ToWorldCoordinates(worldDest, actor.getCell()->getCell()); + + isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60); + + if (!isOccupied) + break; + } + + if (!isOccupied) + break; + + // Will try an another neighboring node + points.erase(points.begin()+randomIndex); + } + + // there is no free space, nowhere to move + if (isOccupied) + return; + } + + // place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be underground. + // Adding 20 in adjustPosition() is not enough. + dest.mZ += 60; - dest.mX += OffsetToPreventOvercrowding(); - dest.mY += OffsetToPreventOvercrowding(); ToWorldCoordinates(dest, actor.getCell()->getCell()); + state.moveIn(new AiWanderStorage()); + MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); actor.getClass().adjustPosition(actor, false); } - int AiWander::OffsetToPreventOvercrowding() + void AiWander::getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points) { - return static_cast(20 * (Misc::Rng::rollProbability() * 2.0f - 1.0f)); + const ESM::Pathgrid *pathgrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*currentCell->getCell()); + + int index = PathFinder::GetClosestPoint(pathgrid, PathFinder::MakeOsgVec3(dest)); + + getPathGridGraph(currentCell).getNeighbouringPoints(index, points); } void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage) @@ -853,7 +926,7 @@ namespace MWMechanics { osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter])); if((npcPos - nodePos).length2() <= mDistance * mDistance && - cellStore->isPointConnected(closestPointIndex, counter)) + getPathGridGraph(cellStore).isPointConnected(closestPointIndex, counter)) { storage.mAllowedNodes.push_back(pathgrid->mPoints[counter]); pointIndex = counter; diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 01d889e2f..6266a7708 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -104,6 +104,8 @@ namespace MWMechanics bool mHasDestination; osg::Vec3f mDestination; + void getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points); + void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage); void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 7ccac6ec1..6e6570b00 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1338,7 +1338,16 @@ bool CharacterController::updateWeaponState() bool animPlaying; if(mAttackingOrSpell) { - mIdleState = CharState_None; + MWWorld::Ptr player = getPlayer(); + + // We should reset player's idle animation in the first-person mode. + if (mPtr == player && MWBase::Environment::get().getWorld()->isFirstPerson()) + mIdleState = CharState_None; + + // In other cases we should not break swim and sneak animations + if (mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) + mIdleState = CharState_None; + if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) { MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); @@ -1348,7 +1357,7 @@ bool CharacterController::updateWeaponState() // Unset casting flag, otherwise pressing the mouse button down would // continue casting every frame if there is no animation mAttackingOrSpell = false; - if (mPtr == getPlayer()) + if (mPtr == player) { MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); } @@ -1358,7 +1367,7 @@ bool CharacterController::updateWeaponState() // For the player, set the spell we want to cast // This has to be done at the start of the casting animation, // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) - if (mPtr == getPlayer()) + if (mPtr == player) { std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); stats.getSpells().setSelectedSpell(selectedSpell); @@ -1389,7 +1398,7 @@ bool CharacterController::updateWeaponState() MWMechanics::CastSpell cast(mPtr, NULL); cast.playSpellCastingEffects(spellid); - const ESM::Spell *spell = store.get().find(spellid); + const ESM::Spell *spell = store.get().find(spellid); const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back(); const ESM::MagicEffect *effect; @@ -1728,7 +1737,7 @@ void CharacterController::update(float duration) float speed = 0.f; updateMagicEffects(); - + if (isKnockedOut()) mTimeUntilWake -= duration; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 021444915..31be09c78 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1635,6 +1635,11 @@ namespace MWMechanics mActors.getObjectsInRange(position, radius, objects); } + bool MechanicsManager::isAnyActorInRange(const osg::Vec3f &position, float radius) + { + return mActors.isAnyObjectInRange(position, radius); + } + std::list MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor) { return mActors.getActorsSidingWith(actor); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index fafcdcd28..8908dd6ab 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -168,6 +168,9 @@ namespace MWMechanics virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& objects); virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector &objects); + /// Check if there are actors in selected range + virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius); + virtual std::list getActorsSidingWith(const MWWorld::Ptr& actor); virtual std::list getActorsFollowing(const MWWorld::Ptr& actor); virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 5c0456096..1dccb6c9a 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -5,16 +5,16 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "pathgrid.hpp" #include "coordinateconverter.hpp" namespace { // Chooses a reachable end pathgrid point. start is assumed reachable. std::pair getClosestReachablePoint(const ESM::Pathgrid* grid, - const MWWorld::CellStore *cell, + const MWMechanics::PathgridGraph *graph, const osg::Vec3f& pos, int start) { assert(grid && !grid->mPoints.empty()); @@ -31,7 +31,7 @@ namespace if (potentialDistBetween < closestDistanceReachable) { // found a closer one - if (cell->isPointConnected(start, counter)) + if (graph->isPointConnected(start, counter)) { closestDistanceReachable = potentialDistBetween; closestReachableIndex = counter; @@ -45,7 +45,7 @@ namespace } // post-condition: start and endpoint must be connected - assert(cell->isPointConnected(start, closestReachableIndex)); + assert(graph->isPointConnected(start, closestReachableIndex)); // AiWander has logic that depends on whether a path was created, deleting // allowed nodes if not. Hence a path needs to be created even if the start @@ -120,8 +120,8 @@ namespace MWMechanics } PathFinder::PathFinder() - : mPathgrid(NULL), - mCell(NULL) + : mPathgrid(NULL) + , mCell(NULL) { } @@ -169,14 +169,15 @@ namespace MWMechanics */ void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell) + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph) { mPath.clear(); + // TODO: consider removing mCell / mPathgrid in favor of mPathgridGraph if(mCell != cell || !mPathgrid) { mCell = cell; - mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); + mPathgrid = pathgridGraph.getPathgrid(); } // Refer to AiWander reseach topic on openmw forums for some background. @@ -200,7 +201,7 @@ namespace MWMechanics int startNode = GetClosestPoint(mPathgrid, startPointInLocalCoords); osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint)); - std::pair endNode = getClosestReachablePoint(mPathgrid, cell, + std::pair endNode = getClosestReachablePoint(mPathgrid, &pathgridGraph, endPointInLocalCoords, startNode); @@ -228,7 +229,7 @@ namespace MWMechanics } else { - mPath = mCell->aStarSearch(startNode, endNode.first); + mPath = pathgridGraph.aStarSearch(startNode, endNode.first); // convert supplied path to world coordinates for (std::list::iterator iter(mPath.begin()); iter != mPath.end(); ++iter) @@ -301,18 +302,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) + const MWWorld::CellStore* cell, const MWMechanics::PathgridGraph& pathgridGraph) { 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, pathgridGraph); } else { const ESM::Pathgrid::Point oldStart(*getPath().begin()); - buildPath(startPoint, endPoint, cell); + buildPath(startPoint, endPoint, cell, pathgridGraph); 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 945a7f927..ebc22f10d 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -14,6 +14,8 @@ namespace MWWorld namespace MWMechanics { + class PathgridGraph; + 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); @@ -54,7 +56,7 @@ namespace MWMechanics void clearPath(); void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell); + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); bool checkPathCompleted(float x, float y, float tolerance = PathTolerance); ///< \Returns true if we are within \a tolerance units of the last path point. @@ -89,7 +91,7 @@ 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, const PathgridGraph& pathgridGraph); void addPointToPath(const ESM::Pathgrid::Point &point) { diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index c557beadd..c0122a861 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -49,7 +49,7 @@ namespace namespace MWMechanics { - PathgridGraph::PathgridGraph() + PathgridGraph::PathgridGraph(const MWWorld::CellStore *cell) : mCell(NULL) , mPathgrid(NULL) , mIsExterior(0) @@ -58,6 +58,7 @@ namespace MWMechanics , mSCCId(0) , mSCCIndex(0) { + load(cell); } /* @@ -130,6 +131,11 @@ namespace MWMechanics return true; } + const ESM::Pathgrid *PathgridGraph::getPathgrid() const + { + return mPathgrid; + } + // v is the pathgrid point index (some call them vertices) void PathgridGraph::recursiveStrongConnect(int v) { @@ -214,6 +220,16 @@ namespace MWMechanics return (mGraph[start].componentId == mGraph[end].componentId); } + void PathgridGraph::getNeighbouringPoints(const int index, ESM::Pathgrid::PointList &nodes) const + { + for(int i = 0; i < static_cast (mGraph[index].edges.size()); i++) + { + int neighbourIndex = mGraph[index].edges[i].index; + if (neighbourIndex != index) + nodes.push_back(mPathgrid->mPoints[neighbourIndex]); + } + } + /* * NOTE: Based on buildPath2(), please check git history if interested * Should consider using a 3rd party library version (e.g. boost) diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index d90cb47cd..0c71c4561 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -20,14 +20,19 @@ namespace MWMechanics class PathgridGraph { public: - PathgridGraph(); + PathgridGraph(const MWWorld::CellStore* cell); bool load(const MWWorld::CellStore *cell); + const ESM::Pathgrid* getPathgrid() const; + // returns true if end point is strongly connected (i.e. reachable // from start point) both start and end are pathgrid point indexes bool isPointConnected(const int start, const int end) const; + // get neighbouring nodes for index node and put them to "nodes" vector + void getNeighbouringPoints(const int index, ESM::Pathgrid::PointList &nodes) const; + // the input parameters are pathgrid point indexes // the output list is in local (internal cells) or world (external // cells) coordinates diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index fa221154a..9b8c8c6a4 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -633,10 +633,6 @@ namespace MWWorld loadRefs (); mState = State_Loaded; - - // TODO: the pathgrid graph only needs to be loaded for active cells, so move this somewhere else. - // In a simple test, loading the graph for all cells in MW + expansions took 200 ms - mPathgridGraph.load(this); } } @@ -1125,16 +1121,6 @@ namespace MWWorld return !(left==right); } - bool CellStore::isPointConnected(const int start, const int end) const - { - return mPathgridGraph.isPointConnected(start, end); - } - - std::list CellStore::aStarSearch(const int start, const int end) const - { - return mPathgridGraph.aStarSearch(start, end); - } - void CellStore::setFog(ESM::FogState *fog) { mFogState.reset(fog); diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 20fba595a..33e629ea0 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -32,13 +32,12 @@ #include #include -#include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld - #include "timestamp.hpp" #include "ptr.hpp" namespace ESM { + struct Cell; struct CellState; struct FogState; struct CellId; @@ -436,10 +435,6 @@ namespace MWWorld void respawn (); ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. - bool isPointConnected(const int start, const int end) const; - - std::list aStarSearch(const int start, const int end) const; - private: /// Run through references and store IDs @@ -451,8 +446,6 @@ namespace MWWorld ///< Make case-adjustments to \a ref and insert it into the respective container. /// /// Invalid \a ref objects are silently dropped. - - MWMechanics::PathgridGraph mPathgridGraph; }; template<>