diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 6c9fd0f52..cee015da0 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -68,12 +68,14 @@ namespace MWGui for (int i = 0; i < ESM::Skill::Length; ++i) { - mSkillValues.insert(std::pair(i, MWMechanics::SkillValue())); - mSkillWidgetMap.insert(std::pair(i, (MyGUI::TextBox*)NULL)); + mSkillValues.insert(std::make_pair(i, MWMechanics::SkillValue())); + mSkillWidgetMap.insert(std::make_pair(i, std::make_pair((MyGUI::TextBox*)NULL, (MyGUI::TextBox*)NULL))); } MyGUI::Window* t = mMainWidget->castType(); t->eventWindowChangeCoord += MyGUI::newDelegate(this, &StatsWindow::onWindowResize); + + onWindowResize(t); } void StatsWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) @@ -188,11 +190,32 @@ namespace MWGui } } + void setSkillProgress(MyGUI::Widget* w, float progress, int skillId) + { + MWWorld::Ptr player = MWMechanics::getPlayer(); + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); + + float progressRequirement = player.getClass().getNpcStats(player).getSkillProgressRequirement(skillId, + *esmStore.get().find(player.get()->mBase->mClass)); + + // This is how vanilla MW displays the progress bar (I think). Note it's slightly inaccurate, + // due to the int casting in the skill levelup logic. Also the progress label could in rare cases + // reach 100% without the skill levelling up. + // Leaving the original display logic for now, for consistency with ess-imported savegames. + int progressPercent = int(float(progress) / float(progressRequirement) * 100.f + 0.5f); + + w->setUserString("Caption_SkillProgressText", MyGUI::utility::toString(progressPercent)+"/100"); + w->setUserString("RangePosition_SkillProgress", MyGUI::utility::toString(progressPercent)); + } + void StatsWindow::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) { mSkillValues[parSkill] = value; - MyGUI::TextBox* widget = mSkillWidgetMap[(int)parSkill]; - if (widget) + std::pair widgets = mSkillWidgetMap[(int)parSkill]; + MyGUI::TextBox* valueWidget = widgets.second; + MyGUI::TextBox* nameWidget = widgets.first; + if (valueWidget && nameWidget) { int modified = value.getModified(), base = value.getBase(); std::string text = MyGUI::utility::toString(modified); @@ -202,8 +225,20 @@ namespace MWGui else if (modified < base) state = "decreased"; - widget->setCaption(text); - widget->_setWidgetState(state); + int widthBefore = valueWidget->getTextSize().width; + + valueWidget->setCaption(text); + valueWidget->_setWidgetState(state); + + int widthAfter = valueWidget->getTextSize().width; + if (widthBefore != widthAfter) + { + valueWidget->setCoord(valueWidget->getLeft() - (widthAfter-widthBefore), valueWidget->getTop(), valueWidget->getWidth() + (widthAfter-widthBefore), valueWidget->getHeight()); + nameWidget->setSize(nameWidget->getWidth() - (widthAfter-widthBefore), nameWidget->getHeight()); + } + + if (value.getBase() < 100) + setSkillProgress(valueWidget, value.getProgress(), parSkill); } } @@ -316,7 +351,7 @@ namespace MWGui coord2.top += sLineHeight; } - MyGUI::TextBox* StatsWindow::addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) + std::pair StatsWindow::addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::TextBox *skillNameWidget, *skillValueWidget; @@ -340,7 +375,7 @@ namespace MWGui coord1.top += sLineHeight; coord2.top += sLineHeight; - return skillValueWidget; + return std::make_pair(skillNameWidget, skillValueWidget); } MyGUI::Widget* StatsWindow::addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) @@ -384,19 +419,9 @@ namespace MWGui int base = stat.getBase(); int modified = stat.getModified(); - MWWorld::Ptr player = MWMechanics::getPlayer(); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); - float progressRequirement = player.getClass().getNpcStats(player).getSkillProgressRequirement(skillId, - *esmStore.get().find(player.get()->mBase->mClass)); - - // This is how vanilla MW displays the progress bar (I think). Note it's slightly inaccurate, - // due to the int casting in the skill levelup logic. Also the progress label could in rare cases - // reach 100% without the skill levelling up. - // Leaving the original display logic for now, for consistency with ess-imported savegames. - int progressPercent = int(float(stat.getProgress()) / float(progressRequirement) * 100.f + 0.5f); - const ESM::Skill* skill = esmStore.get().find(skillId); std::string icon = "icons\\k\\" + ESM::Skill::sIconNames[skillId]; @@ -409,7 +434,7 @@ namespace MWGui state = "increased"; else if (modified < base) state = "decreased"; - MyGUI::TextBox* widget = addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString(skillNameId, skillNameId), + std::pair widgets = addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString(skillNameId, skillNameId), MyGUI::utility::toString(static_cast(modified)), state, coord1, coord2); for (int i=0; i<2; ++i) @@ -420,6 +445,7 @@ namespace MWGui mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillDescription", skill->mDescription); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillAttribute", "#{sGoverningAttribute}: #{" + attr->mName + "}"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ImageTexture_SkillImage", icon); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Range_SkillProgress", "100"); if (base < 100) { mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Visible_SkillMaxed", "false"); @@ -428,9 +454,7 @@ namespace MWGui mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Visible_SkillProgressVBox", "true"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("UserData^Hidden_SkillProgressVBox", "false"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillProgressText", MyGUI::utility::toString(progressPercent)+"/100"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Range_SkillProgress", "100"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("RangePosition_SkillProgress", MyGUI::utility::toString(progressPercent)); + setSkillProgress(mSkillWidgets[mSkillWidgets.size()-1-i], stat.getProgress(), skillId); } else { mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Visible_SkillMaxed", "true"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("UserData^Hidden_SkillMaxed", "false"); @@ -440,7 +464,7 @@ namespace MWGui } } - mSkillWidgetMap[skillId] = widget; + mSkillWidgetMap[skillId] = widgets; } } diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index 2cf4ca819..1fe3cb68d 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -43,7 +43,7 @@ namespace MWGui void addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); - MyGUI::TextBox* addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); + std::pair addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); MyGUI::Widget* addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void setFactions (const FactionList& factions); @@ -62,7 +62,7 @@ namespace MWGui SkillList mMajorSkills, mMinorSkills, mMiscSkills; std::map mSkillValues; - std::map mSkillWidgetMap; + std::map > mSkillWidgetMap; std::map mFactionWidgetMap; FactionList mFactions; ///< Stores a list of factions and the current rank std::string mBirthSignId; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 566aa5f11..f01c8d219 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1487,6 +1487,10 @@ namespace MWInput ret.push_back(A_QuickKey8); ret.push_back(A_QuickKey9); ret.push_back(A_QuickKey10); + ret.push_back(A_CycleSpellLeft); + ret.push_back(A_CycleSpellRight); + ret.push_back(A_CycleWeaponLeft); + ret.push_back(A_CycleWeaponRight); return ret; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 3aa7a27e6..5316c255f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1137,6 +1137,7 @@ namespace MWMechanics timerUpdateAITargets += duration; timerUpdateHeadTrack += duration; + timerUpdateEquippedLight += duration; // Looping magic VFX update // Note: we need to do this before any of the animations are updated. diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 3d1485fb2..669c60162 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -338,22 +338,16 @@ namespace MWMechanics } } - bool update = false; - //Loop over ESM::Skill::SkillEnum for(int i = 0; i < ESM::Skill::Length; ++i) { if(stats.getSkill(i) != mWatchedSkills[i] || mWatchedStatsEmpty) { - update = true; mWatchedSkills[i] = stats.getSkill(i); winMgr->setValue((ESM::Skill::SkillEnum)i, stats.getSkill(i)); } } - if(update) - winMgr->updateSkillArea(); - winMgr->setValue("level", stats.getLevel()); mWatchedStatsEmpty = false; diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 139825c21..a15b4cba4 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -29,7 +29,8 @@ void Objects::addObject(const MWWorld::Ptr& ptr) removeObject(ptr); MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); - if(anim) mObjects.insert(std::make_pair(ptr, new CharacterController(ptr, anim))); + if (anim && anim->hasAnimSources()) + mObjects.insert(std::make_pair(ptr, new CharacterController(ptr, anim))); } void Objects::removeObject(const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 9be34495e..5167f6ef0 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -1,6 +1,6 @@ #include "actor.hpp" -#include +#include #include #include @@ -18,7 +18,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world) : mCanWaterWalk(false), mWalkingOnWater(false) - , mCollisionObject(0), mForce(0.f, 0.f, 0.f), mOnGround(false) + , mCollisionObject(0), mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) , mExternalCollisionMode(true) , mCollisionWorld(world) @@ -31,8 +31,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr // Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it) if (std::abs(mHalfExtents.x()-mHalfExtents.y())= mHalfExtents.x()) { - // Could also be btCapsuleShapeZ, but the movement solver seems to have issues with it (jumping on slopes doesn't work) - mShape.reset(new btCylinderShapeZ(toBullet(mHalfExtents))); + mShape.reset(new btCapsuleShapeZ(mHalfExtents.x(), 2*mHalfExtents.z() - 2*mHalfExtents.x())); } else mShape.reset(new btBoxShape(toBullet(mHalfExtents))); @@ -180,6 +179,11 @@ void Actor::setOnGround(bool grounded) mOnGround = grounded; } +void Actor::setOnSlope(bool slope) +{ + mOnSlope = slope; +} + bool Actor::isWalkingOnWater() const { return mWalkingOnWater; diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index a0bf5bfc0..9c0144b9d 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -124,6 +124,13 @@ namespace MWPhysics return mInternalCollisionMode && mOnGround; } + void setOnSlope(bool slope); + + bool getOnSlope() const + { + return mInternalCollisionMode && mOnSlope; + } + btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); @@ -160,6 +167,7 @@ namespace MWPhysics osg::Vec3f mForce; bool mOnGround; + bool mOnSlope; bool mInternalCollisionMode; bool mExternalCollisionMode; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2d0964f25..0ab636865 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -250,6 +250,8 @@ namespace MWPhysics } else { + actor->setOnGround(true); + // Check if we actually found a valid spawn point (use an infinitely thin ray this time). // Required for some broken door destinations in Morrowind.esm, where the spawn point // intersects with other geometry if the actor's base is taken into account @@ -266,11 +268,13 @@ namespace MWPhysics ( (toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos).length2() > 35*35 || !isWalkableSlope(tracer.mPlaneNormal))) { - actor->setOnGround(isWalkableSlope(resultCallback1.m_hitNormalWorld)); + actor->setOnSlope(isWalkableSlope(resultCallback1.m_hitNormalWorld)); return toOsg(resultCallback1.m_hitPointWorld) + osg::Vec3f(0.f, 0.f, 1.f); } - - actor->setOnGround(isWalkableSlope(tracer.mPlaneNormal)); + else + { + actor->setOnSlope(isWalkableSlope(tracer.mPlaneNormal)); + } return tracer.mEndPos; } @@ -322,12 +326,10 @@ namespace MWPhysics { velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement; - if (velocity.z() > 0.f && physicActor->getOnGround()) + if (velocity.z() > 0.f && physicActor->getOnGround() && !physicActor->getOnSlope()) inertia = velocity; - else if(!physicActor->getOnGround()) - { + else if(!physicActor->getOnGround() || physicActor->getOnSlope()) velocity = velocity + physicActor->getInertialForce(); - } } // dead actors underwater will float to the surface, if the CharacterController tells us to do so @@ -440,13 +442,14 @@ namespace MWPhysics } bool isOnGround = false; + bool isOnSlope = false; if (!(inertia.z() > 0.f) && !(newPosition.z() < swimlevel)) { osg::Vec3f from = newPosition; osg::Vec3f to = newPosition - (physicActor->getOnGround() ? osg::Vec3f(0,0,sStepSizeDown+2.f) : osg::Vec3f(0,0,2.f)); tracer.doTrace(colobj, from, to, collisionWorld); - if(tracer.mFraction < 1.0f && isWalkableSlope(tracer.mPlaneNormal) + if(tracer.mFraction < 1.0f && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != CollisionType_Actor) { const btCollisionObject* standingOn = tracer.mHitObject; @@ -460,6 +463,8 @@ namespace MWPhysics newPosition.z() = tracer.mEndPos.z() + 1.0f; isOnGround = true; + + isOnSlope = !isWalkableSlope(tracer.mPlaneNormal); } else { @@ -483,7 +488,7 @@ namespace MWPhysics } } - if(isOnGround || newPosition.z() < swimlevel || isFlying) + if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || isFlying) physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f)); else { @@ -497,6 +502,7 @@ namespace MWPhysics physicActor->setInertialForce(inertia); } physicActor->setOnGround(isOnGround); + physicActor->setOnSlope(isOnSlope); newPosition.z() -= halfExtents.z(); // remove what was added at the beginning return newPosition; @@ -989,41 +995,10 @@ namespace MWPhysics return !result.mHit; } - // physactor->getOnGround() is not a reliable indicator of whether the actor - // is on the ground (defaults to false, which means code blocks such as - // CharacterController::update() may falsely detect "falling"). - // - // Also, collisions can move z position slightly off zero, giving a false - // indication. In order to reduce false detection of jumping, small distance - // below the actor is detected and ignored. A value of 1.5 is used here, but - // something larger may be more suitable. This change should resolve Bug#1271. - // - // TODO: There might be better places to update PhysicActor::mOnGround. bool PhysicsSystem::isOnGround(const MWWorld::Ptr &actor) { Actor* physactor = getActor(actor); - if(!physactor) - return false; - if(physactor->getOnGround()) - return true; - else - { - osg::Vec3f pos(actor.getRefData().getPosition().asVec3()); - - ActorTracer tracer; - // a small distance above collision object is considered "on ground" - tracer.findGround(physactor, - pos, - pos - osg::Vec3f(0, 0, 1.5f), // trace a small amount down - mCollisionWorld); - if(tracer.mFraction < 1.0f) // collision, must be close to something below - { - physactor->setOnGround(true); - return true; - } - else - return false; - } + return physactor->getOnGround(); } bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index cdbeffa19..976d83a8f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -565,6 +565,11 @@ namespace MWRender } } + bool Animation::hasAnimSources() const + { + return !mAnimSources.empty(); + } + void Animation::clearAnimSources() { mStates.clear(); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index f765a7a40..2ea369ea7 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -341,6 +341,8 @@ public: Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); virtual ~Animation(); + bool hasAnimSources() const; + MWWorld::ConstPtr getPtr() const; /// Set active flag on the object skeleton, if one exists.