Merge remote-tracking branch 'upstream/master' into opencs-settings

This commit is contained in:
U-NSFW-50TH\cc9c 2014-09-17 21:00:22 +10:00
commit ca80a2b856
25 changed files with 616 additions and 141 deletions

View file

@ -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

View file

@ -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)

View file

@ -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 },

View file

@ -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);

View file

@ -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()

View file

@ -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
} }

View file

@ -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:

View file

@ -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);

View file

@ -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();
} }

View file

@ -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)

View file

@ -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

View file

@ -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))

View file

@ -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;
} }

View file

@ -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())
{ {

View file

@ -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);

View file

@ -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));

View file

@ -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;

View file

@ -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)
{ {

View file

@ -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));

View file

@ -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;

View 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

View file

@ -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())

View file

@ -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);
} }

View file

@ -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 ()

View file

@ -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)