forked from teamnwah/openmw-tes3coop
Merge pull request #361 from OpenMW/master while resolving conflicts
# Conflicts: # .travis.yml
This commit is contained in:
commit
dddd2f1cc7
18 changed files with 255 additions and 154 deletions
|
@ -24,7 +24,7 @@ addons:
|
||||||
- llvm-toolchain-precise-3.6
|
- llvm-toolchain-precise-3.6
|
||||||
packages: [
|
packages: [
|
||||||
# Dev
|
# Dev
|
||||||
clang-3.6, libunshield-dev, libtinyxml-dev,
|
cmake, clang-3.6, libunshield-dev, libtinyxml-dev,
|
||||||
g++-6,
|
g++-6,
|
||||||
# Tests
|
# Tests
|
||||||
libgtest-dev, google-mock,
|
libgtest-dev, google-mock,
|
||||||
|
|
|
@ -360,6 +360,7 @@ if (NOT WIN32 AND NOT APPLE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# CXX Compiler settings
|
# CXX Compiler settings
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -std=c++11 -pedantic -Wno-long-long")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -std=c++11 -pedantic -Wno-long-long")
|
||||||
add_definitions( -DBOOST_NO_CXX11_SCOPED_ENUMS=ON )
|
add_definitions( -DBOOST_NO_CXX11_SCOPED_ENUMS=ON )
|
||||||
|
|
|
@ -169,18 +169,25 @@ void CSMPrefs::State::declare()
|
||||||
"list go to the first/last item");
|
"list go to the first/last item");
|
||||||
|
|
||||||
declareCategory ("3D Scene Input");
|
declareCategory ("3D Scene Input");
|
||||||
|
|
||||||
|
declareDouble ("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0);
|
||||||
|
declareDouble ("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0);
|
||||||
|
declareSeparator();
|
||||||
|
|
||||||
declareDouble ("p-navi-free-sensitivity", "Free Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0);
|
declareDouble ("p-navi-free-sensitivity", "Free Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0);
|
||||||
declareBool ("p-navi-free-invert", "Invert Free Camera Mouse Input", false);
|
declareBool ("p-navi-free-invert", "Invert Free Camera Mouse Input", false);
|
||||||
declareDouble ("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0);
|
|
||||||
declareBool ("p-navi-orbit-invert", "Invert Orbit Camera Mouse Input", false);
|
|
||||||
declareDouble ("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0);
|
|
||||||
declareDouble ("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0);
|
|
||||||
declareDouble ("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0);
|
declareDouble ("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0);
|
||||||
declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28);
|
declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28);
|
||||||
declareDouble ("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0);
|
declareDouble ("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0);
|
||||||
|
declareSeparator();
|
||||||
|
|
||||||
|
declareDouble ("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0);
|
||||||
|
declareBool ("p-navi-orbit-invert", "Invert Orbit Camera Mouse Input", false);
|
||||||
declareDouble ("navi-orbit-rot-speed", "Orbital Camera Rotational Speed", 3.14 / 4).setRange(0.001, 6.28);
|
declareDouble ("navi-orbit-rot-speed", "Orbital Camera Rotational Speed", 3.14 / 4).setRange(0.001, 6.28);
|
||||||
declareDouble ("navi-orbit-speed-mult", "Orbital Camera Speed Multiplier (from Modifier)", 4).setRange(0.001, 1000.0);
|
declareDouble ("navi-orbit-speed-mult", "Orbital Camera Speed Multiplier (from Modifier)", 4).setRange(0.001, 1000.0);
|
||||||
|
declareBool ("navi-orbit-const-roll", "Keep camera roll constant for orbital camera", true);
|
||||||
declareSeparator();
|
declareSeparator();
|
||||||
|
|
||||||
declareBool ("context-select", "Context Sensitive Selection", false);
|
declareBool ("context-select", "Context Sensitive Selection", false);
|
||||||
declareDouble ("drag-factor", "Mouse sensitivity during drag operations", 1.0).
|
declareDouble ("drag-factor", "Mouse sensitivity during drag operations", 1.0).
|
||||||
setRange (0.001, 100.0);
|
setRange (0.001, 100.0);
|
||||||
|
@ -191,6 +198,13 @@ void CSMPrefs::State::declare()
|
||||||
setTooltip ("Acceleration factor during drag operations while holding down shift").
|
setTooltip ("Acceleration factor during drag operations while holding down shift").
|
||||||
setRange (0.001, 100.0);
|
setRange (0.001, 100.0);
|
||||||
declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1);
|
declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1);
|
||||||
|
|
||||||
|
declareCategory ("Rendering");
|
||||||
|
declareInt ("camera-fov", "Camera FOV", 90).setRange(10, 170);
|
||||||
|
declareBool ("camera-ortho", "Orthographic projection for camera", false);
|
||||||
|
declareInt ("camera-ortho-size", "Orthographic projection size parameter", 100).
|
||||||
|
setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.").
|
||||||
|
setRange(10, 10000);
|
||||||
declareDouble ("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0,1);
|
declareDouble ("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0,1);
|
||||||
|
|
||||||
declareCategory ("Tooltips");
|
declareCategory ("Tooltips");
|
||||||
|
|
|
@ -84,7 +84,6 @@ CSVPrefs::Dialogue::~Dialogue()
|
||||||
void CSVPrefs::Dialogue::closeEvent (QCloseEvent *event)
|
void CSVPrefs::Dialogue::closeEvent (QCloseEvent *event)
|
||||||
{
|
{
|
||||||
QMainWindow::closeEvent (event);
|
QMainWindow::closeEvent (event);
|
||||||
|
|
||||||
CSMPrefs::State::get().save();
|
CSMPrefs::State::get().save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -669,15 +669,24 @@ namespace CSVRender
|
||||||
mInitialized = true;
|
mInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OrbitCameraController::setConstRoll(bool enabled)
|
||||||
|
{
|
||||||
|
mConstRoll = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
void OrbitCameraController::rotateHorizontal(double value)
|
void OrbitCameraController::rotateHorizontal(double value)
|
||||||
{
|
{
|
||||||
osg::Vec3d eye, center, up;
|
osg::Vec3d eye, center, up;
|
||||||
getCamera()->getViewMatrixAsLookAt(eye, center, up);
|
getCamera()->getViewMatrixAsLookAt(eye, center, up);
|
||||||
|
osg::Vec3d absoluteUp = osg::Vec3(0,0,1);
|
||||||
|
|
||||||
osg::Quat rotation = osg::Quat(value, up);
|
osg::Quat rotation = osg::Quat(value, mConstRoll ? absoluteUp : up);
|
||||||
osg::Vec3d oldOffset = eye - mCenter;
|
osg::Vec3d oldOffset = eye - mCenter;
|
||||||
osg::Vec3d newOffset = rotation * oldOffset;
|
osg::Vec3d newOffset = rotation * oldOffset;
|
||||||
|
|
||||||
|
if (mConstRoll)
|
||||||
|
up = rotation * up;
|
||||||
|
|
||||||
getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up);
|
getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -687,10 +696,15 @@ namespace CSVRender
|
||||||
getCamera()->getViewMatrixAsLookAt(eye, center, up);
|
getCamera()->getViewMatrixAsLookAt(eye, center, up);
|
||||||
|
|
||||||
osg::Vec3d forward = center - eye;
|
osg::Vec3d forward = center - eye;
|
||||||
osg::Quat rotation = osg::Quat(value, up ^ forward);
|
osg::Vec3d axis = up ^ forward;
|
||||||
|
|
||||||
|
osg::Quat rotation = osg::Quat(value,axis);
|
||||||
osg::Vec3d oldOffset = eye - mCenter;
|
osg::Vec3d oldOffset = eye - mCenter;
|
||||||
osg::Vec3d newOffset = rotation * oldOffset;
|
osg::Vec3d newOffset = rotation * oldOffset;
|
||||||
|
|
||||||
|
if (mConstRoll)
|
||||||
|
up = rotation * up;
|
||||||
|
|
||||||
getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up);
|
getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,8 @@ namespace CSVRender
|
||||||
/// \brief Flag controller to be re-initialized.
|
/// \brief Flag controller to be re-initialized.
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
|
void setConstRoll(bool enable);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void initialize();
|
void initialize();
|
||||||
|
@ -181,6 +183,8 @@ namespace CSVRender
|
||||||
double mOrbitSpeed;
|
double mOrbitSpeed;
|
||||||
double mOrbitSpeedMult;
|
double mOrbitSpeedMult;
|
||||||
|
|
||||||
|
bool mConstRoll;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
||||||
void naviPrimary(bool active);
|
void naviPrimary(bool active);
|
||||||
|
|
|
@ -471,7 +471,7 @@ void CSVRender::Object::setSelected(bool selected)
|
||||||
else
|
else
|
||||||
mRootNode->addChild(mBaseNode);
|
mRootNode->addChild(mBaseNode);
|
||||||
|
|
||||||
mMarkerTransparency = CSMPrefs::get()["3D Scene Input"]["object-marker-alpha"].toDouble();
|
mMarkerTransparency = CSMPrefs::get()["Rendering"]["object-marker-alpha"].toDouble();
|
||||||
updateMarker();
|
updateMarker();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f)
|
||||||
traits->vsync = false;
|
traits->vsync = false;
|
||||||
|
|
||||||
mView = new osgViewer::View;
|
mView = new osgViewer::View;
|
||||||
|
updateCameraParameters( traits->width / static_cast<double>(traits->height) );
|
||||||
|
|
||||||
osg::ref_ptr<osgQt::GraphicsWindowQt> window = new osgQt::GraphicsWindowQt(traits.get());
|
osg::ref_ptr<osgQt::GraphicsWindowQt> window = new osgQt::GraphicsWindowQt(traits.get());
|
||||||
QLayout* layout = new QHBoxLayout(this);
|
QLayout* layout = new QHBoxLayout(this);
|
||||||
|
@ -66,7 +67,6 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f)
|
||||||
mView->getCamera()->setGraphicsContext(window);
|
mView->getCamera()->setGraphicsContext(window);
|
||||||
mView->getCamera()->setClearColor( osg::Vec4(0.2, 0.2, 0.6, 1.0) );
|
mView->getCamera()->setClearColor( osg::Vec4(0.2, 0.2, 0.6, 1.0) );
|
||||||
mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) );
|
mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) );
|
||||||
mView->getCamera()->setProjectionMatrixAsPerspective(30.0f, static_cast<double>(traits->width)/static_cast<double>(traits->height), 1.0f, 10000.0f );
|
|
||||||
|
|
||||||
SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager;
|
SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager;
|
||||||
lightMgr->setStartLight(1);
|
lightMgr->setStartLight(1);
|
||||||
|
@ -188,6 +188,8 @@ SceneWidget::SceneWidget(std::shared_ptr<Resource::ResourceSystem> resourceSyste
|
||||||
|
|
||||||
mOrbitCamControl->setPickingMask(Mask_Reference | Mask_Terrain);
|
mOrbitCamControl->setPickingMask(Mask_Reference | Mask_Terrain);
|
||||||
|
|
||||||
|
mOrbitCamControl->setConstRoll( CSMPrefs::get()["3D Scene Input"]["navi-orbit-const-roll"].isTrue() );
|
||||||
|
|
||||||
// we handle lighting manually
|
// we handle lighting manually
|
||||||
mView->setLightingMode(osgViewer::View::NO_LIGHT);
|
mView->setLightingMode(osgViewer::View::NO_LIGHT);
|
||||||
|
|
||||||
|
@ -370,6 +372,40 @@ void SceneWidget::settingChanged (const CSMPrefs::Setting *setting)
|
||||||
{
|
{
|
||||||
mOrbitCamControl->setOrbitSpeedMultiplier(setting->toDouble());
|
mOrbitCamControl->setOrbitSpeedMultiplier(setting->toDouble());
|
||||||
}
|
}
|
||||||
|
else if (*setting=="3D Scene Input/navi-orbit-const-roll")
|
||||||
|
{
|
||||||
|
mOrbitCamControl->setConstRoll(setting->isTrue());
|
||||||
|
}
|
||||||
|
else if (*setting=="Rendering/camera-fov" ||
|
||||||
|
*setting=="Rendering/camera-ortho" ||
|
||||||
|
*setting=="Rendering/camera-ortho-size")
|
||||||
|
{
|
||||||
|
updateCameraParameters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderWidget::updateCameraParameters(double overrideAspect)
|
||||||
|
{
|
||||||
|
const float nearDist = 1.0;
|
||||||
|
const float farDist = 1000.0;
|
||||||
|
|
||||||
|
if (CSMPrefs::get()["Rendering"]["camera-ortho"].isTrue())
|
||||||
|
{
|
||||||
|
const float size = CSMPrefs::get()["Rendering"]["camera-ortho-size"].toInt();
|
||||||
|
const float aspect = overrideAspect >= 0.0 ? overrideAspect : (width() / static_cast<double>(height()));
|
||||||
|
const float halfH = size * 10.0;
|
||||||
|
const float halfW = halfH * aspect;
|
||||||
|
|
||||||
|
mView->getCamera()->setProjectionMatrixAsOrtho(
|
||||||
|
-halfW, halfW, -halfH, halfH, nearDist, farDist);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mView->getCamera()->setProjectionMatrixAsPerspective(
|
||||||
|
CSMPrefs::get()["Rendering"]["camera-fov"].toInt(),
|
||||||
|
static_cast<double>(width())/static_cast<double>(height()),
|
||||||
|
nearDist, farDist);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneWidget::selectNavigationMode (const std::string& mode)
|
void SceneWidget::selectNavigationMode (const std::string& mode)
|
||||||
|
|
|
@ -64,6 +64,8 @@ namespace CSVRender
|
||||||
osg::ref_ptr<osgViewer::View> mView;
|
osg::ref_ptr<osgViewer::View> mView;
|
||||||
osg::ref_ptr<osg::Group> mRootNode;
|
osg::ref_ptr<osg::Group> mRootNode;
|
||||||
|
|
||||||
|
void updateCameraParameters(double overrideAspect = -1.0);
|
||||||
|
|
||||||
QTimer mTimer;
|
QTimer mTimer;
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
|
|
|
@ -131,7 +131,7 @@ void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setti
|
||||||
mDragWheelFactor = setting->toDouble();
|
mDragWheelFactor = setting->toDouble();
|
||||||
else if (*setting=="3D Scene Input/drag-shift-factor")
|
else if (*setting=="3D Scene Input/drag-shift-factor")
|
||||||
mDragShiftFactor = setting->toDouble();
|
mDragShiftFactor = setting->toDouble();
|
||||||
else if (*setting=="3D Scene Input/object-marker-alpha" && !mInConstructor)
|
else if (*setting=="Rendering/object-marker-alpha" && !mInConstructor)
|
||||||
{
|
{
|
||||||
float alpha = setting->toDouble();
|
float alpha = setting->toDouble();
|
||||||
// getSelection is virtual, thus this can not be called from the constructor
|
// getSelection is virtual, thus this can not be called from the constructor
|
||||||
|
|
|
@ -1026,9 +1026,9 @@ namespace MWMechanics
|
||||||
float y = roll / std::min(x, 100.f);
|
float y = roll / std::min(x, 100.f);
|
||||||
y *= 0.25f * x;
|
y *= 0.25f * x;
|
||||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||||
effect.mDuration = static_cast<int>(y);
|
|
||||||
else
|
|
||||||
effect.mDuration = 1;
|
effect.mDuration = 1;
|
||||||
|
else
|
||||||
|
effect.mDuration = static_cast<int>(y);
|
||||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
|
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
|
||||||
{
|
{
|
||||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
|
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
|
||||||
|
|
|
@ -625,7 +625,7 @@ namespace MWRender
|
||||||
|
|
||||||
float Animation::getStartTime(const std::string &groupname) const
|
float Animation::getStartTime(const std::string &groupname) const
|
||||||
{
|
{
|
||||||
for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter)
|
for(AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter)
|
||||||
{
|
{
|
||||||
const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys();
|
const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys();
|
||||||
|
|
||||||
|
@ -638,7 +638,7 @@ namespace MWRender
|
||||||
|
|
||||||
float Animation::getTextKeyTime(const std::string &textKey) const
|
float Animation::getTextKeyTime(const std::string &textKey) const
|
||||||
{
|
{
|
||||||
for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter)
|
for(AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter)
|
||||||
{
|
{
|
||||||
const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys();
|
const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys();
|
||||||
|
|
||||||
|
|
|
@ -232,11 +232,6 @@ namespace MWScript
|
||||||
if (!ptr.isInCell())
|
if (!ptr.isInCell())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (ptr == MWMechanics::getPlayer())
|
|
||||||
{
|
|
||||||
MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
|
std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
|
||||||
runtime.pop();
|
runtime.pop();
|
||||||
Interpreter::Type_Float pos = runtime[0].mFloat;
|
Interpreter::Type_Float pos = runtime[0].mFloat;
|
||||||
|
@ -246,6 +241,8 @@ namespace MWScript
|
||||||
float ay = ptr.getRefData().getPosition().pos[1];
|
float ay = ptr.getRefData().getPosition().pos[1];
|
||||||
float az = ptr.getRefData().getPosition().pos[2];
|
float az = ptr.getRefData().getPosition().pos[2];
|
||||||
|
|
||||||
|
// Note: SetPos does not skip weather transitions in vanilla engine, so we do not call setTeleported(true) here.
|
||||||
|
|
||||||
MWWorld::Ptr updated = ptr;
|
MWWorld::Ptr updated = ptr;
|
||||||
if(axis == "x")
|
if(axis == "x")
|
||||||
{
|
{
|
||||||
|
@ -257,6 +254,17 @@ namespace MWScript
|
||||||
}
|
}
|
||||||
else if(axis == "z")
|
else if(axis == "z")
|
||||||
{
|
{
|
||||||
|
// We should not place actors under ground
|
||||||
|
if (ptr.getClass().isActor())
|
||||||
|
{
|
||||||
|
float terrainHeight = -std::numeric_limits<float>::max();
|
||||||
|
if (ptr.getCell()->isExterior())
|
||||||
|
terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(osg::Vec3f(ax, ay, az));
|
||||||
|
|
||||||
|
if (pos < terrainHeight)
|
||||||
|
pos = terrainHeight;
|
||||||
|
}
|
||||||
|
|
||||||
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos);
|
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <sstream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include <components/vfs/manager.hpp>
|
#include <components/vfs/manager.hpp>
|
||||||
|
@ -11,11 +11,6 @@
|
||||||
namespace MWSound
|
namespace MWSound
|
||||||
{
|
{
|
||||||
|
|
||||||
void FFmpeg_Decoder::fail(const std::string &msg)
|
|
||||||
{
|
|
||||||
throw std::runtime_error("FFmpeg exception: "+msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size)
|
int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -33,7 +28,8 @@ int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size)
|
||||||
|
|
||||||
int FFmpeg_Decoder::writePacket(void *, uint8_t *, int)
|
int FFmpeg_Decoder::writePacket(void *, uint8_t *, int)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("can't write to read-only stream");
|
std::cerr<< "can't write to read-only stream" <<std::endl;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t FFmpeg_Decoder::seek(void *user_data, int64_t offset, int whence)
|
int64_t FFmpeg_Decoder::seek(void *user_data, int64_t offset, int whence)
|
||||||
|
@ -186,8 +182,10 @@ void FFmpeg_Decoder::open(const std::string &fname)
|
||||||
mDataStream = mResourceMgr->get(fname);
|
mDataStream = mResourceMgr->get(fname);
|
||||||
|
|
||||||
if((mFormatCtx=avformat_alloc_context()) == NULL)
|
if((mFormatCtx=avformat_alloc_context()) == NULL)
|
||||||
fail("Failed to allocate context");
|
throw std::runtime_error("Failed to allocate context");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek);
|
mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek);
|
||||||
if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0)
|
if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0)
|
||||||
{
|
{
|
||||||
|
@ -207,13 +205,11 @@ void FFmpeg_Decoder::open(const std::string &fname)
|
||||||
avformat_free_context(mFormatCtx);
|
avformat_free_context(mFormatCtx);
|
||||||
}
|
}
|
||||||
mFormatCtx = NULL;
|
mFormatCtx = NULL;
|
||||||
fail("Failed to allocate input stream");
|
throw std::runtime_error("Failed to allocate input stream");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if(avformat_find_stream_info(mFormatCtx, NULL) < 0)
|
if(avformat_find_stream_info(mFormatCtx, NULL) < 0)
|
||||||
fail("Failed to find stream info in "+fname);
|
throw std::runtime_error("Failed to find stream info in "+fname);
|
||||||
|
|
||||||
for(size_t j = 0;j < mFormatCtx->nb_streams;j++)
|
for(size_t j = 0;j < mFormatCtx->nb_streams;j++)
|
||||||
{
|
{
|
||||||
|
@ -224,24 +220,38 @@ void FFmpeg_Decoder::open(const std::string &fname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!mStream)
|
if(!mStream)
|
||||||
fail("No audio streams in "+fname);
|
throw std::runtime_error("No audio streams in "+fname);
|
||||||
|
|
||||||
(*mStream)->codec->request_sample_fmt = (*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)
|
||||||
{
|
{
|
||||||
std::stringstream ss("No codec found for id ");
|
std::string ss = "No codec found for id " +
|
||||||
ss << (*mStream)->codec->codec_id;
|
std::to_string((*mStream)->codec->codec_id);
|
||||||
fail(ss.str());
|
throw std::runtime_error(ss);
|
||||||
}
|
}
|
||||||
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));
|
throw std::runtime_error(std::string("Failed to open audio codec ") +
|
||||||
|
codec->long_name);
|
||||||
|
|
||||||
mFrame = av_frame_alloc();
|
mFrame = av_frame_alloc();
|
||||||
|
|
||||||
|
if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT ||
|
||||||
|
(*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLTP)
|
||||||
|
mOutputSampleFormat = AV_SAMPLE_FMT_S16; // FIXME: Check for AL_EXT_FLOAT32 support
|
||||||
|
else 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
|
||||||
|
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
|
||||||
|
|
||||||
|
mOutputChannelLayout = (*mStream)->codec->channel_layout;
|
||||||
|
if(mOutputChannelLayout == 0)
|
||||||
|
mOutputChannelLayout = av_get_default_channel_layout((*mStream)->codec->channels);
|
||||||
}
|
}
|
||||||
catch(std::exception&)
|
catch(...) {
|
||||||
{
|
|
||||||
if(mStream)
|
if(mStream)
|
||||||
avcodec_close((*mStream)->codec);
|
avcodec_close((*mStream)->codec);
|
||||||
mStream = NULL;
|
mStream = NULL;
|
||||||
|
@ -301,16 +311,7 @@ std::string FFmpeg_Decoder::getName()
|
||||||
void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
|
void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
|
||||||
{
|
{
|
||||||
if(!mStream)
|
if(!mStream)
|
||||||
fail("No audio stream info");
|
throw std::runtime_error("No audio stream info");
|
||||||
|
|
||||||
if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT || (*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLTP)
|
|
||||||
mOutputSampleFormat = AV_SAMPLE_FMT_S16; // FIXME: Check for AL_EXT_FLOAT32 support
|
|
||||||
else 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
|
|
||||||
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
|
|
||||||
|
|
||||||
if(mOutputSampleFormat == AV_SAMPLE_FMT_U8)
|
if(mOutputSampleFormat == AV_SAMPLE_FMT_U8)
|
||||||
*type = SampleType_UInt8;
|
*type = SampleType_UInt8;
|
||||||
|
@ -318,19 +319,11 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *
|
||||||
*type = SampleType_Int16;
|
*type = SampleType_Int16;
|
||||||
else if(mOutputSampleFormat == AV_SAMPLE_FMT_FLT)
|
else if(mOutputSampleFormat == AV_SAMPLE_FMT_FLT)
|
||||||
*type = SampleType_Float32;
|
*type = SampleType_Float32;
|
||||||
|
else
|
||||||
int64_t ch_layout = (*mStream)->codec->channel_layout;
|
{
|
||||||
|
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
|
||||||
if(ch_layout == 0)
|
*type = SampleType_Int16;
|
||||||
ch_layout = av_get_default_channel_layout((*mStream)->codec->channels);
|
}
|
||||||
|
|
||||||
mOutputChannelLayout = ch_layout;
|
|
||||||
if (ch_layout == AV_CH_LAYOUT_5POINT1 || ch_layout == AV_CH_LAYOUT_7POINT1
|
|
||||||
|| ch_layout == AV_CH_LAYOUT_QUAD) // FIXME: check for AL_EXT_MCFORMATS support
|
|
||||||
mOutputChannelLayout = AV_CH_LAYOUT_STEREO;
|
|
||||||
else if (ch_layout != AV_CH_LAYOUT_MONO
|
|
||||||
&& ch_layout != AV_CH_LAYOUT_STEREO)
|
|
||||||
mOutputChannelLayout = AV_CH_LAYOUT_STEREO;
|
|
||||||
|
|
||||||
if(mOutputChannelLayout == AV_CH_LAYOUT_MONO)
|
if(mOutputChannelLayout == AV_CH_LAYOUT_MONO)
|
||||||
*chans = ChannelConfig_Mono;
|
*chans = ChannelConfig_Mono;
|
||||||
|
@ -347,13 +340,27 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *
|
||||||
char str[1024];
|
char str[1024];
|
||||||
av_get_channel_layout_string(str, sizeof(str), (*mStream)->codec->channels,
|
av_get_channel_layout_string(str, sizeof(str), (*mStream)->codec->channels,
|
||||||
(*mStream)->codec->channel_layout);
|
(*mStream)->codec->channel_layout);
|
||||||
fail(std::string("Unsupported channel layout: ")+str);
|
std::cerr<< "Unsupported channel layout: "<<str <<std::endl;
|
||||||
|
|
||||||
|
if((*mStream)->codec->channels == 1)
|
||||||
|
{
|
||||||
|
mOutputChannelLayout = AV_CH_LAYOUT_MONO;
|
||||||
|
*chans = ChannelConfig_Mono;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mOutputChannelLayout = AV_CH_LAYOUT_STEREO;
|
||||||
|
*chans = ChannelConfig_Stereo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*samplerate = (*mStream)->codec->sample_rate;
|
*samplerate = (*mStream)->codec->sample_rate;
|
||||||
|
int64_t ch_layout = (*mStream)->codec->channel_layout;
|
||||||
|
if(ch_layout == 0)
|
||||||
|
ch_layout = av_get_default_channel_layout((*mStream)->codec->channels);
|
||||||
|
|
||||||
if(mOutputSampleFormat != (*mStream)->codec->sample_fmt
|
if(mOutputSampleFormat != (*mStream)->codec->sample_fmt ||
|
||||||
|| mOutputChannelLayout != ch_layout)
|
mOutputChannelLayout != ch_layout)
|
||||||
{
|
{
|
||||||
mSwr = swr_alloc_set_opts(mSwr, // SwrContext
|
mSwr = swr_alloc_set_opts(mSwr, // SwrContext
|
||||||
mOutputChannelLayout, // output ch layout
|
mOutputChannelLayout, // output ch layout
|
||||||
|
@ -365,24 +372,29 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *
|
||||||
0, // logging level offset
|
0, // logging level offset
|
||||||
NULL); // log context
|
NULL); // log context
|
||||||
if(!mSwr)
|
if(!mSwr)
|
||||||
fail(std::string("Couldn't allocate SwrContext"));
|
throw std::runtime_error("Couldn't allocate SwrContext");
|
||||||
if(swr_init(mSwr) < 0)
|
if(swr_init(mSwr) < 0)
|
||||||
fail(std::string("Couldn't initialize SwrContext"));
|
throw std::runtime_error("Couldn't initialize SwrContext");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t FFmpeg_Decoder::read(char *buffer, size_t bytes)
|
size_t FFmpeg_Decoder::read(char *buffer, size_t bytes)
|
||||||
{
|
{
|
||||||
if(!mStream)
|
if(!mStream)
|
||||||
fail("No audio stream");
|
{
|
||||||
|
std::cerr<< "No audio stream" <<std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return readAVAudioData(buffer, bytes);
|
return readAVAudioData(buffer, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFmpeg_Decoder::readAll(std::vector<char> &output)
|
void FFmpeg_Decoder::readAll(std::vector<char> &output)
|
||||||
{
|
{
|
||||||
if(!mStream)
|
if(!mStream)
|
||||||
fail("No audio stream");
|
{
|
||||||
|
std::cerr<< "No audio stream" <<std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
while(getAVAudioData())
|
while(getAVAudioData())
|
||||||
{
|
{
|
||||||
|
|
|
@ -35,7 +35,7 @@ extern "C"
|
||||||
|
|
||||||
namespace MWSound
|
namespace MWSound
|
||||||
{
|
{
|
||||||
class FFmpeg_Decoder : public Sound_Decoder
|
class FFmpeg_Decoder final : public Sound_Decoder
|
||||||
{
|
{
|
||||||
AVFormatContext *mFormatCtx;
|
AVFormatContext *mFormatCtx;
|
||||||
AVStream **mStream;
|
AVStream **mStream;
|
||||||
|
@ -66,17 +66,15 @@ namespace MWSound
|
||||||
bool getAVAudioData();
|
bool getAVAudioData();
|
||||||
size_t readAVAudioData(void *data, size_t length);
|
size_t readAVAudioData(void *data, size_t length);
|
||||||
|
|
||||||
virtual void open(const std::string &fname);
|
void open(const std::string &fname) override;
|
||||||
virtual void close();
|
void close() override;
|
||||||
|
|
||||||
virtual std::string getName();
|
std::string getName() override;
|
||||||
virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type);
|
void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) override;
|
||||||
|
|
||||||
virtual size_t read(char *buffer, size_t bytes);
|
size_t read(char *buffer, size_t bytes) override;
|
||||||
virtual void readAll(std::vector<char> &output);
|
void readAll(std::vector<char> &output) override;
|
||||||
virtual size_t getSampleOffset();
|
size_t getSampleOffset() override;
|
||||||
|
|
||||||
void fail(const std::string &msg);
|
|
||||||
|
|
||||||
FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs);
|
FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs);
|
||||||
FFmpeg_Decoder(const FFmpeg_Decoder &rhs);
|
FFmpeg_Decoder(const FFmpeg_Decoder &rhs);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "movieaudiofactory.hpp"
|
#include "movieaudiofactory.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include <extern/osg-ffmpeg-videoplayer/audiodecoder.hpp>
|
#include <extern/osg-ffmpeg-videoplayer/audiodecoder.hpp>
|
||||||
#include <extern/osg-ffmpeg-videoplayer/videostate.hpp>
|
#include <extern/osg-ffmpeg-videoplayer/videostate.hpp>
|
||||||
|
|
||||||
|
@ -13,7 +15,7 @@ namespace MWSound
|
||||||
{
|
{
|
||||||
|
|
||||||
class MovieAudioDecoder;
|
class MovieAudioDecoder;
|
||||||
class MWSoundDecoderBridge : public Sound_Decoder
|
class MWSoundDecoderBridge final : public Sound_Decoder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MWSoundDecoderBridge(MWSound::MovieAudioDecoder* decoder)
|
MWSoundDecoderBridge(MWSound::MovieAudioDecoder* decoder)
|
||||||
|
@ -25,12 +27,12 @@ namespace MWSound
|
||||||
private:
|
private:
|
||||||
MWSound::MovieAudioDecoder* mDecoder;
|
MWSound::MovieAudioDecoder* mDecoder;
|
||||||
|
|
||||||
virtual void open(const std::string &fname);
|
void open(const std::string &fname) override;
|
||||||
virtual void close();
|
void close() override;
|
||||||
virtual std::string getName();
|
std::string getName() override;
|
||||||
virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type);
|
void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) override;
|
||||||
virtual size_t read(char *buffer, size_t bytes);
|
size_t read(char *buffer, size_t bytes) override;
|
||||||
virtual size_t getSampleOffset();
|
size_t getSampleOffset() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MovieAudioDecoder : public Video::MovieAudioDecoder
|
class MovieAudioDecoder : public Video::MovieAudioDecoder
|
||||||
|
@ -98,7 +100,7 @@ namespace MWSound
|
||||||
|
|
||||||
void MWSoundDecoderBridge::open(const std::string &fname)
|
void MWSoundDecoderBridge::open(const std::string &fname)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("unimplemented");
|
throw std::runtime_error("Method not implemented");
|
||||||
}
|
}
|
||||||
void MWSoundDecoderBridge::close() {}
|
void MWSoundDecoderBridge::close() {}
|
||||||
|
|
||||||
|
@ -123,11 +125,8 @@ namespace MWSound
|
||||||
else if (outputChannelLayout == AV_CH_LAYOUT_QUAD)
|
else if (outputChannelLayout == AV_CH_LAYOUT_QUAD)
|
||||||
*chans = ChannelConfig_Quad;
|
*chans = ChannelConfig_Quad;
|
||||||
else
|
else
|
||||||
{
|
throw std::runtime_error("Unsupported channel layout: "+
|
||||||
std::stringstream error;
|
std::to_string(outputChannelLayout));
|
||||||
error << "Unsupported channel layout: " << outputChannelLayout;
|
|
||||||
throw std::runtime_error(error.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
AVSampleFormat outputSampleFormat = mDecoder->getOutputSampleFormat();
|
AVSampleFormat outputSampleFormat = mDecoder->getOutputSampleFormat();
|
||||||
if (outputSampleFormat == AV_SAMPLE_FMT_U8)
|
if (outputSampleFormat == AV_SAMPLE_FMT_U8)
|
||||||
|
@ -140,7 +139,7 @@ namespace MWSound
|
||||||
{
|
{
|
||||||
char str[1024];
|
char str[1024];
|
||||||
av_get_sample_fmt_string(str, sizeof(str), outputSampleFormat);
|
av_get_sample_fmt_string(str, sizeof(str), outputSampleFormat);
|
||||||
throw std::runtime_error(std::string("Unsupported sample format: ") + str);
|
throw std::runtime_error(std::string("Unsupported sample format: ")+str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -411,9 +411,14 @@ bool OpenAL_SoundStream::init(bool getLoudnessData)
|
||||||
ChannelConfig chans;
|
ChannelConfig chans;
|
||||||
SampleType type;
|
SampleType type;
|
||||||
|
|
||||||
|
try {
|
||||||
mDecoder->getInfo(&mSampleRate, &chans, &type);
|
mDecoder->getInfo(&mSampleRate, &chans, &type);
|
||||||
mFormat = getALFormat(chans, type);
|
mFormat = getALFormat(chans, type);
|
||||||
if(!mFormat) return false;
|
}
|
||||||
|
catch(std::exception &e) {
|
||||||
|
std::cerr<< "Failed to get stream info: "<<e.what() <<std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
switch(type)
|
switch(type)
|
||||||
{
|
{
|
||||||
|
@ -945,6 +950,11 @@ std::pair<Sound_Handle,size_t> OpenAL_Output::loadSound(const std::string &fname
|
||||||
{
|
{
|
||||||
getALError();
|
getALError();
|
||||||
|
|
||||||
|
std::vector<char> data;
|
||||||
|
ALenum format;
|
||||||
|
int srate;
|
||||||
|
|
||||||
|
try {
|
||||||
DecoderPtr decoder = mManager.getDecoder();
|
DecoderPtr decoder = mManager.getDecoder();
|
||||||
// Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav.
|
// Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav.
|
||||||
if(decoder->mResourceMgr->exists(fname))
|
if(decoder->mResourceMgr->exists(fname))
|
||||||
|
@ -958,18 +968,23 @@ std::pair<Sound_Handle,size_t> OpenAL_Output::loadSound(const std::string &fname
|
||||||
decoder->open(file);
|
decoder->open(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<char> data;
|
|
||||||
ChannelConfig chans;
|
ChannelConfig chans;
|
||||||
SampleType type;
|
SampleType type;
|
||||||
ALenum format;
|
|
||||||
int srate;
|
|
||||||
|
|
||||||
decoder->getInfo(&srate, &chans, &type);
|
decoder->getInfo(&srate, &chans, &type);
|
||||||
format = getALFormat(chans, type);
|
format = getALFormat(chans, type);
|
||||||
if(!format) return std::make_pair(nullptr, 0);
|
if(format) decoder->readAll(data);
|
||||||
|
}
|
||||||
|
catch(std::exception &e) {
|
||||||
|
std::cerr<< "Failed to load audio from "<<fname<<": "<<e.what() <<std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
decoder->readAll(data);
|
if(data.empty())
|
||||||
decoder->close();
|
{
|
||||||
|
// If we failed to get any usable audio, substitute with silence.
|
||||||
|
format = AL_FORMAT_MONO8;
|
||||||
|
srate = 8000;
|
||||||
|
data.assign(8000, -128);
|
||||||
|
}
|
||||||
|
|
||||||
ALint size;
|
ALint size;
|
||||||
ALuint buf = 0;
|
ALuint buf = 0;
|
||||||
|
|
|
@ -2609,9 +2609,8 @@ namespace MWWorld
|
||||||
if (ptr.getRefData().isDeleted())
|
if (ptr.getRefData().isDeleted())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// we should not sell ingrediends from owned organic containers
|
// vanilla Morrowind does not allow to sell items from containers with zero capacity
|
||||||
MWWorld::LiveCellRef<ESM::Container>* ref = ptr.get<ESM::Container>();
|
if (ptr.getClass().getCapacity(ptr) <= 0.f)
|
||||||
if (ref && (ref->mBase->mFlags & ESM::Container::Organic))
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), mOwner.getCellRef().getRefId()))
|
if (Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), mOwner.getCellRef().getRefId()))
|
||||||
|
|
Loading…
Reference in a new issue