diff --git a/.travis.yml b/.travis.yml index b5c113af0..9308ccf87 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ before_install: - sudo apt-get update -qq - sudo apt-get install -qq libgtest-dev google-mock - sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev libboost-wave-dev - - sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev + - sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavresample-dev - sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev - sudo mkdir /usr/src/gtest/build - cd /usr/src/gtest/build diff --git a/CMakeLists.txt b/CMakeLists.txt index 407f616c4..2252ec9da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,10 +140,24 @@ set(OPENMW_LIBS ${OENGINE_ALL}) set(OPENMW_LIBS_HEADER) # Sound setup -set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE) -find_package(FFmpeg REQUIRED) +set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE AVRESAMPLE) +unset(FFMPEG_LIBRARIES CACHE) +find_package(FFmpeg) +if ( NOT AVCODEC_FOUND OR NOT AVFORMAT_FOUND OR NOT AVUTIL_FOUND OR NOT SWSCALE_FOUND ) + message(FATAL_ERROR "FFmpeg component required, but not found!") +endif() set(SOUND_INPUT_INCLUDES ${FFMPEG_INCLUDE_DIRS}) set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARIES}) +if( SWRESAMPLE_FOUND ) + add_definitions(-DHAVE_LIBSWRESAMPLE) + set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES} ${SWRESAMPLE_LIBRARIES}) +else() + if( AVRESAMPLE_FOUND ) + set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES} ${AVRESAMPLE_LIBRARIES}) + else() + message(FATAL_ERROR "Install either libswresample (FFmpeg) or libavresample (Libav).") + endif() +endif() # TinyXML option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF) diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index d338a4a3b..816dbc4fb 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -46,7 +46,7 @@ namespace { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", 0 }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Meshes, "Meshes", 0 }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Icons, "Icons", 0 }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Musics, "Musics", 0 }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Musics, "Music Files", 0 }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_SoundsRes, "Sound Files", 0 }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Textures, "Textures", 0 }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", 0 }, diff --git a/apps/opencs/view/doc/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp index 9cd2fad42..f18fe695a 100644 --- a/apps/opencs/view/doc/filewidget.cpp +++ b/apps/opencs/view/doc/filewidget.cpp @@ -17,7 +17,7 @@ CSVDoc::FileWidget::FileWidget (QWidget *parent) : QWidget (parent), mAddon (fal QHBoxLayout *layout = new QHBoxLayout (this); mInput = new QLineEdit (this); - mInput->setValidator (new QRegExpValidator(QRegExp("^[a-zA-Z0-9\\s]*$"))); + mInput->setValidator (new QRegExpValidator(QRegExp("^[a-zA-Z0-9_-\\s]*$"))); layout->addWidget (mInput, 1); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 28ef002d4..349b1e037 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -30,6 +30,11 @@ void CSVDoc::View::closeEvent (QCloseEvent *event) { if (!mViewManager.closeRequest (this)) event->ignore(); + else + { + // closeRequest() returns true if last document + mViewManager.removeDocAndView(mDocument); + } } void CSVDoc::View::setupFileMenu() diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 6f4217aa8..638b42d5f 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -172,7 +172,7 @@ bool CSVDoc::ViewManager::closeRequest (View *view) { std::vector::iterator iter = std::find (mViews.begin(), mViews.end(), view); - bool continueWithClose = true; + bool continueWithClose = false; if (iter!=mViews.end()) { @@ -192,6 +192,24 @@ bool CSVDoc::ViewManager::closeRequest (View *view) return continueWithClose; } +// NOTE: This method assumes that it is called only if the last document +void CSVDoc::ViewManager::removeDocAndView (CSMDoc::Document *document) +{ + for (std::vector::iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + { + // the first match should also be the only match + if((*iter)->getDocument() == document) + { + mDocumentManager.removeDocument(document); + (*iter)->deleteLater(); + mViews.erase (iter); + + updateIndices(); + return; + } + } +} + bool CSVDoc::ViewManager::notifySaveOnClose (CSVDoc::View *view) { bool result = true; @@ -210,13 +228,19 @@ bool CSVDoc::ViewManager::notifySaveOnClose (CSVDoc::View *view) bool CSVDoc::ViewManager::showModifiedDocumentMessageBox (CSVDoc::View *view) { - QMessageBox messageBox; + emit closeMessageBox(); + + QMessageBox messageBox(view); CSMDoc::Document *document = view->getDocument(); + messageBox.setWindowTitle (document->getSavePath().filename().string().c_str()); messageBox.setText ("The document has been modified."); messageBox.setInformativeText ("Do you want to save your changes?"); messageBox.setStandardButtons (QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); messageBox.setDefaultButton (QMessageBox::Save); + messageBox.setWindowModality (Qt::NonModal); + messageBox.hide(); + messageBox.show(); bool retVal = true; @@ -341,8 +365,40 @@ void CSVDoc::ViewManager::onExitWarningHandler (int state, CSMDoc::Document *doc } } +bool CSVDoc::ViewManager::removeDocument (CSVDoc::View *view) +{ + if(!notifySaveOnClose(view)) + return false; + else + { + // don't bother closing views or updating indicies, but remove from mViews + CSMDoc::Document * document = view->getDocument(); + std::vector remainingViews; + std::vector::const_iterator iter = mViews.begin(); + for (; iter!=mViews.end(); ++iter) + { + if(document == (*iter)->getDocument()) + (*iter)->setVisible(false); + else + remainingViews.push_back(*iter); + } + mDocumentManager.removeDocument(document); + mViews = remainingViews; + } + return true; +} + void CSVDoc::ViewManager::exitApplication (CSVDoc::View *view) { - if (notifySaveOnClose (view)) - QApplication::instance()->exit(); + if(!removeDocument(view)) // close the current document first + return; + + while(!mViews.empty()) // attempt to close all other documents + { + mViews.back()->activateWindow(); + mViews.back()->raise(); // raise the window to alert the user + if(!removeDocument(mViews.back())) + return; + } + // Editor exits (via a signal) when the last document is deleted } diff --git a/apps/opencs/view/doc/viewmanager.hpp b/apps/opencs/view/doc/viewmanager.hpp index 8cc92774b..753d7f0cb 100644 --- a/apps/opencs/view/doc/viewmanager.hpp +++ b/apps/opencs/view/doc/viewmanager.hpp @@ -41,6 +41,7 @@ namespace CSVDoc bool notifySaveOnClose (View *view = 0); bool showModifiedDocumentMessageBox (View *view); bool showSaveInProgressMessageBox (View *view); + bool removeDocument(View *view); public: @@ -55,6 +56,7 @@ namespace CSVDoc ///< Return number of views for \a document. bool closeRequest (View *view); + void removeDocAndView (CSMDoc::Document *document); signals: diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp index ec5647618..ef2aeb4d3 100644 --- a/apps/opencs/view/filter/recordfilterbox.cpp +++ b/apps/opencs/view/filter/recordfilterbox.cpp @@ -13,7 +13,9 @@ CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *pare layout->setContentsMargins (0, 0, 0, 0); - layout->addWidget (new QLabel ("Record Filter", this)); + QLabel *label = new QLabel("Record Filter", this); + label->setIndent(2); + layout->addWidget (label); mEdit = new EditWidget (data, this); diff --git a/apps/opencs/view/settings/dialog.cpp b/apps/opencs/view/settings/dialog.cpp index 56bc1fdfe..72a022c9d 100644 --- a/apps/opencs/view/settings/dialog.cpp +++ b/apps/opencs/view/settings/dialog.cpp @@ -114,8 +114,20 @@ void CSVSettings::Dialog::show() setViewValues(); } - QPoint screenCenter = QApplication::desktop()->screenGeometry().center(); - - move (screenCenter - geometry().center()); + QWidget *currView = QApplication::activeWindow(); + if(currView) + { + // place at the center of the window with focus + QSize size = currView->size(); + move(currView->geometry().x()+(size.width() - frameGeometry().width())/2, + currView->geometry().y()+(size.height() - frameGeometry().height())/2); + } + else + { + // something's gone wrong, place at the center of the screen + QPoint screenCenter = QApplication::desktop()->screenGeometry().center(); + move(screenCenter - QPoint(frameGeometry().width()/2, + frameGeometry().height()/2)); + } QWidget::show(); } diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 57f47033d..2c2df0fd0 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -448,12 +448,12 @@ void CSVWorld::Table::tableSizeUpdate() size = rows; } - tableSizeChanged (size, deleted, modified); + emit tableSizeChanged (size, deleted, modified); } void CSVWorld::Table::selectionSizeUpdate() { - selectionSizeChanged (selectionModel()->selectedRows().size()); + emit selectionSizeChanged (selectionModel()->selectedRows().size()); } void CSVWorld::Table::requestFocus (const std::string& id) @@ -467,6 +467,8 @@ void CSVWorld::Table::requestFocus (const std::string& id) void CSVWorld::Table::recordFilterChanged (boost::shared_ptr filter) { mProxyModel->setFilter (filter); + tableSizeUpdate(); + selectionSizeUpdate(); } void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index ff583fe5c..0ad2cbd2a 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -55,7 +55,7 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwsound - soundmanagerimp openal_output ffmpeg_decoder sound sound_decoder sound_output loudness + soundmanagerimp openal_output ffmpeg_decoder sound sound_decoder sound_output loudness libavwrapper ) add_openmw_dir (mwworld diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index c31ab2a2a..8d8a2f823 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -209,6 +209,9 @@ namespace MWClass const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); MWMechanics::CreatureStats &stats = getCreatureStats(ptr); + if (stats.getDrawState() != MWMechanics::DrawState_Weapon) + return; + // Get the weapon used (if hand-to-hand, weapon = inv.end()) MWWorld::Ptr weapon; if (ptr.getClass().hasInventoryStore(ptr)) diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 1c18dd028..67afad7a5 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -8,7 +8,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" #include #include @@ -292,6 +292,38 @@ namespace MWMechanics case ESM::MagicEffect::CurePoison: return 1001.f * numEffectsToCure(actor, ESM::MagicEffect::Poison); + case ESM::MagicEffect::DisintegrateArmor: // TODO: check if actor is wearing armor + case ESM::MagicEffect::DisintegrateWeapon: // TODO: check if actor is wearing weapon + break; + + case ESM::MagicEffect::DamageAttribute: + case ESM::MagicEffect::DrainAttribute: + if (!target.isEmpty() && target.getClass().getCreatureStats(target).getAttribute(effect.mAttribute).getModified() <= 0) + return 0.f; + { + const float attributePriorities[ESM::Attribute::Length] = { + 1.f, // Strength + 0.5, // Intelligence + 0.6, // Willpower + 0.7, // Agility + 0.5, // Speed + 0.8, // Endurance + 0.7, // Personality + 0.3 // Luck + }; + if (effect.mAttribute >= 0 && effect.mAttribute < ESM::Attribute::Length) + rating *= attributePriorities[effect.mAttribute]; + } + break; + + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::DrainSkill: + if (target.isEmpty() || !target.getClass().isNpc()) + return 0.f; + if (target.getClass().getNpcStats(target).getSkill(effect.mSkill).getModified() <= 0) + return 0.f; + break; + default: break; } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 9d1e5e52f..fe316b768 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -258,6 +258,8 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat } const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); + if (!mPtr.getClass().hasInventoryStore(mPtr)) + weap = sWeaponTypeListEnd; if(force || idle != mIdleState) { @@ -308,7 +310,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat } } - if(mJumpState == JumpState_Falling) + if(mJumpState == JumpState_InAir) { int mode = ((jump == mCurrentJump) ? 2 : 1); @@ -414,7 +416,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat speedmult = mMovementSpeed / vel; } else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) - speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed + speedmult = 1.f; // adjusted each frame else if (mMovementSpeed > 0.0f) { // The first person anims don't have any velocity to calculate a speed multiplier from. @@ -590,6 +592,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mSkipAnim(false) , mSecondsOfRunning(0) , mSecondsOfSwimming(0) + , mTurnAnimationThreshold(0) { if(!mAnimation) return; @@ -666,10 +669,10 @@ void CharacterController::updateIdleStormState() mAnimation->getInfo("idlestorm", &complete); if (complete == 0) - mAnimation->play("idlestorm", Priority_Torch, MWRender::Animation::Group_RightArm, false, + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, false, 1.0f, "start", "loop start", 0.0f, 0); else if (complete == 1) - mAnimation->play("idlestorm", Priority_Torch, MWRender::Animation::Group_RightArm, false, + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, false, 1.0f, "loop start", "loop stop", 0.0f, ~0ul); } else @@ -680,7 +683,7 @@ void CharacterController::updateIdleStormState() { if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop")) { - mAnimation->play("idlestorm", Priority_Torch, MWRender::Animation::Group_RightArm, true, + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, true, 1.0f, "loop stop", "stop", 0.0f, 0); } } @@ -690,11 +693,52 @@ void CharacterController::updateIdleStormState() } } +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(); CreatureStats &stats = cls.getCreatureStats(mPtr); + WeaponType weapType = WeapType_None; + if(stats.getDrawState() == DrawState_Weapon) + weapType = WeapType_HandToHand; + else if (stats.getDrawState() == DrawState_Spell) + weapType = WeapType_Spell; + + if (weapType != mWeaponType) + { + mWeaponType = weapType; + if (mAnimation->isPlaying(mCurrentWeapon)) + mAnimation->disable(mCurrentWeapon); + } + if(stats.getAttackingOrSpell()) { if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None) @@ -715,7 +759,18 @@ bool CharacterController::updateCreatureState() 1, "start", "stop", 0.0f, 0); mUpperBodyState = UpperCharState_StartToMinAttack; + + if (weapType == WeapType_Spell) + { + const std::string spellid = stats.getSpells().getSelectedSpell(); + if (!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) + { + castSpell(spellid); + MWBase::Environment::get().getWorld()->castSpell(mPtr); + } + } } + stats.setAttackingOrSpell(false); } @@ -854,9 +909,7 @@ bool CharacterController::updateWeaponState() if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) { - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; + castSpell(spellid); const ESM::Spell *spell = store.get().find(spellid); const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); @@ -864,15 +917,7 @@ bool CharacterController::updateWeaponState() 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); - - castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); + const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); if (mAnimation->getNode("Left Hand")) { mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Left Hand", effect->mParticle); @@ -896,12 +941,6 @@ bool CharacterController::updateWeaponState() weapSpeed, mAttackType+" start", mAttackType+" stop", 0.0f, 0); mUpperBodyState = UpperCharState_CastingSpell; - - 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); } if (inv.getSelectedEnchantItem() != inv.end()) { @@ -1237,17 +1276,8 @@ void CharacterController::update(float duration) } } - //Ogre::Vector3 vec = cls.getMovementVector(mPtr); Ogre::Vector3 vec(cls.getMovementSettings(mPtr).mPosition); - if(vec.z > 0.0f) // to avoid slow-down when jumping - { - Ogre::Vector2 vecXY = Ogre::Vector2(vec.x, vec.y); - vecXY.normalise(); - vec.x = vecXY.x; - vec.y = vecXY.y; - } - else - vec.normalise(); + vec.normalise(); if(mHitState != CharState_None && mJumpState == JumpState_None) vec = Ogre::Vector3(0.0f); @@ -1341,32 +1371,28 @@ void CharacterController::update(float duration) cls.getCreatureStats(mPtr).land(); } - forcestateupdate = (mJumpState != JumpState_Falling); - mJumpState = JumpState_Falling; + forcestateupdate = (mJumpState != JumpState_InAir); + mJumpState = JumpState_InAir; // This is a guess. All that seems to be known is that "While the player is in the - // air, fJumpMoveBase and fJumpMoveMult governs air control." Assuming Acrobatics - // plays a role, this makes the most sense. - float mult = 0.0f; - if(cls.isNpc()) - { - const NpcStats &stats = cls.getNpcStats(mPtr); - static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); - static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat(); + // air, fJumpMoveBase and fJumpMoveMult governs air control". What does fJumpMoveMult do? + static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); - mult = fJumpMoveBase + - (stats.getSkill(ESM::Skill::Acrobatics).getModified()/100.0f * - fJumpMoveMult); - } - - vec.x *= mult; - vec.y *= mult; + vec.x *= fJumpMoveBase; + vec.y *= fJumpMoveBase; vec.z = 0.0f; } else if(vec.z > 0.0f && mJumpState == JumpState_None) { // Started a jump. - vec.z = cls.getJump(mPtr); + float z = cls.getJump(mPtr); + if(vec.x == 0 && vec.y == 0) + vec = Ogre::Vector3(0.0f, 0.0f, z); + else + { + Ogre::Vector3 lat = Ogre::Vector3(vec.x, vec.y, 0.0f).normalisedCopy(); + vec = Ogre::Vector3(lat.x, lat.y, 1.0f) * z * 0.707f; + } // advance acrobatics if (mPtr.getRefData().getHandle() == "player") @@ -1382,7 +1408,7 @@ void CharacterController::update(float duration) fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); cls.getCreatureStats(mPtr).setFatigue(fatigue); } - else if(mJumpState == JumpState_Falling) + else if(mJumpState == JumpState_InAir) { forcestateupdate = true; mJumpState = JumpState_Landing; @@ -1455,6 +1481,15 @@ void CharacterController::update(float duration) } } + mTurnAnimationThreshold -= duration; + if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft) + mTurnAnimationThreshold = 0.05; + else if (movestate == CharState_None && (mMovementState == CharState_TurnRight || mMovementState == CharState_TurnLeft) + && mTurnAnimationThreshold > 0) + { + movestate = mMovementState; + } + if (onground) cls.getCreatureStats(mPtr).land(); @@ -1485,6 +1520,12 @@ void CharacterController::update(float duration) if (inJump) mMovementAnimationControlled = false; + if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) + { + if (duration > 0) + mAnimation->adjustSpeedMult(mCurrentMovement, std::min(1.5f, std::abs(rot.z) / duration / Ogre::Math::PI)); + } + if (!mSkipAnim) { rot *= Ogre::Math::RadiansToDegrees(1.0f); @@ -1503,7 +1544,9 @@ void CharacterController::update(float duration) world->queueMovement(mPtr, Ogre::Vector3(0.0f)); movement = vec; - cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = cls.getMovementSettings(mPtr).mPosition[2] = 0; + cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0; + // Can't reset jump state (mPosition[2]) here; we don't know for sure whether the PhysicSystem will actually handle it in this frame + // due to the fixed minimum timestep used for the physics update. It will be reset in PhysicSystem::move once the jump is handled. } else if(cls.getCreatureStats(mPtr).isDead()) { diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 8110c88cd..550cae5fc 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -32,6 +32,7 @@ enum Priority { Priority_Weapon, Priority_Knockdown, Priority_Torch, + Priority_Storm, Priority_Death, @@ -129,7 +130,7 @@ enum UpperBodyCharacterState { enum JumpingState { JumpState_None, - JumpState_Falling, + JumpState_InAir, JumpState_Landing }; @@ -170,6 +171,8 @@ class CharacterController float mSecondsOfSwimming; float mSecondsOfRunning; + float mTurnAnimationThreshold; // how long to continue playing turning animation after actor stopped turning + std::string mAttackType; // slash, chop or thrust void determineAttackType(); @@ -181,6 +184,8 @@ class CharacterController bool updateCreatureState(); void updateIdleStormState(); + void castSpell(const std::string& spellid); + void updateVisibility(); void playDeath(float startpoint, CharacterState death); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 81b92dcbf..e2f3ce62f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -914,6 +914,13 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo } } +void Animation::adjustSpeedMult(const std::string &groupname, float speedmult) +{ + AnimStateMap::iterator state(mStates.find(groupname)); + if(state != mStates.end()) + state->second.mSpeedMult = speedmult; +} + bool Animation::isPlaying(const std::string &groupname) const { AnimStateMap::const_iterator state(mStates.find(groupname)); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 4f53a737b..8ca3582dc 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -260,6 +260,10 @@ public: float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops); + /** Adjust the speed multiplier of an already playing animation. + */ + void adjustSpeedMult (const std::string& groupname, float speedmult); + /** Returns true if the named animation group is playing. */ bool isPlaying(const std::string &groupname) const; diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index 08d047472..aa2f24101 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -32,10 +32,15 @@ extern "C" #include #include - // From libavformat version 55.0.100 and onward the declaration of av_gettime() is removed from libavformat/avformat.h and moved - // to libavutil/time.h + #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1) + #define av_frame_alloc avcodec_alloc_frame + #endif + + // From libavformat version 55.0.100 and onward the declaration of av_gettime() is + // removed from libavformat/avformat.h and moved to libavutil/time.h // https://github.com/FFmpeg/FFmpeg/commit/06a83505992d5f49846c18507a6c3eb8a47c650e - #if AV_VERSION_INT(55, 0, 100) <= AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO) + #if AV_VERSION_INT(55, 0, 100) <= AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ + LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO) #include #endif @@ -46,30 +51,25 @@ extern "C" LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO) #include #endif -} -#ifdef _WIN32 - // Decide whether to play binkaudio. - #include - // libavcodec versions 54.10.100 (or maybe earlier) to 54.54.100 potentially crashes Windows 64bit. - // From version 54.56 or higher, there's no sound due to the encoding format changing from S16 to FLTP - // (see https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d and - // http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=3049d5b9b32845c86aa5588bb3352bdeb2edfdb2;hp=43c6b45a53a186a187f7266e4d6bd3c2620519f1), - // but does not crash (or at least no known crash). - #if (LIBAVCODEC_VERSION_MAJOR > 54) - #define FFMPEG_PLAY_BINKAUDIO + // WARNING: avcodec versions up to 54.54.100 potentially crashes on Windows 64bit. + + // From version 54.56 binkaudio encoding format changed from S16 to FLTP. See: + // https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d + // http://ffmpeg.zeranoe.com/forum/viewtopic.php?f=15&t=872 + #ifdef HAVE_LIBSWRESAMPLE + #include #else - #ifdef _WIN64 - #if ((LIBAVCODEC_VERSION_MAJOR == 54) && (LIBAVCODEC_VERSION_MINOR >= 55)) - #define FFMPEG_PLAY_BINKAUDIO - #endif - #else - #if ((LIBAVCODEC_VERSION_MAJOR == 54) && (LIBAVCODEC_VERSION_MINOR >= 10)) - #define FFMPEG_PLAY_BINKAUDIO - #endif - #endif + /* FIXME: remove this section once libswresample is available on all platforms */ + #include + #include + #define SwrContext AVAudioResampleContext + int swr_init(AVAudioResampleContext *avr); + void swr_free(AVAudioResampleContext **avr); + int swr_convert( AVAudioResampleContext *avr, uint8_t** output, int out_samples, const uint8_t** input, int in_samples); + AVAudioResampleContext * swr_alloc_set_opts( AVAudioResampleContext *avr, int64_t out_ch_layout, AVSampleFormat out_fmt, int out_rate, int64_t in_ch_layout, AVSampleFormat in_fmt, int in_rate, int o, void* l); #endif -#endif +} #define MAX_AUDIOQ_SIZE (5 * 16 * 1024) #define MAX_VIDEOQ_SIZE (5 * 256 * 1024) @@ -317,6 +317,12 @@ class MovieAudioDecoder : public MWSound::Sound_Decoder VideoState *mVideoState; AVStream *mAVStream; + SwrContext *mSwr; + enum AVSampleFormat mOutputSampleFormat; + uint8_t *mDataBuf; + uint8_t **mFrameData; + int mDataBufLen; + AutoAVPacket mPacket; AVFrame *mFrame; ssize_t mFramePos; @@ -383,6 +389,28 @@ class MovieAudioDecoder : public MWSound::Sound_Decoder if(!got_frame || frame->nb_samples <= 0) continue; + if(mSwr) + { + if(!mDataBuf || mDataBufLen < frame->nb_samples) + { + av_freep(&mDataBuf); + if(av_samples_alloc(&mDataBuf, NULL, mAVStream->codec->channels, + frame->nb_samples, mOutputSampleFormat, 0) < 0) + break; + else + mDataBufLen = frame->nb_samples; + } + + if(swr_convert(mSwr, (uint8_t**)&mDataBuf, frame->nb_samples, + (const uint8_t**)frame->extended_data, frame->nb_samples) < 0) + { + break; + } + mFrameData = &mDataBuf; + } + else + mFrameData = &frame->data[0]; + mAudioClock += (double)frame->nb_samples / (double)mAVStream->codec->sample_rate; @@ -420,7 +448,7 @@ public: MovieAudioDecoder(VideoState *is) : mVideoState(is) , mAVStream(*is->audio_st) - , mFrame(avcodec_alloc_frame()) + , mFrame(av_frame_alloc()) , mFramePos(0) , mFrameSize(0) , mAudioClock(0.0) @@ -429,10 +457,17 @@ public: /* Correct audio only if larger error than this */ , mAudioDiffThreshold(2.0 * 0.050/* 50 ms */) , mAudioDiffAvgCount(0) + , mSwr(0) + , mOutputSampleFormat(AV_SAMPLE_FMT_NONE) + , mDataBuf(NULL) + , mFrameData(NULL) + , mDataBufLen(0) { } virtual ~MovieAudioDecoder() { av_freep(&mFrame); + swr_free(&mSwr); + av_freep(&mDataBuf); } void getInfo(int *samplerate, MWSound::ChannelConfig *chans, MWSound::SampleType * type) @@ -443,10 +478,18 @@ public: *type = MWSound::SampleType_Int16; else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_FLT) *type = MWSound::SampleType_Float32; + else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_U8P) + *type = MWSound::SampleType_UInt8; + else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_S16P) + *type = MWSound::SampleType_Int16; + else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) + *type = MWSound::SampleType_Float32; else fail(std::string("Unsupported sample format: ")+ av_get_sample_fmt_name(mAVStream->codec->sample_fmt)); + int64_t ch_layout = mAVStream->codec->channel_layout; + if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_MONO) *chans = MWSound::ChannelConfig_Mono; else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_STEREO) @@ -461,9 +504,15 @@ public: { /* Unknown channel layout. Try to guess. */ if(mAVStream->codec->channels == 1) + { *chans = MWSound::ChannelConfig_Mono; + ch_layout = AV_CH_LAYOUT_MONO; + } else if(mAVStream->codec->channels == 2) + { *chans = MWSound::ChannelConfig_Stereo; + ch_layout = AV_CH_LAYOUT_STEREO; + } else { std::stringstream sstr("Unsupported raw channel count: "); @@ -480,6 +529,30 @@ public: } *samplerate = mAVStream->codec->sample_rate; + + if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_U8P) + mOutputSampleFormat = AV_SAMPLE_FMT_U8; + else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_S16P) + mOutputSampleFormat = AV_SAMPLE_FMT_S16; + else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) + mOutputSampleFormat = AV_SAMPLE_FMT_FLT; + + if(mOutputSampleFormat != AV_SAMPLE_FMT_NONE) + { + mSwr = swr_alloc_set_opts(mSwr, // SwrContext + ch_layout, // output ch layout + mOutputSampleFormat, // output sample format + mAVStream->codec->sample_rate, // output sample rate + ch_layout, // input ch layout + mAVStream->codec->sample_fmt, // input sample format + mAVStream->codec->sample_rate, // input sample rate + 0, // logging level offset + NULL); // log context + if(!mSwr) + fail(std::string("Couldn't allocate SwrContext")); + if(swr_init(mSwr) < 0) + fail(std::string("Couldn't initialize SwrContext")); + } } size_t read(char *stream, size_t len) @@ -500,7 +573,8 @@ public: } mFramePos = std::min(mFrameSize, sample_skip); - sample_skip -= mFramePos; + if(sample_skip > 0 || mFrameSize > -sample_skip) + sample_skip -= mFramePos; continue; } @@ -508,7 +582,7 @@ public: if(mFramePos >= 0) { len1 = std::min(len1, mFrameSize-mFramePos); - memcpy(stream, mFrame->data[0]+mFramePos, len1); + memcpy(stream, mFrameData[0]+mFramePos, len1); } else { @@ -519,29 +593,29 @@ public: /* add samples by copying the first sample*/ if(n == 1) - memset(stream, *mFrame->data[0], len1); + memset(stream, *mFrameData[0], len1); else if(n == 2) { - const int16_t val = *((int16_t*)mFrame->data[0]); + const int16_t val = *((int16_t*)mFrameData[0]); for(size_t nb = 0;nb < len1;nb += n) *((int16_t*)(stream+nb)) = val; } else if(n == 4) { - const int32_t val = *((int32_t*)mFrame->data[0]); + const int32_t val = *((int32_t*)mFrameData[0]); for(size_t nb = 0;nb < len1;nb += n) *((int32_t*)(stream+nb)) = val; } else if(n == 8) { - const int64_t val = *((int64_t*)mFrame->data[0]); + const int64_t val = *((int64_t*)mFrameData[0]); for(size_t nb = 0;nb < len1;nb += n) *((int64_t*)(stream+nb)) = val; } else { for(size_t nb = 0;nb < len1;nb += n) - memcpy(stream+nb, mFrame->data[0], n); + memcpy(stream+nb, mFrameData[0], n); } } @@ -768,9 +842,9 @@ void VideoState::video_thread_loop(VideoState *self) AVFrame *pFrame; double pts; - pFrame = avcodec_alloc_frame(); + pFrame = av_frame_alloc(); - self->rgbaFrame = avcodec_alloc_frame(); + self->rgbaFrame = av_frame_alloc(); avpicture_alloc((AVPicture*)self->rgbaFrame, PIX_FMT_RGBA, (*self->video_st)->codec->width, (*self->video_st)->codec->height); while(self->videoq.get(packet, self) >= 0) @@ -992,12 +1066,8 @@ void VideoState::init(const std::string& resourceName) this->external_clock_base = av_gettime(); -#if !defined(_WIN32) || defined(FFMPEG_PLAY_BINKAUDIO) if(audio_index >= 0) this->stream_open(audio_index, this->format_ctx); -#else - std::cout<<"FFmpeg sound disabled for \""+resourceName+"\""<= 0) { diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 982d0c5ff..7b6e12576 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -5,6 +5,16 @@ #include +extern "C" { +#ifndef HAVE_LIBSWRESAMPLE +/* FIXME: remove this section once libswresample is available on all platforms */ +int swr_init(AVAudioResampleContext *avr); +void swr_free(AVAudioResampleContext **avr); +int swr_convert( AVAudioResampleContext *avr, uint8_t** output, int out_samples, const uint8_t** input, int in_samples); +AVAudioResampleContext * swr_alloc_set_opts( AVAudioResampleContext *avr, int64_t out_ch_layout, AVSampleFormat out_fmt, int out_rate, int64_t in_ch_layout, AVSampleFormat in_fmt, int in_rate, int o, void* l); +#endif +} + namespace MWSound { @@ -95,6 +105,29 @@ bool FFmpeg_Decoder::getAVAudioData() memmove(mPacket.data, &mPacket.data[len], remaining); av_shrink_packet(&mPacket, remaining); } + + if(mSwr) + { + if(!mDataBuf || mDataBufLen < mFrame->nb_samples) + { + av_freep(&mDataBuf); + if(av_samples_alloc(&mDataBuf, NULL, (*mStream)->codec->channels, + mFrame->nb_samples, mOutputSampleFormat, 0) < 0) + break; + else + mDataBufLen = mFrame->nb_samples; + } + + if(swr_convert(mSwr, (uint8_t**)&mDataBuf, mFrame->nb_samples, + (const uint8_t**)mFrame->extended_data, mFrame->nb_samples) < 0) + { + break; + } + mFrameData = &mDataBuf; + } + else + mFrameData = &mFrame->data[0]; + } while(got_frame == 0 || mFrame->nb_samples == 0); mNextPts += (double)mFrame->nb_samples / (double)(*mStream)->codec->sample_rate; @@ -122,7 +155,7 @@ size_t FFmpeg_Decoder::readAVAudioData(void *data, size_t length) size_t rem = std::min(length-dec, mFrameSize-mFramePos); /* Copy the data to the app's buffer and increment */ - memcpy(data, mFrame->data[0]+mFramePos, rem); + memcpy(data, mFrameData[0]+mFramePos, rem); data = (char*)data + rem; dec += rem; mFramePos += rem; @@ -132,19 +165,6 @@ size_t FFmpeg_Decoder::readAVAudioData(void *data, size_t length) return dec; } -static AVSampleFormat ffmpegNonPlanarSampleFormat (AVSampleFormat format) -{ - switch (format) - { - case AV_SAMPLE_FMT_U8P: return AV_SAMPLE_FMT_U8; - case AV_SAMPLE_FMT_S16P: return AV_SAMPLE_FMT_S16; - case AV_SAMPLE_FMT_S32P: return AV_SAMPLE_FMT_S32; - case AV_SAMPLE_FMT_FLTP: return AV_SAMPLE_FMT_FLT; - case AV_SAMPLE_FMT_DBLP: return AV_SAMPLE_FMT_DBL; - default:return format; - } -} - void FFmpeg_Decoder::open(const std::string &fname) { close(); @@ -191,7 +211,7 @@ void FFmpeg_Decoder::open(const std::string &fname) if(!mStream) fail("No audio streams in "+fname); - (*mStream)->codec->request_sample_fmt = ffmpegNonPlanarSampleFormat ((*mStream)->codec->sample_fmt); + (*mStream)->codec->request_sample_fmt = (*mStream)->codec->sample_fmt; AVCodec *codec = avcodec_find_decoder((*mStream)->codec->codec_id); if(!codec) @@ -203,7 +223,7 @@ void FFmpeg_Decoder::open(const std::string &fname) if(avcodec_open2((*mStream)->codec, codec, NULL) < 0) fail("Failed to open audio codec " + std::string(codec->long_name)); - mFrame = avcodec_alloc_frame(); + mFrame = av_frame_alloc(); } catch(std::exception&) { @@ -228,6 +248,8 @@ void FFmpeg_Decoder::close() av_free_packet(&mPacket); av_freep(&mFrame); + swr_free(&mSwr); + av_freep(&mDataBuf); if(mFormatCtx) { @@ -268,10 +290,18 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * *type = SampleType_Int16; else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT) *type = SampleType_Float32; + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_U8P) + *type = SampleType_UInt8; + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16P) + *type = SampleType_Int16; + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) + *type = SampleType_Float32; else fail(std::string("Unsupported sample format: ")+ av_get_sample_fmt_name((*mStream)->codec->sample_fmt)); + int64_t ch_layout = (*mStream)->codec->channel_layout; + if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_MONO) *chans = ChannelConfig_Mono; else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_STEREO) @@ -286,9 +316,15 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * { /* Unknown channel layout. Try to guess. */ if((*mStream)->codec->channels == 1) + { *chans = ChannelConfig_Mono; + ch_layout = AV_CH_LAYOUT_MONO; + } else if((*mStream)->codec->channels == 2) + { *chans = ChannelConfig_Stereo; + ch_layout = AV_CH_LAYOUT_STEREO; + } else { std::stringstream sstr("Unsupported raw channel count: "); @@ -305,6 +341,31 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * } *samplerate = (*mStream)->codec->sample_rate; + + if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_U8P) + mOutputSampleFormat = AV_SAMPLE_FMT_U8; + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16P) + mOutputSampleFormat = AV_SAMPLE_FMT_S16; + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) + mOutputSampleFormat = AV_SAMPLE_FMT_FLT; + + if(mOutputSampleFormat != AV_SAMPLE_FMT_NONE) + { + mSwr = swr_alloc_set_opts(mSwr, // SwrContext + ch_layout, // output ch layout + mOutputSampleFormat, // output sample format + (*mStream)->codec->sample_rate, // output sample rate + ch_layout, // input ch layout + (*mStream)->codec->sample_fmt, // input sample format + (*mStream)->codec->sample_rate, // input sample rate + 0, // logging level offset + NULL); // log context + if(!mSwr) + fail(std::string("Couldn't allocate SwrContext")); + if(swr_init(mSwr) < 0) + fail(std::string("Couldn't initialize SwrContext")); + + } } size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) @@ -323,7 +384,7 @@ void FFmpeg_Decoder::readAll(std::vector &output) { size_t got = mFrame->nb_samples * (*mStream)->codec->channels * av_get_bytes_per_sample((*mStream)->codec->sample_fmt); - const char *inbuf = reinterpret_cast(mFrame->data[0]); + const char *inbuf = reinterpret_cast(mFrameData[0]); output.insert(output.end(), inbuf, inbuf+got); } } @@ -352,6 +413,11 @@ FFmpeg_Decoder::FFmpeg_Decoder() , mFrameSize(0) , mFramePos(0) , mNextPts(0.0) + , mSwr(0) + , mOutputSampleFormat(AV_SAMPLE_FMT_NONE) + , mDataBuf(NULL) + , mFrameData(NULL) + , mDataBufLen(0) { memset(&mPacket, 0, sizeof(mPacket)); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 8276b45c7..dc9937e81 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -18,6 +18,21 @@ extern "C" LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO) #include #endif + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1) +#define av_frame_alloc avcodec_alloc_frame +#endif + +// From version 54.56 binkaudio encoding format changed from S16 to FLTP. See: +// https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d +// http://ffmpeg.zeranoe.com/forum/viewtopic.php?f=15&t=872 +#ifdef HAVE_LIBSWRESAMPLE +#include +#else +#include +#include +#define SwrContext AVAudioResampleContext +#endif } #include @@ -40,6 +55,12 @@ namespace MWSound double mNextPts; + SwrContext *mSwr; + enum AVSampleFormat mOutputSampleFormat; + uint8_t *mDataBuf; + uint8_t **mFrameData; + int mDataBufLen; + bool getNextPacket(); Ogre::DataStreamPtr mDataStream; diff --git a/apps/openmw/mwsound/libavwrapper.cpp b/apps/openmw/mwsound/libavwrapper.cpp new file mode 100644 index 000000000..a12f412d1 --- /dev/null +++ b/apps/openmw/mwsound/libavwrapper.cpp @@ -0,0 +1,109 @@ +#ifndef HAVE_LIBSWRESAMPLE +extern "C" +{ +#define __STDC_CONSTANT_MACROS +#include + +#include +#include +// From libavutil version 52.2.0 and onward the declaration of +// AV_CH_LAYOUT_* is removed from libavcodec/avcodec.h and moved to +// libavutil/channel_layout.h +#if AV_VERSION_INT(52, 2, 0) <= AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ + LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO) + #include +#endif +#include +#include + +/* FIXME: delete this file once libswresample is available on all platforms */ + +int swr_init(AVAudioResampleContext *avr) { return 1; } + +void swr_free(AVAudioResampleContext **avr) { avresample_free(avr); } + +int swr_convert( + AVAudioResampleContext *avr, + uint8_t** output, + int out_samples, + const uint8_t** input, + int in_samples) +{ + // FIXME: potential performance hit + int out_plane_size = 0; + int in_plane_size = 0; + return avresample_convert(avr, output, out_plane_size, out_samples, + (uint8_t **)input, in_plane_size, in_samples); +} + +AVAudioResampleContext * swr_alloc_set_opts( + AVAudioResampleContext *avr, + int64_t out_ch_layout, + AVSampleFormat out_fmt, + int out_rate, + int64_t in_ch_layout, + AVSampleFormat in_fmt, + int in_rate, + int o, + void* l) +{ + avr = avresample_alloc_context(); + if(!avr) + return 0; + + int res; + res = av_opt_set_int(avr, "out_channel_layout", out_ch_layout, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_ch_layout = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "out_sample_fmt", out_fmt, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_fmt = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "out_sample_rate", out_rate, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_rate = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "in_channel_layout", in_ch_layout, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_ch_layout = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "in_sample_fmt", in_fmt, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_fmt = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "in_sample_rate", in_rate, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_rate = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "internal_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: internal_sample_fmt\n"); + return 0; + } + + + if(avresample_open(avr) < 0) + { + av_log(avr, AV_LOG_ERROR, "Error opening context\n"); + return 0; + } + else + return avr; +} + +} +#endif diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 51aae2bc3..31d52e39d 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -23,6 +23,7 @@ #include "../mwbase/environment.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/movement.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" @@ -296,21 +297,24 @@ namespace MWWorld else { velocity = Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * movement; + // not in water nor can fly, so need to deal with gravity if(!physicActor->getOnGround()) // if current OnGround status is false, must be falling or jumping { - // If falling, add part of the incoming velocity with the current inertia - // TODO: but we could be jumping up? - velocity = velocity * time + physicActor->getInertialForce(); - - // avoid getting infinite inertia in air + // If falling or jumping up, add part of the incoming velocity with the current inertia, + // but don't allow increasing inertia beyond actor's speed (except on the initial jump impulse) float actorSpeed = ptr.getClass().getSpeed(ptr); - float speedXY = Ogre::Vector2(velocity.x, velocity.y).length(); - if (speedXY > actorSpeed) + float cap = std::max(actorSpeed, Ogre::Vector2(physicActor->getInertialForce().x, physicActor->getInertialForce().y).length()); + Ogre::Vector3 newVelocity = velocity + physicActor->getInertialForce(); + if (Ogre::Vector2(newVelocity.x, newVelocity.y).squaredLength() > cap*cap) { - velocity.x *= actorSpeed / speedXY; - velocity.y *= actorSpeed / speedXY; + velocity = newVelocity; + float speedXY = Ogre::Vector2(velocity.x, velocity.y).length(); + velocity.x *= cap / speedXY; + velocity.y *= cap / speedXY; } + else + velocity = newVelocity; } inertia = velocity; // NOTE: velocity is for z axis only in this code block @@ -331,6 +335,7 @@ namespace MWWorld } } } + ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; // Now that we have the effective movement vector, apply wind forces to it if (MWBase::Environment::get().getWorld()->isInStorm()) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4a2f79b0d..425fa20e5 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -162,7 +162,6 @@ namespace MWWorld mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback); - // NOTE: We might need to reserve one more for the running game / save. mEsm.resize(contentFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); @@ -2502,9 +2501,9 @@ namespace MWWorld { const ESM::Spell* spell = getStore().get().search(selectedSpell); - // A power can be used once per 24h - if (spell->mData.mType == ESM::Spell::ST_Power) - stats.getSpells().usePower(spell->mId); + // A power can be used once per 24h + if (spell->mData.mType == ESM::Spell::ST_Power) + stats.getSpells().usePower(spell->mId); cast.cast(spell); } diff --git a/cmake/FindFFmpeg.cmake b/cmake/FindFFmpeg.cmake index a3509597b..74584bf31 100644 --- a/cmake/FindFFmpeg.cmake +++ b/cmake/FindFFmpeg.cmake @@ -14,6 +14,7 @@ # - AVUTIL # - POSTPROCESS # - SWSCALE +# - SWRESAMPLE # the following variables will be defined # _FOUND - System has # _INCLUDE_DIRS - Include directory necessary for using the headers @@ -112,6 +113,8 @@ if (NOT FFMPEG_LIBRARIES) find_component(AVUTIL libavutil avutil libavutil/avutil.h) find_component(SWSCALE libswscale swscale libswscale/swscale.h) find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) + find_component(SWRESAMPLE libswresample swresample libswresample/swresample.h) + find_component(AVRESAMPLE libavresample avresample libavresample/avresample.h) # Check if the required components were found and add their stuff to the FFMPEG_* vars. foreach (_component ${FFmpeg_FIND_COMPONENTS}) @@ -142,7 +145,7 @@ if (NOT FFMPEG_LIBRARIES) endif () # Now set the noncached _FOUND vars for the components. -foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE) +foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE SWRESAMPLE AVRESAMPLE) set_component_found(${_component}) endforeach () diff --git a/credits.txt b/credits.txt index 893c8a4e5..49e5a646e 100644 --- a/credits.txt +++ b/credits.txt @@ -30,22 +30,28 @@ darkf Dmitry Shkurskiy (endorph) Douglas Diniz (Dgdiniz) Douglas Mencken (dougmencken) +dreamer-dead Edmondo Tommasina (edmondo) Eduard Cot (trombonecot) Eli2 Emanuel Guével (potatoesmaster) +eroen Fil Krynicki (filkry) +Gašper Sedej gugus/gus Hallfaer Tuilinn Jacob Essex (Yacoby) Jannik Heller (scrawl) Jason Hooks (jhooks) +jeaye Jeffrey Haines (Jyby) Joel Graff (graffy) John Blomberg (fstp) +Jordan Ayers Jordan Milne Julien Voisin (jvoisin/ap0) Karl-Felix Glatzer (k1ll) +Kevin Poitra (PuppyKevin) Lars Söderberg (Lazaroth) lazydev Leon Saunders (emoose) @@ -61,16 +67,23 @@ Michael Hogan (Xethik) Michael Mc Donnell Michael Papageorgiou (werdanith) Michał Bień (Glorf) +Miroslav Puda (pakanek) +MiroslavR Nathan Jeffords (blunted2night) Nikolay Kasyanov (corristo) +nobrakal Nolan Poe (nopoe) Paul McElroy (Greendogo) Pieter van der Kloet (pvdk) Radu-Marius Popovici (rpopovici) +riothamus +Robert MacGregor (Ragora) +Rohit Nirmal Roman Melnik (Kromgart) Roman Proskuryakov (humbug) sandstranger Sandy Carter (bwrsandman) +Scott Howard Sebastian Wick (swick) Sergey Shambir sir_herrbatka @@ -80,6 +93,7 @@ Sylvain Thesnieres (Garvek) Thomas Luppi (Digmaster) Tom Mason (wheybags) Torben Leif Carrington (TorbenC) +Vincent Heuken Packagers: Alexander Olofsson (Ace) - Windows @@ -124,11 +138,12 @@ Sadler Artwork: Necrod - OpenMW Logo Mickey Lyle (raevol) - Wordpress Theme -Okulo - OpenMW Editor Icons +Okulo, SirHerrbatka, crysthala - OpenMW Editor Icons Inactive Contributors: Ardekantur Armin Preiml +Berulacks Carl Maxwell Diggory Hardy Dmitry Marakasov (AMDmi3)