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