forked from mirror/openmw-tes3mp
Merge remote-tracking branch 'upstream/master' into opencs-settings
This commit is contained in:
commit
ca80a2b856
25 changed files with 616 additions and 141 deletions
|
@ -12,7 +12,7 @@ before_install:
|
||||||
- sudo apt-get update -qq
|
- sudo apt-get update -qq
|
||||||
- sudo apt-get install -qq libgtest-dev google-mock
|
- 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 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 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
|
- sudo mkdir /usr/src/gtest/build
|
||||||
- cd /usr/src/gtest/build
|
- cd /usr/src/gtest/build
|
||||||
|
|
|
@ -140,10 +140,24 @@ set(OPENMW_LIBS ${OENGINE_ALL})
|
||||||
set(OPENMW_LIBS_HEADER)
|
set(OPENMW_LIBS_HEADER)
|
||||||
|
|
||||||
# Sound setup
|
# Sound setup
|
||||||
set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE)
|
set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE AVRESAMPLE)
|
||||||
find_package(FFmpeg REQUIRED)
|
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_INCLUDES ${FFMPEG_INCLUDE_DIRS})
|
||||||
set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARIES})
|
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
|
# TinyXML
|
||||||
option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF)
|
option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF)
|
||||||
|
|
|
@ -46,7 +46,7 @@ namespace
|
||||||
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", 0 },
|
{ 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_Meshes, "Meshes", 0 },
|
||||||
{ CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Icons, "Icons", 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_SoundsRes, "Sound Files", 0 },
|
||||||
{ CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Textures, "Textures", 0 },
|
{ CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Textures, "Textures", 0 },
|
||||||
{ CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", 0 },
|
{ CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", 0 },
|
||||||
|
|
|
@ -17,7 +17,7 @@ CSVDoc::FileWidget::FileWidget (QWidget *parent) : QWidget (parent), mAddon (fal
|
||||||
QHBoxLayout *layout = new QHBoxLayout (this);
|
QHBoxLayout *layout = new QHBoxLayout (this);
|
||||||
|
|
||||||
mInput = new QLineEdit (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);
|
layout->addWidget (mInput, 1);
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,11 @@ void CSVDoc::View::closeEvent (QCloseEvent *event)
|
||||||
{
|
{
|
||||||
if (!mViewManager.closeRequest (this))
|
if (!mViewManager.closeRequest (this))
|
||||||
event->ignore();
|
event->ignore();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// closeRequest() returns true if last document
|
||||||
|
mViewManager.removeDocAndView(mDocument);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVDoc::View::setupFileMenu()
|
void CSVDoc::View::setupFileMenu()
|
||||||
|
|
|
@ -172,7 +172,7 @@ bool CSVDoc::ViewManager::closeRequest (View *view)
|
||||||
{
|
{
|
||||||
std::vector<View *>::iterator iter = std::find (mViews.begin(), mViews.end(), view);
|
std::vector<View *>::iterator iter = std::find (mViews.begin(), mViews.end(), view);
|
||||||
|
|
||||||
bool continueWithClose = true;
|
bool continueWithClose = false;
|
||||||
|
|
||||||
if (iter!=mViews.end())
|
if (iter!=mViews.end())
|
||||||
{
|
{
|
||||||
|
@ -192,6 +192,24 @@ bool CSVDoc::ViewManager::closeRequest (View *view)
|
||||||
return continueWithClose;
|
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<View *>::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 CSVDoc::ViewManager::notifySaveOnClose (CSVDoc::View *view)
|
||||||
{
|
{
|
||||||
bool result = true;
|
bool result = true;
|
||||||
|
@ -210,13 +228,19 @@ bool CSVDoc::ViewManager::notifySaveOnClose (CSVDoc::View *view)
|
||||||
|
|
||||||
bool CSVDoc::ViewManager::showModifiedDocumentMessageBox (CSVDoc::View *view)
|
bool CSVDoc::ViewManager::showModifiedDocumentMessageBox (CSVDoc::View *view)
|
||||||
{
|
{
|
||||||
QMessageBox messageBox;
|
emit closeMessageBox();
|
||||||
|
|
||||||
|
QMessageBox messageBox(view);
|
||||||
CSMDoc::Document *document = view->getDocument();
|
CSMDoc::Document *document = view->getDocument();
|
||||||
|
|
||||||
|
messageBox.setWindowTitle (document->getSavePath().filename().string().c_str());
|
||||||
messageBox.setText ("The document has been modified.");
|
messageBox.setText ("The document has been modified.");
|
||||||
messageBox.setInformativeText ("Do you want to save your changes?");
|
messageBox.setInformativeText ("Do you want to save your changes?");
|
||||||
messageBox.setStandardButtons (QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
|
messageBox.setStandardButtons (QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
|
||||||
messageBox.setDefaultButton (QMessageBox::Save);
|
messageBox.setDefaultButton (QMessageBox::Save);
|
||||||
|
messageBox.setWindowModality (Qt::NonModal);
|
||||||
|
messageBox.hide();
|
||||||
|
messageBox.show();
|
||||||
|
|
||||||
bool retVal = true;
|
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<View *> remainingViews;
|
||||||
|
std::vector<View *>::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)
|
void CSVDoc::ViewManager::exitApplication (CSVDoc::View *view)
|
||||||
{
|
{
|
||||||
if (notifySaveOnClose (view))
|
if(!removeDocument(view)) // close the current document first
|
||||||
QApplication::instance()->exit();
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ namespace CSVDoc
|
||||||
bool notifySaveOnClose (View *view = 0);
|
bool notifySaveOnClose (View *view = 0);
|
||||||
bool showModifiedDocumentMessageBox (View *view);
|
bool showModifiedDocumentMessageBox (View *view);
|
||||||
bool showSaveInProgressMessageBox (View *view);
|
bool showSaveInProgressMessageBox (View *view);
|
||||||
|
bool removeDocument(View *view);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ namespace CSVDoc
|
||||||
///< Return number of views for \a document.
|
///< Return number of views for \a document.
|
||||||
|
|
||||||
bool closeRequest (View *view);
|
bool closeRequest (View *view);
|
||||||
|
void removeDocAndView (CSMDoc::Document *document);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,9 @@ CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *pare
|
||||||
|
|
||||||
layout->setContentsMargins (0, 0, 0, 0);
|
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);
|
mEdit = new EditWidget (data, this);
|
||||||
|
|
||||||
|
|
|
@ -114,8 +114,20 @@ void CSVSettings::Dialog::show()
|
||||||
setViewValues();
|
setViewValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
QPoint screenCenter = QApplication::desktop()->screenGeometry().center();
|
QWidget *currView = QApplication::activeWindow();
|
||||||
|
if(currView)
|
||||||
move (screenCenter - geometry().center());
|
{
|
||||||
|
// 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();
|
QWidget::show();
|
||||||
}
|
}
|
||||||
|
|
|
@ -448,12 +448,12 @@ void CSVWorld::Table::tableSizeUpdate()
|
||||||
size = rows;
|
size = rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
tableSizeChanged (size, deleted, modified);
|
emit tableSizeChanged (size, deleted, modified);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVWorld::Table::selectionSizeUpdate()
|
void CSVWorld::Table::selectionSizeUpdate()
|
||||||
{
|
{
|
||||||
selectionSizeChanged (selectionModel()->selectedRows().size());
|
emit selectionSizeChanged (selectionModel()->selectedRows().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVWorld::Table::requestFocus (const std::string& id)
|
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<CSMFilter::Node> filter)
|
void CSVWorld::Table::recordFilterChanged (boost::shared_ptr<CSMFilter::Node> filter)
|
||||||
{
|
{
|
||||||
mProxyModel->setFilter (filter);
|
mProxyModel->setFilter (filter);
|
||||||
|
tableSizeUpdate();
|
||||||
|
selectionSizeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event)
|
void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event)
|
||||||
|
|
|
@ -55,7 +55,7 @@ add_openmw_dir (mwscript
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwsound
|
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
|
add_openmw_dir (mwworld
|
||||||
|
|
|
@ -209,6 +209,9 @@ namespace MWClass
|
||||||
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||||
MWMechanics::CreatureStats &stats = getCreatureStats(ptr);
|
MWMechanics::CreatureStats &stats = getCreatureStats(ptr);
|
||||||
|
|
||||||
|
if (stats.getDrawState() != MWMechanics::DrawState_Weapon)
|
||||||
|
return;
|
||||||
|
|
||||||
// Get the weapon used (if hand-to-hand, weapon = inv.end())
|
// Get the weapon used (if hand-to-hand, weapon = inv.end())
|
||||||
MWWorld::Ptr weapon;
|
MWWorld::Ptr weapon;
|
||||||
if (ptr.getClass().hasInventoryStore(ptr))
|
if (ptr.getClass().hasInventoryStore(ptr))
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#include "../mwworld/inventorystore.hpp"
|
#include "../mwworld/inventorystore.hpp"
|
||||||
#include "../mwworld/actionequip.hpp"
|
#include "../mwworld/actionequip.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/npcstats.hpp"
|
||||||
|
|
||||||
#include <components/esm/loadench.hpp>
|
#include <components/esm/loadench.hpp>
|
||||||
#include <components/esm/loadmgef.hpp>
|
#include <components/esm/loadmgef.hpp>
|
||||||
|
@ -292,6 +292,38 @@ namespace MWMechanics
|
||||||
case ESM::MagicEffect::CurePoison:
|
case ESM::MagicEffect::CurePoison:
|
||||||
return 1001.f * numEffectsToCure(actor, ESM::MagicEffect::Poison);
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -258,6 +258,8 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
||||||
}
|
}
|
||||||
|
|
||||||
const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType));
|
const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType));
|
||||||
|
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||||
|
weap = sWeaponTypeListEnd;
|
||||||
|
|
||||||
if(force || idle != mIdleState)
|
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);
|
int mode = ((jump == mCurrentJump) ? 2 : 1);
|
||||||
|
|
||||||
|
@ -414,7 +416,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
||||||
speedmult = mMovementSpeed / vel;
|
speedmult = mMovementSpeed / vel;
|
||||||
}
|
}
|
||||||
else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight)
|
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)
|
else if (mMovementSpeed > 0.0f)
|
||||||
{
|
{
|
||||||
// The first person anims don't have any velocity to calculate a speed multiplier from.
|
// 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)
|
, mSkipAnim(false)
|
||||||
, mSecondsOfRunning(0)
|
, mSecondsOfRunning(0)
|
||||||
, mSecondsOfSwimming(0)
|
, mSecondsOfSwimming(0)
|
||||||
|
, mTurnAnimationThreshold(0)
|
||||||
{
|
{
|
||||||
if(!mAnimation)
|
if(!mAnimation)
|
||||||
return;
|
return;
|
||||||
|
@ -666,10 +669,10 @@ void CharacterController::updateIdleStormState()
|
||||||
mAnimation->getInfo("idlestorm", &complete);
|
mAnimation->getInfo("idlestorm", &complete);
|
||||||
|
|
||||||
if (complete == 0)
|
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);
|
1.0f, "start", "loop start", 0.0f, 0);
|
||||||
else if (complete == 1)
|
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);
|
1.0f, "loop start", "loop stop", 0.0f, ~0ul);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -680,7 +683,7 @@ void CharacterController::updateIdleStormState()
|
||||||
{
|
{
|
||||||
if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop"))
|
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);
|
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<ESM::Spell>().find(spellid);
|
||||||
|
const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0);
|
||||||
|
|
||||||
|
const ESM::MagicEffect *effect;
|
||||||
|
effect = store.get<ESM::MagicEffect>().find(effectentry.mEffectID);
|
||||||
|
|
||||||
|
const ESM::Static* castStatic;
|
||||||
|
if (!effect->mCasting.empty())
|
||||||
|
castStatic = store.get<ESM::Static>().find (effect->mCasting);
|
||||||
|
else
|
||||||
|
castStatic = store.get<ESM::Static>().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()
|
bool CharacterController::updateCreatureState()
|
||||||
{
|
{
|
||||||
const MWWorld::Class &cls = mPtr.getClass();
|
const MWWorld::Class &cls = mPtr.getClass();
|
||||||
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
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(stats.getAttackingOrSpell())
|
||||||
{
|
{
|
||||||
if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None)
|
if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None)
|
||||||
|
@ -715,7 +759,18 @@ bool CharacterController::updateCreatureState()
|
||||||
1, "start", "stop",
|
1, "start", "stop",
|
||||||
0.0f, 0);
|
0.0f, 0);
|
||||||
mUpperBodyState = UpperCharState_StartToMinAttack;
|
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);
|
stats.setAttackingOrSpell(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -854,9 +909,7 @@ bool CharacterController::updateWeaponState()
|
||||||
|
|
||||||
if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr))
|
if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr))
|
||||||
{
|
{
|
||||||
static const std::string schools[] = {
|
castSpell(spellid);
|
||||||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
|
||||||
};
|
|
||||||
|
|
||||||
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
|
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
|
||||||
const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0);
|
const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0);
|
||||||
|
@ -864,15 +917,7 @@ bool CharacterController::updateWeaponState()
|
||||||
const ESM::MagicEffect *effect;
|
const ESM::MagicEffect *effect;
|
||||||
effect = store.get<ESM::MagicEffect>().find(effectentry.mEffectID);
|
effect = store.get<ESM::MagicEffect>().find(effectentry.mEffectID);
|
||||||
|
|
||||||
const ESM::Static* castStatic;
|
const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Hands");
|
||||||
if (!effect->mCasting.empty())
|
|
||||||
castStatic = store.get<ESM::Static>().find (effect->mCasting);
|
|
||||||
else
|
|
||||||
castStatic = store.get<ESM::Static>().find ("VFX_DefaultCast");
|
|
||||||
|
|
||||||
mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex);
|
|
||||||
|
|
||||||
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Hands");
|
|
||||||
if (mAnimation->getNode("Left Hand"))
|
if (mAnimation->getNode("Left Hand"))
|
||||||
{
|
{
|
||||||
mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Left Hand", effect->mParticle);
|
mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Left Hand", effect->mParticle);
|
||||||
|
@ -896,12 +941,6 @@ bool CharacterController::updateWeaponState()
|
||||||
weapSpeed, mAttackType+" start", mAttackType+" stop",
|
weapSpeed, mAttackType+" start", mAttackType+" stop",
|
||||||
0.0f, 0);
|
0.0f, 0);
|
||||||
mUpperBodyState = UpperCharState_CastingSpell;
|
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())
|
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);
|
Ogre::Vector3 vec(cls.getMovementSettings(mPtr).mPosition);
|
||||||
if(vec.z > 0.0f) // to avoid slow-down when jumping
|
vec.normalise();
|
||||||
{
|
|
||||||
Ogre::Vector2 vecXY = Ogre::Vector2(vec.x, vec.y);
|
|
||||||
vecXY.normalise();
|
|
||||||
vec.x = vecXY.x;
|
|
||||||
vec.y = vecXY.y;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
vec.normalise();
|
|
||||||
|
|
||||||
if(mHitState != CharState_None && mJumpState == JumpState_None)
|
if(mHitState != CharState_None && mJumpState == JumpState_None)
|
||||||
vec = Ogre::Vector3(0.0f);
|
vec = Ogre::Vector3(0.0f);
|
||||||
|
@ -1341,32 +1371,28 @@ void CharacterController::update(float duration)
|
||||||
cls.getCreatureStats(mPtr).land();
|
cls.getCreatureStats(mPtr).land();
|
||||||
}
|
}
|
||||||
|
|
||||||
forcestateupdate = (mJumpState != JumpState_Falling);
|
forcestateupdate = (mJumpState != JumpState_InAir);
|
||||||
mJumpState = JumpState_Falling;
|
mJumpState = JumpState_InAir;
|
||||||
|
|
||||||
// This is a guess. All that seems to be known is that "While the player is in the
|
// 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
|
// air, fJumpMoveBase and fJumpMoveMult governs air control". What does fJumpMoveMult do?
|
||||||
// plays a role, this makes the most sense.
|
static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat();
|
||||||
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();
|
|
||||||
|
|
||||||
mult = fJumpMoveBase +
|
vec.x *= fJumpMoveBase;
|
||||||
(stats.getSkill(ESM::Skill::Acrobatics).getModified()/100.0f *
|
vec.y *= fJumpMoveBase;
|
||||||
fJumpMoveMult);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec.x *= mult;
|
|
||||||
vec.y *= mult;
|
|
||||||
vec.z = 0.0f;
|
vec.z = 0.0f;
|
||||||
}
|
}
|
||||||
else if(vec.z > 0.0f && mJumpState == JumpState_None)
|
else if(vec.z > 0.0f && mJumpState == JumpState_None)
|
||||||
{
|
{
|
||||||
// Started a jump.
|
// 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
|
// advance acrobatics
|
||||||
if (mPtr.getRefData().getHandle() == "player")
|
if (mPtr.getRefData().getHandle() == "player")
|
||||||
|
@ -1382,7 +1408,7 @@ void CharacterController::update(float duration)
|
||||||
fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease);
|
fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease);
|
||||||
cls.getCreatureStats(mPtr).setFatigue(fatigue);
|
cls.getCreatureStats(mPtr).setFatigue(fatigue);
|
||||||
}
|
}
|
||||||
else if(mJumpState == JumpState_Falling)
|
else if(mJumpState == JumpState_InAir)
|
||||||
{
|
{
|
||||||
forcestateupdate = true;
|
forcestateupdate = true;
|
||||||
mJumpState = JumpState_Landing;
|
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)
|
if (onground)
|
||||||
cls.getCreatureStats(mPtr).land();
|
cls.getCreatureStats(mPtr).land();
|
||||||
|
|
||||||
|
@ -1485,6 +1520,12 @@ void CharacterController::update(float duration)
|
||||||
if (inJump)
|
if (inJump)
|
||||||
mMovementAnimationControlled = false;
|
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)
|
if (!mSkipAnim)
|
||||||
{
|
{
|
||||||
rot *= Ogre::Math::RadiansToDegrees(1.0f);
|
rot *= Ogre::Math::RadiansToDegrees(1.0f);
|
||||||
|
@ -1503,7 +1544,9 @@ void CharacterController::update(float duration)
|
||||||
world->queueMovement(mPtr, Ogre::Vector3(0.0f));
|
world->queueMovement(mPtr, Ogre::Vector3(0.0f));
|
||||||
|
|
||||||
movement = vec;
|
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())
|
else if(cls.getCreatureStats(mPtr).isDead())
|
||||||
{
|
{
|
||||||
|
|
|
@ -32,6 +32,7 @@ enum Priority {
|
||||||
Priority_Weapon,
|
Priority_Weapon,
|
||||||
Priority_Knockdown,
|
Priority_Knockdown,
|
||||||
Priority_Torch,
|
Priority_Torch,
|
||||||
|
Priority_Storm,
|
||||||
|
|
||||||
Priority_Death,
|
Priority_Death,
|
||||||
|
|
||||||
|
@ -129,7 +130,7 @@ enum UpperBodyCharacterState {
|
||||||
|
|
||||||
enum JumpingState {
|
enum JumpingState {
|
||||||
JumpState_None,
|
JumpState_None,
|
||||||
JumpState_Falling,
|
JumpState_InAir,
|
||||||
JumpState_Landing
|
JumpState_Landing
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -170,6 +171,8 @@ class CharacterController
|
||||||
float mSecondsOfSwimming;
|
float mSecondsOfSwimming;
|
||||||
float mSecondsOfRunning;
|
float mSecondsOfRunning;
|
||||||
|
|
||||||
|
float mTurnAnimationThreshold; // how long to continue playing turning animation after actor stopped turning
|
||||||
|
|
||||||
std::string mAttackType; // slash, chop or thrust
|
std::string mAttackType; // slash, chop or thrust
|
||||||
void determineAttackType();
|
void determineAttackType();
|
||||||
|
|
||||||
|
@ -181,6 +184,8 @@ class CharacterController
|
||||||
bool updateCreatureState();
|
bool updateCreatureState();
|
||||||
void updateIdleStormState();
|
void updateIdleStormState();
|
||||||
|
|
||||||
|
void castSpell(const std::string& spellid);
|
||||||
|
|
||||||
void updateVisibility();
|
void updateVisibility();
|
||||||
|
|
||||||
void playDeath(float startpoint, CharacterState death);
|
void playDeath(float startpoint, CharacterState death);
|
||||||
|
|
|
@ -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
|
bool Animation::isPlaying(const std::string &groupname) const
|
||||||
{
|
{
|
||||||
AnimStateMap::const_iterator state(mStates.find(groupname));
|
AnimStateMap::const_iterator state(mStates.find(groupname));
|
||||||
|
|
|
@ -260,6 +260,10 @@ public:
|
||||||
float speedmult, const std::string &start, const std::string &stop,
|
float speedmult, const std::string &start, const std::string &stop,
|
||||||
float startpoint, size_t loops);
|
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. */
|
/** Returns true if the named animation group is playing. */
|
||||||
bool isPlaying(const std::string &groupname) const;
|
bool isPlaying(const std::string &groupname) const;
|
||||||
|
|
||||||
|
|
|
@ -32,10 +32,15 @@ extern "C"
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
#include <libswscale/swscale.h>
|
#include <libswscale/swscale.h>
|
||||||
|
|
||||||
// From libavformat version 55.0.100 and onward the declaration of av_gettime() is removed from libavformat/avformat.h and moved
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
|
||||||
// to libavutil/time.h
|
#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
|
// 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 <libavutil/time.h>
|
#include <libavutil/time.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -46,30 +51,25 @@ extern "C"
|
||||||
LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO)
|
LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO)
|
||||||
#include <libavutil/channel_layout.h>
|
#include <libavutil/channel_layout.h>
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
// WARNING: avcodec versions up to 54.54.100 potentially crashes on Windows 64bit.
|
||||||
// Decide whether to play binkaudio.
|
|
||||||
#include <libavcodec/version.h>
|
// From version 54.56 binkaudio encoding format changed from S16 to FLTP. See:
|
||||||
// libavcodec versions 54.10.100 (or maybe earlier) to 54.54.100 potentially crashes Windows 64bit.
|
// https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d
|
||||||
// From version 54.56 or higher, there's no sound due to the encoding format changing from S16 to FLTP
|
// http://ffmpeg.zeranoe.com/forum/viewtopic.php?f=15&t=872
|
||||||
// (see https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d and
|
#ifdef HAVE_LIBSWRESAMPLE
|
||||||
// http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=3049d5b9b32845c86aa5588bb3352bdeb2edfdb2;hp=43c6b45a53a186a187f7266e4d6bd3c2620519f1),
|
#include <libswresample/swresample.h>
|
||||||
// but does not crash (or at least no known crash).
|
|
||||||
#if (LIBAVCODEC_VERSION_MAJOR > 54)
|
|
||||||
#define FFMPEG_PLAY_BINKAUDIO
|
|
||||||
#else
|
#else
|
||||||
#ifdef _WIN64
|
/* FIXME: remove this section once libswresample is available on all platforms */
|
||||||
#if ((LIBAVCODEC_VERSION_MAJOR == 54) && (LIBAVCODEC_VERSION_MINOR >= 55))
|
#include <libavresample/avresample.h>
|
||||||
#define FFMPEG_PLAY_BINKAUDIO
|
#include <libavutil/opt.h>
|
||||||
#endif
|
#define SwrContext AVAudioResampleContext
|
||||||
#else
|
int swr_init(AVAudioResampleContext *avr);
|
||||||
#if ((LIBAVCODEC_VERSION_MAJOR == 54) && (LIBAVCODEC_VERSION_MINOR >= 10))
|
void swr_free(AVAudioResampleContext **avr);
|
||||||
#define FFMPEG_PLAY_BINKAUDIO
|
int swr_convert( AVAudioResampleContext *avr, uint8_t** output, int out_samples, const uint8_t** input, int in_samples);
|
||||||
#endif
|
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
|
#endif
|
||||||
#endif
|
}
|
||||||
|
|
||||||
#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
|
#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
|
||||||
#define MAX_VIDEOQ_SIZE (5 * 256 * 1024)
|
#define MAX_VIDEOQ_SIZE (5 * 256 * 1024)
|
||||||
|
@ -317,6 +317,12 @@ class MovieAudioDecoder : public MWSound::Sound_Decoder
|
||||||
VideoState *mVideoState;
|
VideoState *mVideoState;
|
||||||
AVStream *mAVStream;
|
AVStream *mAVStream;
|
||||||
|
|
||||||
|
SwrContext *mSwr;
|
||||||
|
enum AVSampleFormat mOutputSampleFormat;
|
||||||
|
uint8_t *mDataBuf;
|
||||||
|
uint8_t **mFrameData;
|
||||||
|
int mDataBufLen;
|
||||||
|
|
||||||
AutoAVPacket mPacket;
|
AutoAVPacket mPacket;
|
||||||
AVFrame *mFrame;
|
AVFrame *mFrame;
|
||||||
ssize_t mFramePos;
|
ssize_t mFramePos;
|
||||||
|
@ -383,6 +389,28 @@ class MovieAudioDecoder : public MWSound::Sound_Decoder
|
||||||
if(!got_frame || frame->nb_samples <= 0)
|
if(!got_frame || frame->nb_samples <= 0)
|
||||||
continue;
|
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 /
|
mAudioClock += (double)frame->nb_samples /
|
||||||
(double)mAVStream->codec->sample_rate;
|
(double)mAVStream->codec->sample_rate;
|
||||||
|
|
||||||
|
@ -420,7 +448,7 @@ public:
|
||||||
MovieAudioDecoder(VideoState *is)
|
MovieAudioDecoder(VideoState *is)
|
||||||
: mVideoState(is)
|
: mVideoState(is)
|
||||||
, mAVStream(*is->audio_st)
|
, mAVStream(*is->audio_st)
|
||||||
, mFrame(avcodec_alloc_frame())
|
, mFrame(av_frame_alloc())
|
||||||
, mFramePos(0)
|
, mFramePos(0)
|
||||||
, mFrameSize(0)
|
, mFrameSize(0)
|
||||||
, mAudioClock(0.0)
|
, mAudioClock(0.0)
|
||||||
|
@ -429,10 +457,17 @@ public:
|
||||||
/* Correct audio only if larger error than this */
|
/* Correct audio only if larger error than this */
|
||||||
, mAudioDiffThreshold(2.0 * 0.050/* 50 ms */)
|
, mAudioDiffThreshold(2.0 * 0.050/* 50 ms */)
|
||||||
, mAudioDiffAvgCount(0)
|
, mAudioDiffAvgCount(0)
|
||||||
|
, mSwr(0)
|
||||||
|
, mOutputSampleFormat(AV_SAMPLE_FMT_NONE)
|
||||||
|
, mDataBuf(NULL)
|
||||||
|
, mFrameData(NULL)
|
||||||
|
, mDataBufLen(0)
|
||||||
{ }
|
{ }
|
||||||
virtual ~MovieAudioDecoder()
|
virtual ~MovieAudioDecoder()
|
||||||
{
|
{
|
||||||
av_freep(&mFrame);
|
av_freep(&mFrame);
|
||||||
|
swr_free(&mSwr);
|
||||||
|
av_freep(&mDataBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
void getInfo(int *samplerate, MWSound::ChannelConfig *chans, MWSound::SampleType * type)
|
void getInfo(int *samplerate, MWSound::ChannelConfig *chans, MWSound::SampleType * type)
|
||||||
|
@ -443,10 +478,18 @@ public:
|
||||||
*type = MWSound::SampleType_Int16;
|
*type = MWSound::SampleType_Int16;
|
||||||
else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_FLT)
|
else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_FLT)
|
||||||
*type = MWSound::SampleType_Float32;
|
*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
|
else
|
||||||
fail(std::string("Unsupported sample format: ")+
|
fail(std::string("Unsupported sample format: ")+
|
||||||
av_get_sample_fmt_name(mAVStream->codec->sample_fmt));
|
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)
|
if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_MONO)
|
||||||
*chans = MWSound::ChannelConfig_Mono;
|
*chans = MWSound::ChannelConfig_Mono;
|
||||||
else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_STEREO)
|
else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_STEREO)
|
||||||
|
@ -461,9 +504,15 @@ public:
|
||||||
{
|
{
|
||||||
/* Unknown channel layout. Try to guess. */
|
/* Unknown channel layout. Try to guess. */
|
||||||
if(mAVStream->codec->channels == 1)
|
if(mAVStream->codec->channels == 1)
|
||||||
|
{
|
||||||
*chans = MWSound::ChannelConfig_Mono;
|
*chans = MWSound::ChannelConfig_Mono;
|
||||||
|
ch_layout = AV_CH_LAYOUT_MONO;
|
||||||
|
}
|
||||||
else if(mAVStream->codec->channels == 2)
|
else if(mAVStream->codec->channels == 2)
|
||||||
|
{
|
||||||
*chans = MWSound::ChannelConfig_Stereo;
|
*chans = MWSound::ChannelConfig_Stereo;
|
||||||
|
ch_layout = AV_CH_LAYOUT_STEREO;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::stringstream sstr("Unsupported raw channel count: ");
|
std::stringstream sstr("Unsupported raw channel count: ");
|
||||||
|
@ -480,6 +529,30 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
*samplerate = mAVStream->codec->sample_rate;
|
*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)
|
size_t read(char *stream, size_t len)
|
||||||
|
@ -500,7 +573,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
mFramePos = std::min<ssize_t>(mFrameSize, sample_skip);
|
mFramePos = std::min<ssize_t>(mFrameSize, sample_skip);
|
||||||
sample_skip -= mFramePos;
|
if(sample_skip > 0 || mFrameSize > -sample_skip)
|
||||||
|
sample_skip -= mFramePos;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,7 +582,7 @@ public:
|
||||||
if(mFramePos >= 0)
|
if(mFramePos >= 0)
|
||||||
{
|
{
|
||||||
len1 = std::min<size_t>(len1, mFrameSize-mFramePos);
|
len1 = std::min<size_t>(len1, mFrameSize-mFramePos);
|
||||||
memcpy(stream, mFrame->data[0]+mFramePos, len1);
|
memcpy(stream, mFrameData[0]+mFramePos, len1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -519,29 +593,29 @@ public:
|
||||||
|
|
||||||
/* add samples by copying the first sample*/
|
/* add samples by copying the first sample*/
|
||||||
if(n == 1)
|
if(n == 1)
|
||||||
memset(stream, *mFrame->data[0], len1);
|
memset(stream, *mFrameData[0], len1);
|
||||||
else if(n == 2)
|
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)
|
for(size_t nb = 0;nb < len1;nb += n)
|
||||||
*((int16_t*)(stream+nb)) = val;
|
*((int16_t*)(stream+nb)) = val;
|
||||||
}
|
}
|
||||||
else if(n == 4)
|
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)
|
for(size_t nb = 0;nb < len1;nb += n)
|
||||||
*((int32_t*)(stream+nb)) = val;
|
*((int32_t*)(stream+nb)) = val;
|
||||||
}
|
}
|
||||||
else if(n == 8)
|
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)
|
for(size_t nb = 0;nb < len1;nb += n)
|
||||||
*((int64_t*)(stream+nb)) = val;
|
*((int64_t*)(stream+nb)) = val;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for(size_t nb = 0;nb < len1;nb += n)
|
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;
|
AVFrame *pFrame;
|
||||||
double pts;
|
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);
|
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)
|
while(self->videoq.get(packet, self) >= 0)
|
||||||
|
@ -992,12 +1066,8 @@ void VideoState::init(const std::string& resourceName)
|
||||||
|
|
||||||
this->external_clock_base = av_gettime();
|
this->external_clock_base = av_gettime();
|
||||||
|
|
||||||
#if !defined(_WIN32) || defined(FFMPEG_PLAY_BINKAUDIO)
|
|
||||||
if(audio_index >= 0)
|
if(audio_index >= 0)
|
||||||
this->stream_open(audio_index, this->format_ctx);
|
this->stream_open(audio_index, this->format_ctx);
|
||||||
#else
|
|
||||||
std::cout<<"FFmpeg sound disabled for \""+resourceName+"\""<<std::endl;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if(video_index >= 0)
|
if(video_index >= 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,6 +5,16 @@
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
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
|
namespace MWSound
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -95,6 +105,29 @@ bool FFmpeg_Decoder::getAVAudioData()
|
||||||
memmove(mPacket.data, &mPacket.data[len], remaining);
|
memmove(mPacket.data, &mPacket.data[len], remaining);
|
||||||
av_shrink_packet(&mPacket, 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);
|
} while(got_frame == 0 || mFrame->nb_samples == 0);
|
||||||
mNextPts += (double)mFrame->nb_samples / (double)(*mStream)->codec->sample_rate;
|
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<size_t>(length-dec, mFrameSize-mFramePos);
|
size_t rem = std::min<size_t>(length-dec, mFrameSize-mFramePos);
|
||||||
|
|
||||||
/* Copy the data to the app's buffer and increment */
|
/* 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;
|
data = (char*)data + rem;
|
||||||
dec += rem;
|
dec += rem;
|
||||||
mFramePos += rem;
|
mFramePos += rem;
|
||||||
|
@ -132,19 +165,6 @@ size_t FFmpeg_Decoder::readAVAudioData(void *data, size_t length)
|
||||||
return dec;
|
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)
|
void FFmpeg_Decoder::open(const std::string &fname)
|
||||||
{
|
{
|
||||||
close();
|
close();
|
||||||
|
@ -191,7 +211,7 @@ void FFmpeg_Decoder::open(const std::string &fname)
|
||||||
if(!mStream)
|
if(!mStream)
|
||||||
fail("No audio streams in "+fname);
|
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);
|
AVCodec *codec = avcodec_find_decoder((*mStream)->codec->codec_id);
|
||||||
if(!codec)
|
if(!codec)
|
||||||
|
@ -203,7 +223,7 @@ void FFmpeg_Decoder::open(const std::string &fname)
|
||||||
if(avcodec_open2((*mStream)->codec, codec, NULL) < 0)
|
if(avcodec_open2((*mStream)->codec, codec, NULL) < 0)
|
||||||
fail("Failed to open audio codec " + std::string(codec->long_name));
|
fail("Failed to open audio codec " + std::string(codec->long_name));
|
||||||
|
|
||||||
mFrame = avcodec_alloc_frame();
|
mFrame = av_frame_alloc();
|
||||||
}
|
}
|
||||||
catch(std::exception&)
|
catch(std::exception&)
|
||||||
{
|
{
|
||||||
|
@ -228,6 +248,8 @@ void FFmpeg_Decoder::close()
|
||||||
|
|
||||||
av_free_packet(&mPacket);
|
av_free_packet(&mPacket);
|
||||||
av_freep(&mFrame);
|
av_freep(&mFrame);
|
||||||
|
swr_free(&mSwr);
|
||||||
|
av_freep(&mDataBuf);
|
||||||
|
|
||||||
if(mFormatCtx)
|
if(mFormatCtx)
|
||||||
{
|
{
|
||||||
|
@ -268,10 +290,18 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *
|
||||||
*type = SampleType_Int16;
|
*type = SampleType_Int16;
|
||||||
else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT)
|
else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT)
|
||||||
*type = SampleType_Float32;
|
*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
|
else
|
||||||
fail(std::string("Unsupported sample format: ")+
|
fail(std::string("Unsupported sample format: ")+
|
||||||
av_get_sample_fmt_name((*mStream)->codec->sample_fmt));
|
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)
|
if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_MONO)
|
||||||
*chans = ChannelConfig_Mono;
|
*chans = ChannelConfig_Mono;
|
||||||
else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_STEREO)
|
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. */
|
/* Unknown channel layout. Try to guess. */
|
||||||
if((*mStream)->codec->channels == 1)
|
if((*mStream)->codec->channels == 1)
|
||||||
|
{
|
||||||
*chans = ChannelConfig_Mono;
|
*chans = ChannelConfig_Mono;
|
||||||
|
ch_layout = AV_CH_LAYOUT_MONO;
|
||||||
|
}
|
||||||
else if((*mStream)->codec->channels == 2)
|
else if((*mStream)->codec->channels == 2)
|
||||||
|
{
|
||||||
*chans = ChannelConfig_Stereo;
|
*chans = ChannelConfig_Stereo;
|
||||||
|
ch_layout = AV_CH_LAYOUT_STEREO;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::stringstream sstr("Unsupported raw channel count: ");
|
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;
|
*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)
|
size_t FFmpeg_Decoder::read(char *buffer, size_t bytes)
|
||||||
|
@ -323,7 +384,7 @@ void FFmpeg_Decoder::readAll(std::vector<char> &output)
|
||||||
{
|
{
|
||||||
size_t got = mFrame->nb_samples * (*mStream)->codec->channels *
|
size_t got = mFrame->nb_samples * (*mStream)->codec->channels *
|
||||||
av_get_bytes_per_sample((*mStream)->codec->sample_fmt);
|
av_get_bytes_per_sample((*mStream)->codec->sample_fmt);
|
||||||
const char *inbuf = reinterpret_cast<char*>(mFrame->data[0]);
|
const char *inbuf = reinterpret_cast<char*>(mFrameData[0]);
|
||||||
output.insert(output.end(), inbuf, inbuf+got);
|
output.insert(output.end(), inbuf, inbuf+got);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,6 +413,11 @@ FFmpeg_Decoder::FFmpeg_Decoder()
|
||||||
, mFrameSize(0)
|
, mFrameSize(0)
|
||||||
, mFramePos(0)
|
, mFramePos(0)
|
||||||
, mNextPts(0.0)
|
, mNextPts(0.0)
|
||||||
|
, mSwr(0)
|
||||||
|
, mOutputSampleFormat(AV_SAMPLE_FMT_NONE)
|
||||||
|
, mDataBuf(NULL)
|
||||||
|
, mFrameData(NULL)
|
||||||
|
, mDataBufLen(0)
|
||||||
{
|
{
|
||||||
memset(&mPacket, 0, sizeof(mPacket));
|
memset(&mPacket, 0, sizeof(mPacket));
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,21 @@ extern "C"
|
||||||
LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO)
|
LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO)
|
||||||
#include <libavutil/channel_layout.h>
|
#include <libavutil/channel_layout.h>
|
||||||
#endif
|
#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 <libswresample/swresample.h>
|
||||||
|
#else
|
||||||
|
#include <libavresample/avresample.h>
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
#define SwrContext AVAudioResampleContext
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -40,6 +55,12 @@ namespace MWSound
|
||||||
|
|
||||||
double mNextPts;
|
double mNextPts;
|
||||||
|
|
||||||
|
SwrContext *mSwr;
|
||||||
|
enum AVSampleFormat mOutputSampleFormat;
|
||||||
|
uint8_t *mDataBuf;
|
||||||
|
uint8_t **mFrameData;
|
||||||
|
int mDataBufLen;
|
||||||
|
|
||||||
bool getNextPacket();
|
bool getNextPacket();
|
||||||
|
|
||||||
Ogre::DataStreamPtr mDataStream;
|
Ogre::DataStreamPtr mDataStream;
|
||||||
|
|
109
apps/openmw/mwsound/libavwrapper.cpp
Normal file
109
apps/openmw/mwsound/libavwrapper.cpp
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
#ifndef HAVE_LIBSWRESAMPLE
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#define __STDC_CONSTANT_MACROS
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
// 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 <libavutil/channel_layout.h>
|
||||||
|
#endif
|
||||||
|
#include <libavresample/avresample.h>
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
|
||||||
|
/* 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
|
|
@ -23,6 +23,7 @@
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
|
#include "../mwmechanics/movement.hpp"
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
@ -296,21 +297,24 @@ namespace MWWorld
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
velocity = Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * movement;
|
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
|
// 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(!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
|
// If falling or jumping up, add part of the incoming velocity with the current inertia,
|
||||||
// TODO: but we could be jumping up?
|
// but don't allow increasing inertia beyond actor's speed (except on the initial jump impulse)
|
||||||
velocity = velocity * time + physicActor->getInertialForce();
|
|
||||||
|
|
||||||
// avoid getting infinite inertia in air
|
|
||||||
float actorSpeed = ptr.getClass().getSpeed(ptr);
|
float actorSpeed = ptr.getClass().getSpeed(ptr);
|
||||||
float speedXY = Ogre::Vector2(velocity.x, velocity.y).length();
|
float cap = std::max(actorSpeed, Ogre::Vector2(physicActor->getInertialForce().x, physicActor->getInertialForce().y).length());
|
||||||
if (speedXY > actorSpeed)
|
Ogre::Vector3 newVelocity = velocity + physicActor->getInertialForce();
|
||||||
|
if (Ogre::Vector2(newVelocity.x, newVelocity.y).squaredLength() > cap*cap)
|
||||||
{
|
{
|
||||||
velocity.x *= actorSpeed / speedXY;
|
velocity = newVelocity;
|
||||||
velocity.y *= actorSpeed / speedXY;
|
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
|
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
|
// Now that we have the effective movement vector, apply wind forces to it
|
||||||
if (MWBase::Environment::get().getWorld()->isInStorm())
|
if (MWBase::Environment::get().getWorld()->isInStorm())
|
||||||
|
|
|
@ -162,7 +162,6 @@ namespace MWWorld
|
||||||
|
|
||||||
mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback);
|
mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback);
|
||||||
|
|
||||||
// NOTE: We might need to reserve one more for the running game / save.
|
|
||||||
mEsm.resize(contentFiles.size());
|
mEsm.resize(contentFiles.size());
|
||||||
Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
||||||
listener->loadingOn();
|
listener->loadingOn();
|
||||||
|
@ -2502,9 +2501,9 @@ namespace MWWorld
|
||||||
{
|
{
|
||||||
const ESM::Spell* spell = getStore().get<ESM::Spell>().search(selectedSpell);
|
const ESM::Spell* spell = getStore().get<ESM::Spell>().search(selectedSpell);
|
||||||
|
|
||||||
// A power can be used once per 24h
|
// A power can be used once per 24h
|
||||||
if (spell->mData.mType == ESM::Spell::ST_Power)
|
if (spell->mData.mType == ESM::Spell::ST_Power)
|
||||||
stats.getSpells().usePower(spell->mId);
|
stats.getSpells().usePower(spell->mId);
|
||||||
|
|
||||||
cast.cast(spell);
|
cast.cast(spell);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
# - AVUTIL
|
# - AVUTIL
|
||||||
# - POSTPROCESS
|
# - POSTPROCESS
|
||||||
# - SWSCALE
|
# - SWSCALE
|
||||||
|
# - SWRESAMPLE
|
||||||
# the following variables will be defined
|
# the following variables will be defined
|
||||||
# <component>_FOUND - System has <component>
|
# <component>_FOUND - System has <component>
|
||||||
# <component>_INCLUDE_DIRS - Include directory necessary for using the <component> headers
|
# <component>_INCLUDE_DIRS - Include directory necessary for using the <component> headers
|
||||||
|
@ -112,6 +113,8 @@ if (NOT FFMPEG_LIBRARIES)
|
||||||
find_component(AVUTIL libavutil avutil libavutil/avutil.h)
|
find_component(AVUTIL libavutil avutil libavutil/avutil.h)
|
||||||
find_component(SWSCALE libswscale swscale libswscale/swscale.h)
|
find_component(SWSCALE libswscale swscale libswscale/swscale.h)
|
||||||
find_component(POSTPROC libpostproc postproc libpostproc/postprocess.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.
|
# Check if the required components were found and add their stuff to the FFMPEG_* vars.
|
||||||
foreach (_component ${FFmpeg_FIND_COMPONENTS})
|
foreach (_component ${FFmpeg_FIND_COMPONENTS})
|
||||||
|
@ -142,7 +145,7 @@ if (NOT FFMPEG_LIBRARIES)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# Now set the noncached _FOUND vars for the components.
|
# 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})
|
set_component_found(${_component})
|
||||||
endforeach ()
|
endforeach ()
|
||||||
|
|
||||||
|
|
17
credits.txt
17
credits.txt
|
@ -30,22 +30,28 @@ darkf
|
||||||
Dmitry Shkurskiy (endorph)
|
Dmitry Shkurskiy (endorph)
|
||||||
Douglas Diniz (Dgdiniz)
|
Douglas Diniz (Dgdiniz)
|
||||||
Douglas Mencken (dougmencken)
|
Douglas Mencken (dougmencken)
|
||||||
|
dreamer-dead
|
||||||
Edmondo Tommasina (edmondo)
|
Edmondo Tommasina (edmondo)
|
||||||
Eduard Cot (trombonecot)
|
Eduard Cot (trombonecot)
|
||||||
Eli2
|
Eli2
|
||||||
Emanuel Guével (potatoesmaster)
|
Emanuel Guével (potatoesmaster)
|
||||||
|
eroen
|
||||||
Fil Krynicki (filkry)
|
Fil Krynicki (filkry)
|
||||||
|
Gašper Sedej
|
||||||
gugus/gus
|
gugus/gus
|
||||||
Hallfaer Tuilinn
|
Hallfaer Tuilinn
|
||||||
Jacob Essex (Yacoby)
|
Jacob Essex (Yacoby)
|
||||||
Jannik Heller (scrawl)
|
Jannik Heller (scrawl)
|
||||||
Jason Hooks (jhooks)
|
Jason Hooks (jhooks)
|
||||||
|
jeaye
|
||||||
Jeffrey Haines (Jyby)
|
Jeffrey Haines (Jyby)
|
||||||
Joel Graff (graffy)
|
Joel Graff (graffy)
|
||||||
John Blomberg (fstp)
|
John Blomberg (fstp)
|
||||||
|
Jordan Ayers
|
||||||
Jordan Milne
|
Jordan Milne
|
||||||
Julien Voisin (jvoisin/ap0)
|
Julien Voisin (jvoisin/ap0)
|
||||||
Karl-Felix Glatzer (k1ll)
|
Karl-Felix Glatzer (k1ll)
|
||||||
|
Kevin Poitra (PuppyKevin)
|
||||||
Lars Söderberg (Lazaroth)
|
Lars Söderberg (Lazaroth)
|
||||||
lazydev
|
lazydev
|
||||||
Leon Saunders (emoose)
|
Leon Saunders (emoose)
|
||||||
|
@ -61,16 +67,23 @@ Michael Hogan (Xethik)
|
||||||
Michael Mc Donnell
|
Michael Mc Donnell
|
||||||
Michael Papageorgiou (werdanith)
|
Michael Papageorgiou (werdanith)
|
||||||
Michał Bień (Glorf)
|
Michał Bień (Glorf)
|
||||||
|
Miroslav Puda (pakanek)
|
||||||
|
MiroslavR
|
||||||
Nathan Jeffords (blunted2night)
|
Nathan Jeffords (blunted2night)
|
||||||
Nikolay Kasyanov (corristo)
|
Nikolay Kasyanov (corristo)
|
||||||
|
nobrakal
|
||||||
Nolan Poe (nopoe)
|
Nolan Poe (nopoe)
|
||||||
Paul McElroy (Greendogo)
|
Paul McElroy (Greendogo)
|
||||||
Pieter van der Kloet (pvdk)
|
Pieter van der Kloet (pvdk)
|
||||||
Radu-Marius Popovici (rpopovici)
|
Radu-Marius Popovici (rpopovici)
|
||||||
|
riothamus
|
||||||
|
Robert MacGregor (Ragora)
|
||||||
|
Rohit Nirmal
|
||||||
Roman Melnik (Kromgart)
|
Roman Melnik (Kromgart)
|
||||||
Roman Proskuryakov (humbug)
|
Roman Proskuryakov (humbug)
|
||||||
sandstranger
|
sandstranger
|
||||||
Sandy Carter (bwrsandman)
|
Sandy Carter (bwrsandman)
|
||||||
|
Scott Howard
|
||||||
Sebastian Wick (swick)
|
Sebastian Wick (swick)
|
||||||
Sergey Shambir
|
Sergey Shambir
|
||||||
sir_herrbatka
|
sir_herrbatka
|
||||||
|
@ -80,6 +93,7 @@ Sylvain Thesnieres (Garvek)
|
||||||
Thomas Luppi (Digmaster)
|
Thomas Luppi (Digmaster)
|
||||||
Tom Mason (wheybags)
|
Tom Mason (wheybags)
|
||||||
Torben Leif Carrington (TorbenC)
|
Torben Leif Carrington (TorbenC)
|
||||||
|
Vincent Heuken
|
||||||
|
|
||||||
Packagers:
|
Packagers:
|
||||||
Alexander Olofsson (Ace) - Windows
|
Alexander Olofsson (Ace) - Windows
|
||||||
|
@ -124,11 +138,12 @@ Sadler
|
||||||
Artwork:
|
Artwork:
|
||||||
Necrod - OpenMW Logo
|
Necrod - OpenMW Logo
|
||||||
Mickey Lyle (raevol) - Wordpress Theme
|
Mickey Lyle (raevol) - Wordpress Theme
|
||||||
Okulo - OpenMW Editor Icons
|
Okulo, SirHerrbatka, crysthala - OpenMW Editor Icons
|
||||||
|
|
||||||
Inactive Contributors:
|
Inactive Contributors:
|
||||||
Ardekantur
|
Ardekantur
|
||||||
Armin Preiml
|
Armin Preiml
|
||||||
|
Berulacks
|
||||||
Carl Maxwell
|
Carl Maxwell
|
||||||
Diggory Hardy
|
Diggory Hardy
|
||||||
Dmitry Marakasov (AMDmi3)
|
Dmitry Marakasov (AMDmi3)
|
||||||
|
|
Loading…
Reference in a new issue