Add OpenMW commits up to 4 Jul 2020

# Conflicts:
#	.travis.yml
#	CI/before_script.linux.sh
#	apps/openmw/engine.cpp
#	apps/openmw/mwbase/windowmanager.hpp
#	apps/openmw/mwgui/charactercreation.cpp
#	apps/openmw/mwgui/windowmanagerimp.hpp
#	apps/openmw/mwmechanics/character.cpp
pull/593/head
David Cernat 5 years ago
commit 5eb7eb8d88

@ -155,6 +155,7 @@ Programmers
Paul McElroy (Greendogo)
pchan3
Perry Hugh
Petr Mikheev (ptmikheev)
Phillip Andrews (PhillipAnd)
Pi03k
Pieter van der Kloet (pvdk)

@ -29,9 +29,20 @@
Bug #5441: Enemies can't push a player character when in critical strike stance
Bug #5451: Magic projectiles don't disappear with the caster
Bug #5452: Autowalk is being included in savegames
Bug #5472: Mistify mod causes CTD in 0.46 on Mac
Bug #5479: NPCs who should be walking around town are standing around without walking
Bug #5484: Zero value items shouldn't be able to be bought or sold for 1 gold
Bug #5485: Intimidate doesn't increase disposition on marginal wins
Bug #5490: Hits to carried left slot aren't redistributed if there's no shield equipped
Bug #5499: Faction advance is available when requirements not met
Bug #5502: Dead zone for analogue stick movement is too small
Bug #5507: Sound volume is not clamped on ingame settings update
Feature #390: 3rd person look "over the shoulder"
Feature #2386: Distant Statics in the form of Object Paging
Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher
Feature #5362: Show the soul gems' trapped soul in count dialog
Feature #5445: Handle NiLines
Feature #5457: Realistic diagonal movement
Task #5480: Drop Qt4 support
0.46.0

@ -15,32 +15,21 @@ fi
export RAKNET_ROOT=~/CrabNet
if [[ -z "${BUILD_OPENMW}" ]]; then export BUILD_OPENMW=ON; fi
${ANALYZE} cmake .. \
-DCMAKE_C_COMPILER="${CC}" \
-DCMAKE_CXX_COMPILER="${CXX}" \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DBUILD_OPENMW=${BUILD_OPENMW} \
-DBUILD_OPENCS=OFF \
-DBUILD_LAUNCHER=${BUILD_OPENMW_CS} \
-DBUILD_BSATOOL=${BUILD_OPENMW_CS} \
-DBUILD_ESMTOOL=${BUILD_OPENMW_CS} \
-DBUILD_MWINIIMPORTER=${BUILD_OPENMW_CS} \
-DBUILD_ESSIMPORTER=${BUILD_OPENMW_CS} \
-DBUILD_WIZARD=${BUILD_OPENMW_CS} \
-DBUILD_NIFTEST=${BUILD_OPENMW_CS} \
-DBUILD_OPENMW_MP=ON \
-DBUILD_BROWSER=ON \
-DBUILD_MASTER=ON \
-DBUILD_UNITTESTS=1 \
-DUSE_SYSTEM_TINYXML=1 \
-DCMAKE_INSTALL_PREFIX=/usr \
-DBINDIR=/usr/games \
-DCMAKE_BUILD_TYPE="None" \
-DBUILD_UNITTESTS=TRUE \
-DUSE_SYSTEM_TINYXML=TRUE \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DBINDIR="/usr/games" \
-DCMAKE_BUILD_TYPE="DEBUG" \
-DGTEST_ROOT="${GOOGLETEST_DIR}" \
-DGMOCK_ROOT="${GOOGLETEST_DIR}" \
-DRakNet_LIBRARY_RELEASE=~/CrabNet/lib/libRakNetLibStatic.a \

@ -497,13 +497,6 @@ if [ -z $SKIP_DOWNLOAD ]; then
"OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z"
fi
# Qt
if [ -z $APPVEYOR ]; then
download "AQt installer" \
"https://files.pythonhosted.org/packages/f3/bb/aee972f08deecca31bfc46b5aedfad1ce6c7f3aaf1288d685e4a914b53ac/aqtinstall-0.8-py2.py3-none-any.whl" \
"aqtinstall-0.8-py2.py3-none-any.whl"
fi
# SDL2
download "SDL 2.0.12" \
"https://www.libsdl.org/release/SDL2-devel-2.0.12-VC.zip" \
@ -511,11 +504,11 @@ if [ -z $SKIP_DOWNLOAD ]; then
# Google test and mock
if [ ! -z $TEST_FRAMEWORK ]; then
echo "Google test 1.8.1..."
echo "Google test 1.10.0..."
if [ -d googletest ]; then
printf " Google test exists, skipping."
else
git clone -b release-1.8.1 https://github.com/google/googletest.git
git clone -b release-1.10.0 https://github.com/google/googletest.git
fi
fi
fi
@ -732,9 +725,16 @@ fi
fi
if [ -z $APPVEYOR ]; then
cd $DEPS_INSTALL
QT_SDK="$(real_pwd)/Qt/5.15.0/msvc${MSVC_REAL_YEAR}${SUFFIX}"
if [ -d 'Qt/5.15.0' ]; then
qt_version="5.15.0"
if [ "win${BITS}_msvc${MSVC_REAL_YEAR}${SUFFIX}" == "win64_msvc2017_64" ]; then
echo "This combination of options is known not to work. Falling back to Qt 5.14.2."
qt_version="5.14.2"
fi
QT_SDK="$(real_pwd)/Qt/${qt_version}/msvc${MSVC_REAL_YEAR}${SUFFIX}"
if [ -d "Qt/${qt_version}" ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
if [ $MISSINGPYTHON -ne 0 ]; then
@ -745,20 +745,20 @@ fi
pushd "$DEPS" > /dev/null
if ! [ -d 'aqt-venv' ]; then
echo " Creating Virtualenv for aqt..."
eval python -m venv aqt-venv $STRIP
run_cmd python -m venv aqt-venv
fi
if [ -d 'aqt-venv/bin' ]; then
VENV_BIN_DIR='bin'
elif [ -d 'aqt-venv/Scripts' ]; then
VENV_BIN_DIR='Scripts'
else
echo "Error: Failed to create virtualenv."
exit 1
echo "Error: Failed to create virtualenv in expected location."
wrappedExit 1
fi
if ! [ -e "aqt-venv/${VENV_BIN_DIR}/aqt" ]; then
echo " Installing aqt wheel into virtualenv..."
eval "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall-0.8-py2.py3-none-any.whl $STRIP
run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==0.9.2
fi
popd > /dev/null
@ -767,7 +767,7 @@ fi
mkdir Qt
cd Qt
eval "${DEPS}/aqt-venv/${VENV_BIN_DIR}/aqt" install 5.15.0 windows desktop "win${BITS}_msvc${MSVC_REAL_YEAR}${SUFFIX}" $STRIP
run_cmd "${DEPS}/aqt-venv/${VENV_BIN_DIR}/aqt" install $qt_version windows desktop "win${BITS}_msvc${MSVC_REAL_YEAR}${SUFFIX}"
printf " Cleaning up extraneous data... "
rm -rf Qt/{aqtinstall.log,Tools}
@ -820,7 +820,7 @@ cd $DEPS
echo
# Google Test and Google Mock
if [ ! -z $TEST_FRAMEWORK ]; then
printf "Google test 1.8.1 ..."
printf "Google test 1.10.0 ..."
cd googletest
if [ ! -d build ]; then

@ -535,6 +535,9 @@ if(WIN32)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/platforms" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/platforms" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/resources" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/resources" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)

@ -87,7 +87,7 @@ bool parseOptions (int argc, char** argv, Arguments &info)
("plain,p", "Print contents of dialogs, books and scripts. "
"(skipped by default)"
"Only affects dump mode.")
("quiet,q", "Supress all record information. Useful for speed tests.")
("quiet,q", "Suppress all record information. Useful for speed tests.")
("loadcells,C", "Browse through contents of all cells.")
( "encoding,e", bpo::value<std::string>(&(info.encoding))->

@ -740,9 +740,9 @@ void Record<ESM::Faction>::print()
std::cout << " Attribute2 Requirement: "
<< mData.mData.mRankData[i].mAttribute2 << std::endl;
std::cout << " One Skill at Level: "
<< mData.mData.mRankData[i].mSkill1 << std::endl;
<< mData.mData.mRankData[i].mPrimarySkill << std::endl;
std::cout << " Two Skills at Level: "
<< mData.mData.mRankData[i].mSkill2 << std::endl;
<< mData.mData.mRankData[i].mFavouredSkill << std::endl;
std::cout << " Faction Reaction: "
<< mData.mData.mRankData[i].mFactReaction << std::endl;
}

@ -104,6 +104,7 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
loadSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI");
int showOwnedIndex = mEngineSettings.getInt("show owned", "Game");
// Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid.
if (showOwnedIndex >= 0 && showOwnedIndex <= 3)
@ -171,6 +172,7 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
saveSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI");
int showOwnedCurrentIndex = showOwnedComboBox->currentIndex();
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))
mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex);

@ -403,7 +403,7 @@ bool Launcher::MainDialog::setupGameData()
QAbstractButton *skipButton =
msgBox.addButton(tr("Skip"), QMessageBox::RejectRole);
Q_UNUSED(skipButton); // Supress compiler unused warning
Q_UNUSED(skipButton); // Suppress compiler unused warning
msgBox.exec();

@ -216,7 +216,6 @@ endif(APPLE)
target_link_libraries(openmw-cs
${OSG_LIBRARIES}
${OPENTHREADS_LIBRARIES}
${OSGTEXT_LIBRARIES}
${OSGUTIL_LIBRARIES}
${OSGVIEWER_LIBRARIES}

@ -116,7 +116,7 @@ bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value
Qt::ItemFlags CSMWorld::IdTable::flags (const QModelIndex & index) const
{
if (!index.isValid())
return 0;
return Qt::ItemFlags();
Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;

@ -116,7 +116,7 @@ bool CSMWorld::IdTree::setData (const QModelIndex &index, const QVariant &value,
Qt::ItemFlags CSMWorld::IdTree::flags (const QModelIndex & index) const
{
if (!index.isValid())
return 0;
return Qt::ItemFlags();
if (index.internalId() != 0)
{

@ -1143,8 +1143,8 @@ namespace CSMWorld
case 0: return QString(faction.mRanks[subRowIndex].c_str());
case 1: return rankData.mAttribute1;
case 2: return rankData.mAttribute2;
case 3: return rankData.mSkill1;
case 4: return rankData.mSkill2;
case 3: return rankData.mPrimarySkill;
case 4: return rankData.mFavouredSkill;
case 5: return rankData.mFactReaction;
default: throw std::runtime_error("Rank subcolumn index out of range");
}
@ -1165,8 +1165,8 @@ namespace CSMWorld
case 0: faction.mRanks[subRowIndex] = value.toString().toUtf8().constData(); break;
case 1: rankData.mAttribute1 = value.toInt(); break;
case 2: rankData.mAttribute2 = value.toInt(); break;
case 3: rankData.mSkill1 = value.toInt(); break;
case 4: rankData.mSkill2 = value.toInt(); break;
case 3: rankData.mPrimarySkill = value.toInt(); break;
case 4: rankData.mFavouredSkill = value.toInt(); break;
case 5: rankData.mFactReaction = value.toInt(); break;
default: throw std::runtime_error("Rank index out of range");
}

@ -14,7 +14,7 @@ namespace CSVPrefs
public:
ContextMenuList(QWidget* parent = 0);
ContextMenuList(QWidget* parent = nullptr);
protected:

@ -21,7 +21,7 @@ namespace CSVRender
public:
OrbitCameraMode(WorldspaceWidget* worldspaceWidget, const QIcon& icon, const QString& tooltip = "",
QWidget* parent = 0);
QWidget* parent = nullptr);
~OrbitCameraMode();
virtual void activate(CSVWidget::SceneToolbar* toolbar);

@ -1,5 +1,8 @@
#include "scenewidget.hpp"
#include <chrono>
#include <thread>
#include <QEvent>
#include <QResizeEvent>
#include <QTimer>
@ -184,7 +187,7 @@ void CompositeViewer::update()
double minFrameTime = _runMaxFrameRate > 0.0 ? 1.0 / _runMaxFrameRate : 0.0;
if (dt < minFrameTime)
{
OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-dt));
std::this_thread::sleep_for(std::chrono::duration<double>(minFrameTime - dt));
}
}
@ -332,7 +335,7 @@ void SceneWidget::mouseMoveEvent (QMouseEvent *event)
void SceneWidget::wheelEvent(QWheelEvent *event)
{
mCurrentCamControl->handleMouseScrollEvent(event->delta());
mCurrentCamControl->handleMouseScrollEvent(event->angleDelta().y());
}
void SceneWidget::update(double dt)

@ -49,7 +49,7 @@ namespace CSVRender
Q_OBJECT
public:
RenderWidget(QWidget* parent = 0, Qt::WindowFlags f = 0);
RenderWidget(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
virtual ~RenderWidget();
/// Initiates a request to redraw the view
@ -78,8 +78,8 @@ namespace CSVRender
{
Q_OBJECT
public:
SceneWidget(std::shared_ptr<Resource::ResourceSystem> resourceSystem, QWidget* parent = 0,
Qt::WindowFlags f = 0, bool retrieveInput = true);
SceneWidget(std::shared_ptr<Resource::ResourceSystem> resourceSystem, QWidget* parent = nullptr,
Qt::WindowFlags f = Qt::WindowFlags(), bool retrieveInput = true);
virtual ~SceneWidget();
CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent);

@ -33,7 +33,7 @@
#include "cameracontroller.hpp"
CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent)
: SceneWidget (document.getData().getResourceSystem(), parent, 0, false)
: SceneWidget (document.getData().getResourceSystem(), parent, Qt::WindowFlags(), false)
, mSceneElements(0)
, mRun(0)
, mDocument(document)
@ -677,8 +677,7 @@ void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event)
factor *= mDragShiftFactor;
EditMode& editMode = dynamic_cast<CSVRender::EditMode&> (*mEditMode->getCurrent());
editMode.dragWheel (event->delta(), factor);
editMode.dragWheel (event->angleDelta().y(), factor);
}
else
SceneWidget::wheelEvent(event);

@ -273,7 +273,7 @@ void CSVTools::ReportTable::settingChanged (const CSMPrefs::Setting *setting)
if (key.startsWith (base))
{
QString modifierString = key.mid (base.size());
Qt::KeyboardModifiers modifiers = 0;
Qt::KeyboardModifiers modifiers;
if (modifierString=="-s")
modifiers = Qt::ShiftModifier;

@ -671,7 +671,7 @@ void CSVWorld::Table::settingChanged (const CSMPrefs::Setting *setting)
{
std::string modifierString = setting->getKey().substr (6);
Qt::KeyboardModifiers modifiers = 0;
Qt::KeyboardModifiers modifiers;
if (modifierString=="-s")
modifiers = Qt::ShiftModifier;

@ -41,7 +41,7 @@ add_openmw_dir (mwgui
itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview
tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog
recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview
draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation textcolours
draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation textcolours statswatcher
)
add_openmw_dir (mwdialogue
@ -57,7 +57,7 @@ add_openmw_dir (mwscript
add_openmw_dir (mwsound
soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output
loudness movieaudiofactory alext efx efx-presets
loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater volumesettings
)
add_openmw_dir (mwworld
@ -169,7 +169,6 @@ include_directories(
target_link_libraries(tes3mp
${OSG_LIBRARIES}
${OPENTHREADS_LIBRARIES}
${OSGPARTICLE_LIBRARIES}
${OSGUTIL_LIBRARIES}
${OSGDB_LIBRARIES}

@ -2,6 +2,8 @@
#include <iomanip>
#include <fstream>
#include <chrono>
#include <thread>
#include <boost/filesystem/fstream.hpp>
@ -79,6 +81,140 @@ namespace
if (ret != 0)
Log(Debug::Error) << "SDL error: " << SDL_GetError();
}
struct UserStats
{
const std::string mLabel;
const std::string mBegin;
const std::string mEnd;
const std::string mTaken;
UserStats(const std::string& label, const std::string& prefix)
: mLabel(label),
mBegin(prefix + "_time_begin"),
mEnd(prefix + "_time_end"),
mTaken(prefix + "_time_taken")
{}
};
enum class UserStatsType : std::size_t
{
Input,
Sound,
State,
Script,
Mechanics,
Physics,
World,
Gui,
Number,
};
template <UserStatsType type>
struct UserStatsValue
{
static const UserStats sValue;
};
template <>
const UserStats UserStatsValue<UserStatsType::Input>::sValue {"Input", "input"};
template <>
const UserStats UserStatsValue<UserStatsType::Sound>::sValue {"Sound", "sound"};
template <>
const UserStats UserStatsValue<UserStatsType::State>::sValue {"State", "state"};
template <>
const UserStats UserStatsValue<UserStatsType::Script>::sValue {"Script", "script"};
template <>
const UserStats UserStatsValue<UserStatsType::Mechanics>::sValue {"Mech", "mechanics"};
template <>
const UserStats UserStatsValue<UserStatsType::Physics>::sValue {"Phys", "physics"};
template <>
const UserStats UserStatsValue<UserStatsType::World>::sValue {"World", "world"};
template <>
const UserStats UserStatsValue<UserStatsType::Gui>::sValue {"Gui", "gui"};
template <UserStatsType type>
struct ForEachUserStatsValue
{
template <class F>
static void apply(F&& f)
{
f(UserStatsValue<type>::sValue);
using Next = ForEachUserStatsValue<static_cast<UserStatsType>(static_cast<std::size_t>(type) + 1)>;
Next::apply(std::forward<F>(f));
}
};
template <>
struct ForEachUserStatsValue<UserStatsType::Number>
{
template <class F>
static void apply(F&&) {}
};
template <class F>
void forEachUserStatsValue(F&& f)
{
ForEachUserStatsValue<static_cast<UserStatsType>(0)>::apply(std::forward<F>(f));
}
template <UserStatsType sType>
class ScopedProfile
{
public:
ScopedProfile(osg::Timer_t frameStart, unsigned int frameNumber, const osg::Timer& timer, osg::Stats& stats)
: mScopeStart(timer.tick()),
mFrameStart(frameStart),
mFrameNumber(frameNumber),
mTimer(timer),
mStats(stats)
{
}
ScopedProfile(const ScopedProfile&) = delete;
ScopedProfile& operator=(const ScopedProfile&) = delete;
~ScopedProfile()
{
const osg::Timer_t end = mTimer.tick();
const UserStats& stats = UserStatsValue<sType>::sValue;
mStats.setAttribute(mFrameNumber, stats.mBegin, mTimer.delta_s(mFrameStart, mScopeStart));
mStats.setAttribute(mFrameNumber, stats.mTaken, mTimer.delta_s(mScopeStart, end));
mStats.setAttribute(mFrameNumber, stats.mEnd, mTimer.delta_s(mFrameStart, end));
}
private:
const osg::Timer_t mScopeStart;
const osg::Timer_t mFrameStart;
const unsigned int mFrameNumber;
const osg::Timer& mTimer;
osg::Stats& mStats;
};
void initStatsHandler(Resource::Profiler& profiler)
{
const osg::Vec4f textColor(1.f, 1.f, 1.f, 1.f);
const osg::Vec4f barColor(1.f, 1.f, 1.f, 1.f);
const float multiplier = 1000;
const bool average = true;
const bool averageInInverseSpace = false;
const float maxValue = 10000;
forEachUserStatsValue([&] (const UserStats& v)
{
profiler.addUserStatsLine(v.mLabel, textColor, barColor, v.mTaken, multiplier,
average, averageInInverseSpace, v.mBegin, v.mEnd, maxValue);
});
}
}
void OMW::Engine::executeLocalScripts()
@ -128,16 +264,25 @@ bool OMW::Engine::frame(float frametime)
{
try
{
mStartTick = mViewer->getStartTick();
const osg::Timer_t frameStart = mViewer->getStartTick();
const unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber();
const osg::Timer* const timer = osg::Timer::instance();
osg::Stats* const stats = mViewer->getViewerStats();
mEnvironment.setFrameDuration(frametime);
// update input
{
ScopedProfile<UserStatsType::Input> profile(frameStart, frameNumber, *timer, *stats);
mEnvironment.getInputManager()->update(frametime, false);
}
// When the window is minimized, pause the game. Currently this *has* to be here to work around a MyGUI bug.
// If we are not currently rendering, then RenderItems will not be reused resulting in a memory leak upon changing widget textures (fixed in MyGUI 3.3.2),
// and destroyed widgets will not be deleted (not fixed yet, https://github.com/MyGUI/mygui/issues/21)
{
ScopedProfile<UserStatsType::Sound> profile(frameStart, frameNumber, *timer, *stats);
if (!mEnvironment.getWindowManager()->isWindowVisible())
{
mEnvironment.getSoundManager()->pausePlayback();
@ -157,6 +302,7 @@ bool OMW::Engine::frame(float frametime)
// sound
if (mUseSound)
mEnvironment.getSoundManager()->update(frametime);
}
/*
Start of tes3mp addition
@ -183,9 +329,10 @@ bool OMW::Engine::frame(float frametime)
*/
// update game state
{
ScopedProfile<UserStatsType::State> profile(frameStart, frameNumber, *timer, *stats);
mEnvironment.getStateManager()->update (frametime);
bool guiActive = mEnvironment.getWindowManager()->isGuiMode();
}
/*
Start of tes3mp change (major)
@ -193,14 +340,16 @@ bool OMW::Engine::frame(float frametime)
Whether the GUI is active should have no relevance in multiplayer, so the guiActive
boolean is always set to false instead
*/
guiActive = false;
//bool guiActive = mEnvironment.getWindowManager()->isGuiMode();
bool guiActive = false;
/*
End of tes3mp change (major)
*/
osg::Timer_t beforeScriptTick = osg::Timer::instance()->tick();
if (mEnvironment.getStateManager()->getState()!=
MWBase::StateManager::State_NoGame)
{
ScopedProfile<UserStatsType::Script> profile(frameStart, frameNumber, *timer, *stats);
if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame)
{
if (!paused)
{
@ -223,73 +372,59 @@ bool OMW::Engine::frame(float frametime)
mEnvironment.getWorld()->rechargeItems(frametime, true);
}
}
osg::Timer_t afterScriptTick = osg::Timer::instance()->tick();
}
// update mechanics
{
ScopedProfile<UserStatsType::Mechanics> profile(frameStart, frameNumber, *timer, *stats);
// update actors
osg::Timer_t beforeMechanicsTick = osg::Timer::instance()->tick();
if (mEnvironment.getStateManager()->getState()!=
MWBase::StateManager::State_NoGame)
if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame)
{
mEnvironment.getMechanicsManager()->update(frametime,
guiActive);
mEnvironment.getMechanicsManager()->update(frametime, guiActive);
}
osg::Timer_t afterMechanicsTick = osg::Timer::instance()->tick();
if (mEnvironment.getStateManager()->getState()==
MWBase::StateManager::State_Running)
if (mEnvironment.getStateManager()->getState() == MWBase::StateManager::State_Running)
{
MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr();
/*
Start of tes3mp change (major)
In multiplayer, the game should not end when the player dies,
so the code here has been commented out
*/
//MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr();
//if(!guiActive && player.getClass().getCreatureStats(player).isDead())
// mEnvironment.getStateManager()->endGame();
/*
End of tes3mp change (major)
*/
}
}
// update physics
osg::Timer_t beforePhysicsTick = osg::Timer::instance()->tick();
if (mEnvironment.getStateManager()->getState()!=
MWBase::StateManager::State_NoGame)
{
ScopedProfile<UserStatsType::Physics> profile(frameStart, frameNumber, *timer, *stats);
if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame)
{
mEnvironment.getWorld()->updatePhysics(frametime, guiActive);
}
osg::Timer_t afterPhysicsTick = osg::Timer::instance()->tick();
}
// update world
osg::Timer_t beforeWorldTick = osg::Timer::instance()->tick();
if (mEnvironment.getStateManager()->getState()!=
MWBase::StateManager::State_NoGame)
{
ScopedProfile<UserStatsType::World> profile(frameStart, frameNumber, *timer, *stats);
if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame)
{
mEnvironment.getWorld()->update(frametime, guiActive);
}
osg::Timer_t afterWorldTick = osg::Timer::instance()->tick();
}
// update GUI
{
ScopedProfile<UserStatsType::Gui> profile(frameStart, frameNumber, *timer, *stats);
mEnvironment.getWindowManager()->update(frametime);
unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber();
osg::Stats* stats = mViewer->getViewerStats();
stats->setAttribute(frameNumber, "script_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforeScriptTick));
stats->setAttribute(frameNumber, "script_time_taken", osg::Timer::instance()->delta_s(beforeScriptTick, afterScriptTick));
stats->setAttribute(frameNumber, "script_time_end", osg::Timer::instance()->delta_s(mStartTick, afterScriptTick));
stats->setAttribute(frameNumber, "mechanics_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforeMechanicsTick));
stats->setAttribute(frameNumber, "mechanics_time_taken", osg::Timer::instance()->delta_s(beforeMechanicsTick, afterMechanicsTick));
stats->setAttribute(frameNumber, "mechanics_time_end", osg::Timer::instance()->delta_s(mStartTick, afterMechanicsTick));
stats->setAttribute(frameNumber, "physics_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforePhysicsTick));
stats->setAttribute(frameNumber, "physics_time_taken", osg::Timer::instance()->delta_s(beforePhysicsTick, afterPhysicsTick));
stats->setAttribute(frameNumber, "physics_time_end", osg::Timer::instance()->delta_s(mStartTick, afterPhysicsTick));
stats->setAttribute(frameNumber, "world_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforeWorldTick));
stats->setAttribute(frameNumber, "world_time_taken", osg::Timer::instance()->delta_s(beforeWorldTick, afterWorldTick));
stats->setAttribute(frameNumber, "world_time_end", osg::Timer::instance()->delta_s(mStartTick, afterWorldTick));
}
if (stats->collectStats("resource"))
{
@ -302,7 +437,6 @@ bool OMW::Engine::frame(float frametime)
mEnvironment.reportStats(frameNumber, *stats);
}
}
catch (const std::exception& e)
{
@ -345,8 +479,6 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
throw std::runtime_error("Could not initialize SDL! " + std::string(SDL_GetError()));
}
}
mStartTick = osg::Timer::instance()->tick();
}
OMW::Engine::~Engine()
@ -856,14 +988,7 @@ void OMW::Engine::go()
// Setup profiler
osg::ref_ptr<Resource::Profiler> statshandler = new Resource::Profiler;
statshandler->addUserStatsLine("Script", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
"script_time_taken", 1000.0, true, false, "script_time_begin", "script_time_end", 10000);
statshandler->addUserStatsLine("Mech", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
"mechanics_time_taken", 1000.0, true, false, "mechanics_time_begin", "mechanics_time_end", 10000);
statshandler->addUserStatsLine("Phys", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
"physics_time_taken", 1000.0, true, false, "physics_time_begin", "physics_time_end", 10000);
statshandler->addUserStatsLine("World", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
"world_time_taken", 1000.0, true, false, "world_time_begin", "world_time_end", 10000);
initStatsHandler(*statshandler);
mViewer->addEventHandler(statshandler);
@ -915,7 +1040,7 @@ void OMW::Engine::go()
if (!frame(dt))
{
OpenThreads::Thread::microSleep(5000);
std::this_thread::sleep_for(std::chrono::milliseconds(5));
continue;
}
else

@ -111,8 +111,6 @@ namespace OMW
bool mScriptBlacklistUse;
bool mNewGame;
osg::Timer_t mStartTick;
// not implemented
Engine (const Engine&);
Engine& operator= (const Engine&);

@ -1,8 +1,8 @@
#include "environment.hpp"
#include <cassert>
#include <OpenThreads/Thread>
#include <chrono>
#include <thread>
#include "world.hpp"
#include "scriptmanager.hpp"
@ -99,7 +99,7 @@ void MWBase::Environment::limitFrameRate(double dt) const
double minFrameTime = 1.0 / static_cast<double>(mFrameRateLimit);
if (thisFrameTime < minFrameTime)
{
OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-thisFrameTime));
std::this_thread::sleep_for(std::chrono::duration<double>(minFrameTime - thisFrameTime));
}
}
}

@ -67,10 +67,6 @@ namespace MWBase
virtual void drop (const MWWorld::CellStore *cellStore) = 0;
///< Deregister all objects in the given cell.
virtual void watchActor (const MWWorld::Ptr& ptr) = 0;
///< On each update look for changes in a previously registered actor and update the
/// GUI accordingly.
virtual void update (float duration, bool paused) = 0;
///< Update objects
///

@ -6,6 +6,7 @@
#include <set>
#include "../mwworld/ptr.hpp"
#include "../mwsound/type.hpp"
namespace MWWorld
{
@ -44,14 +45,7 @@ namespace MWSound
LoopNoEnv = Loop | NoEnv,
LoopRemoveAtDistance = Loop | RemoveAtDistance
};
enum class Type {
Sfx = 1<<4, /* Normal SFX sound */
Voice = 1<<5, /* Voice sound */
Foot = 1<<6, /* Footstep sound */
Music = 1<<7, /* Music track */
Movie = 1<<8, /* Movie audio track */
Mask = Sfx | Voice | Foot | Music | Movie
};
// Used for creating a type mask for SoundManager::pauseSounds and resumeSounds
inline int operator~(Type a) { return ~static_cast<int>(a); }
inline int operator&(Type a, Type b) { return static_cast<int>(a) & static_cast<int>(b); }
@ -163,9 +157,6 @@ namespace MWBase
virtual void stopSound(const MWWorld::CellStore *cell) = 0;
///< Stop all sounds for the given cell.
virtual void stopSound(const std::string& soundId) = 0;
///< Stop a non-3d looping sound
virtual void fadeOutSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, float duration) = 0;
///< Fade out given sound (that is already playing) of given object
///< @param reference Reference to object, whose sound is faded out

@ -188,27 +188,11 @@ namespace MWBase
End of tes3mp addition
*/
/// Set value for the given ID.
virtual void setValue (const std::string& id, const MWMechanics::AttributeValue& value) = 0;
virtual void setValue (int parSkill, const MWMechanics::SkillValue& value) = 0;
virtual void setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value) = 0;
virtual void setValue (const std::string& id, const std::string& value) = 0;
virtual void setValue (const std::string& id, int value) = 0;
/// Set time left for the player to start drowning (update the drowning bar)
/// @param time time left to start drowning
/// @param maxTime how long we can be underwater (in total) until drowning starts
virtual void setDrowningTimeLeft (float time, float maxTime) = 0;
virtual void setPlayerClass (const ESM::Class &class_) = 0;
///< set current class of player
virtual void configureSkills (const SkillList& major, const SkillList& minor) = 0;
///< configure skill groups, each set contains the skill ID for that group.
virtual void updateSkillArea() = 0;
///< update display of skills, factions, birth sign, reputation and bounty
virtual void changeCell(const MWWorld::CellStore* cell) = 0;
///< change the active cell
@ -445,6 +429,9 @@ namespace MWBase
virtual void windowResized(int x, int y) = 0;
virtual void windowClosed() = 0;
virtual bool isWindowVisible() = 0;
virtual void watchActor(const MWWorld::Ptr& ptr) = 0;
virtual MWWorld::Ptr getWatchedActor() const = 0;
};
}

@ -724,10 +724,11 @@ namespace MWClass
moveSpeed = getSwimSpeed(ptr);
else
moveSpeed = getWalkSpeed(ptr);
if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0)
moveSpeed *= 0.75f;
moveSpeed *= ptr.getClass().getMovementSettings(ptr).mSpeedFactor;
const MWMechanics::Movement& movementSettings = ptr.getClass().getMovementSettings(ptr);
if (movementSettings.mIsStrafing)
moveSpeed *= 0.75f;
moveSpeed *= movementSettings.mSpeedFactor;
return moveSpeed;
}

@ -929,7 +929,22 @@ namespace MWClass
MWWorld::InventoryStore &inv = getInventoryStore(ptr);
MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot);
MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr());
if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name())
bool hasArmor = !armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name();
// If there's no item in the carried left slot or if it is not a shield redistribute the hit.
if (!hasArmor && hitslot == MWWorld::InventoryStore::Slot_CarriedLeft)
{
if (Misc::Rng::rollDice(2) == 0)
hitslot = MWWorld::InventoryStore::Slot_Cuirass;
else
hitslot = MWWorld::InventoryStore::Slot_LeftPauldron;
armorslot = inv.getSlot(hitslot);
if (armorslot != inv.end())
{
armor = *armorslot;
hasArmor = !armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name();
}
}
if (hasArmor)
{
if (!object.isEmpty() || attacker.isEmpty() || attacker.getClass().isNpc()) // Unarmed creature attacks don't affect armor condition
{
@ -1189,13 +1204,14 @@ namespace MWClass
moveSpeed = getRunSpeed(ptr);
else
moveSpeed = getWalkSpeed(ptr);
if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0)
moveSpeed *= 0.75f;
if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing)
moveSpeed *= gmst.fWereWolfRunMult->mValue.getFloat();
moveSpeed *= ptr.getClass().getMovementSettings(ptr).mSpeedFactor;
const MWMechanics::Movement& movementSettings = ptr.getClass().getMovementSettings(ptr);
if (movementSettings.mIsStrafing)
moveSpeed *= 0.75f;
moveSpeed *= movementSettings.mSpeedFactor;
return moveSpeed;
}

@ -368,7 +368,6 @@ namespace MWGui
if (klass)
{
mPlayerClass = *klass;
MWBase::Environment::get().getWindowManager()->setPlayerClass(mPlayerClass);
}
MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog);
mPickClassDialog = 0;
@ -434,7 +433,6 @@ namespace MWGui
End of tes3mp change (major)
*/
MWBase::Environment::get().getWindowManager()->setValue("name", mPlayerName);
MWBase::Environment::get().getMechanicsManager()->setPlayerName(mPlayerName);
MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog);
mNameDialog = 0;
@ -537,7 +535,6 @@ namespace MWGui
MWBase::Environment::get().getMechanicsManager()->setPlayerClass(klass);
mPlayerClass = klass;
MWBase::Environment::get().getWindowManager()->setPlayerClass(klass);
// Do not delete dialog, so that choices are remembered in case we want to go back and adjust them later
mCreateClassDialog->setVisible(false);
@ -734,7 +731,6 @@ namespace MWGui
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(mGenerateClass);
mPlayerClass = *klass;
MWBase::Environment::get().getWindowManager()->setPlayerClass(mPlayerClass);
updatePlayerHealth();
}

@ -1,13 +1,12 @@
#ifndef CHARACTER_CREATION_HPP
#define CHARACTER_CREATION_HPP
#include <components/esm/loadskil.hpp>
#include <components/esm/loadclas.hpp>
#include <map>
#include <vector>
#include "../mwmechanics/stat.hpp"
#include "statswatcher.hpp"
namespace osg
{
@ -35,21 +34,21 @@ namespace MWGui
class ReviewDialog;
class MessageBoxManager;
class CharacterCreation
class CharacterCreation : public StatsListener
{
public:
typedef std::vector<int> SkillList;
CharacterCreation(osg::Group* parent, Resource::ResourceSystem* resourceSystem);
~CharacterCreation();
virtual ~CharacterCreation();
//Show a dialog
void spawnDialog(const char id);
void setValue (const std::string& id, const MWMechanics::AttributeValue& value);
void setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value);
void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value);
void configureSkills (const SkillList& major, const SkillList& minor);
void setValue (const std::string& id, const MWMechanics::AttributeValue& value) override;
void setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value) override;
void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) override;
void configureSkills(const SkillList& major, const SkillList& minor) override;
void onFrame(float duration);

@ -765,6 +765,9 @@ namespace MWGui
void DialogueWindow::updateTopicFormat()
{
if (!Settings::Manager::getBool("color topic enable", "GUI"))
return;
std::string specialColour = Settings::Manager::getString("color topic specific", "GUI");
std::string oldColour = Settings::Manager::getString("color topic exhausted", "GUI");

@ -2,8 +2,7 @@
#define OPENMW_GAME_MWGUI_HUD_H
#include "mapwindow.hpp"
#include "../mwmechanics/stat.hpp"
#include "statswatcher.hpp"
namespace MWWorld
{
@ -17,12 +16,12 @@ namespace MWGui
class ItemWidget;
class SpellWidget;
class HUD : public WindowBase, public LocalMapBase
class HUD : public WindowBase, public LocalMapBase, public StatsListener
{
public:
HUD(CustomMarkerCollection& customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender);
virtual ~HUD();
void setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value);
void setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value) override;
/// Set time left for the player to start drowning
/// @param time time left to start drowning
@ -48,7 +47,7 @@ namespace MWGui
void setCrosshairVisible(bool visible);
void setCrosshairOwned(bool owned);
void onFrame(float dt);
void onFrame(float dt) override;
void setCellName(const std::string& cellName);
@ -59,7 +58,7 @@ namespace MWGui
void setEnemy(const MWWorld::Ptr& enemy);
void resetEnemy();
void clear();
void clear() override;
private:
MyGUI::ProgressBar *mHealth, *mMagicka, *mStamina, *mEnemyHealth, *mDrowning;
@ -113,8 +112,8 @@ namespace MWGui
void onMapClicked(MyGUI::Widget* _sender);
// LocalMapBase
virtual void customMarkerCreated(MyGUI::Widget* marker);
virtual void doorMarkerCreated(MyGUI::Widget* marker);
virtual void customMarkerCreated(MyGUI::Widget* marker) override;
virtual void doorMarkerCreated(MyGUI::Widget* marker) override;
void updateEnemyHealthBar();

@ -0,0 +1,209 @@
#include "statswatcher.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/spellutil.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
namespace MWGui
{
// mWatchedTimeToStartDrowning = -1 for correct drowning state check,
// if stats.getTimeToStartDrowning() == 0 already on game start
StatsWatcher::StatsWatcher()
: mWatchedLevel(-1), mWatchedTimeToStartDrowning(-1), mWatchedStatsEmpty(true)
{
}
void StatsWatcher::watchActor(const MWWorld::Ptr& ptr)
{
mWatched = ptr;
}
void StatsWatcher::update()
{
if (mWatched.isEmpty())
return;
MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager();
const MWMechanics::NpcStats &stats = mWatched.getClass().getNpcStats(mWatched);
for (int i = 0;i < ESM::Attribute::Length;++i)
{
if (stats.getAttribute(i) != mWatchedAttributes[i] || mWatchedStatsEmpty)
{
std::stringstream attrname;
attrname << "AttribVal"<<(i+1);
mWatchedAttributes[i] = stats.getAttribute(i);
setValue(attrname.str(), stats.getAttribute(i));
}
}
if (stats.getHealth() != mWatchedHealth || mWatchedStatsEmpty)
{
static const std::string hbar("HBar");
mWatchedHealth = stats.getHealth();
setValue(hbar, stats.getHealth());
}
if (stats.getMagicka() != mWatchedMagicka || mWatchedStatsEmpty)
{
static const std::string mbar("MBar");
mWatchedMagicka = stats.getMagicka();
setValue(mbar, stats.getMagicka());
}
if (stats.getFatigue() != mWatchedFatigue || mWatchedStatsEmpty)
{
static const std::string fbar("FBar");
mWatchedFatigue = stats.getFatigue();
setValue(fbar, stats.getFatigue());
}
float timeToDrown = stats.getTimeToStartDrowning();
if (timeToDrown != mWatchedTimeToStartDrowning)
{
static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fHoldBreathTime")->mValue.getFloat();
mWatchedTimeToStartDrowning = timeToDrown;
if(timeToDrown >= fHoldBreathTime || timeToDrown == -1.0) // -1.0 is a special value during initialization
winMgr->setDrowningBarVisibility(false);
else
{
winMgr->setDrowningBarVisibility(true);
winMgr->setDrowningTimeLeft(stats.getTimeToStartDrowning(), fHoldBreathTime);
}
}
//Loop over ESM::Skill::SkillEnum
for (int i = 0; i < ESM::Skill::Length; ++i)
{
if(stats.getSkill(i) != mWatchedSkills[i] || mWatchedStatsEmpty)
{
mWatchedSkills[i] = stats.getSkill(i);
setValue((ESM::Skill::SkillEnum)i, stats.getSkill(i));
}
}
if (stats.getLevel() != mWatchedLevel || mWatchedStatsEmpty)
{
mWatchedLevel = stats.getLevel();
setValue("level", mWatchedLevel);
}
if (mWatched.getClass().isNpc())
{
const ESM::NPC *watchedRecord = mWatched.get<ESM::NPC>()->mBase;
if (watchedRecord->mName != mWatchedName || mWatchedStatsEmpty)
{
mWatchedName = watchedRecord->mName;
setValue("name", watchedRecord->mName);
}
if (watchedRecord->mRace != mWatchedRace || mWatchedStatsEmpty)
{
mWatchedRace = watchedRecord->mRace;
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore()
.get<ESM::Race>().find(watchedRecord->mRace);
setValue("race", race->mName);
}
if (watchedRecord->mClass != mWatchedClass || mWatchedStatsEmpty)
{
mWatchedClass = watchedRecord->mClass;
const ESM::Class *cls = MWBase::Environment::get().getWorld()->getStore()
.get<ESM::Class>().find(watchedRecord->mClass);
setValue("class", cls->mName);
MWBase::WindowManager::SkillList majorSkills (5);
MWBase::WindowManager::SkillList minorSkills (5);
for (int i=0; i<5; ++i)
{
minorSkills[i] = cls->mData.mSkills[i][0];
majorSkills[i] = cls->mData.mSkills[i][1];
}
configureSkills(majorSkills, minorSkills);
}
}
mWatchedStatsEmpty = false;
// Update the equipped weapon icon
MWWorld::InventoryStore& inv = mWatched.getClass().getInventoryStore(mWatched);
MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weapon == inv.end())
winMgr->unsetSelectedWeapon();
else
winMgr->setSelectedWeapon(*weapon);
// Update the selected spell icon
MWWorld::ContainerStoreIterator enchantItem = inv.getSelectedEnchantItem();
if (enchantItem != inv.end())
winMgr->setSelectedEnchantItem(*enchantItem);
else
{
const std::string& spell = winMgr->getSelectedSpell();
if (!spell.empty())
winMgr->setSelectedSpell(spell, int(MWMechanics::getSpellSuccessChance(spell, mWatched)));
else
winMgr->unsetSelectedSpell();
}
}
void StatsWatcher::addListener(StatsListener* listener)
{
mListeners.insert(listener);
}
void StatsWatcher::removeListener(StatsListener* listener)
{
mListeners.erase(listener);
}
void StatsWatcher::setValue(const std::string& id, const MWMechanics::AttributeValue& value)
{
for (StatsListener* listener : mListeners)
listener->setValue(id, value);
}
void StatsWatcher::setValue(ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value)
{
/// \todo Don't use the skill enum as a parameter type (we will have to drop it anyway, once we
/// allow custom skills.
for (StatsListener* listener : mListeners)
listener->setValue(parSkill, value);
}
void StatsWatcher::setValue(const std::string& id, const MWMechanics::DynamicStat<float>& value)
{
for (StatsListener* listener : mListeners)
listener->setValue(id, value);
}
void StatsWatcher::setValue(const std::string& id, const std::string& value)
{
for (StatsListener* listener : mListeners)
listener->setValue(id, value);
}
void StatsWatcher::setValue(const std::string& id, int value)
{
for (StatsListener* listener : mListeners)
listener->setValue(id, value);
}
void StatsWatcher::configureSkills(const std::vector<int>& major, const std::vector<int>& minor)
{
for (StatsListener* listener : mListeners)
listener->configureSkills(major, minor);
}
}

@ -0,0 +1,69 @@
#ifndef MWGUI_STATSWATCHER_H
#define MWGUI_STATSWATCHER_H
#include <set>
#include <components/esm/attr.hpp>
#include <components/esm/loadskil.hpp>
#include "../mwmechanics/stat.hpp"
#include "../mwworld/ptr.hpp"
namespace MWGui
{
class StatsListener
{
public:
/// Set value for the given ID.
virtual void setValue(const std::string& id, const MWMechanics::AttributeValue& value) {}
virtual void setValue(const std::string& id, const MWMechanics::DynamicStat<float>& value) {}
virtual void setValue(const std::string& id, const std::string& value) {}
virtual void setValue(const std::string& id, int value) {}
virtual void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) {}
virtual void configureSkills(const std::vector<int>& major, const std::vector<int>& minor) {}
};
class StatsWatcher
{
MWWorld::Ptr mWatched;
MWMechanics::AttributeValue mWatchedAttributes[ESM::Attribute::Length];
MWMechanics::SkillValue mWatchedSkills[ESM::Skill::Length];
MWMechanics::DynamicStat<float> mWatchedHealth;
MWMechanics::DynamicStat<float> mWatchedMagicka;
MWMechanics::DynamicStat<float> mWatchedFatigue;
std::string mWatchedName;
std::string mWatchedRace;
std::string mWatchedClass;
int mWatchedLevel;
float mWatchedTimeToStartDrowning;
bool mWatchedStatsEmpty;
std::set<StatsListener*> mListeners;
void setValue(const std::string& id, const MWMechanics::AttributeValue& value);
void setValue(const std::string& id, const MWMechanics::DynamicStat<float>& value);
void setValue(const std::string& id, const std::string& value);
void setValue(const std::string& id, int value);
void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value);
void configureSkills(const std::vector<int>& major, const std::vector<int>& minor);
public:
StatsWatcher();
void update();
void addListener(StatsListener* listener);
void removeListener(StatsListener* listener);
void watchActor(const MWWorld::Ptr& ptr);
MWWorld::Ptr getWatchedActor() const { return mWatched; }
};
}
#endif

@ -620,10 +620,10 @@ namespace MWGui
text += "\n";
if (rankData.mSkill1 > 0)
text += "\n#{sNeedOneSkill} " + MyGUI::utility::toString(rankData.mSkill1);
if (rankData.mSkill2 > 0)
text += " #{sand} #{sNeedTwoSkills} " + MyGUI::utility::toString(rankData.mSkill2);
if (rankData.mPrimarySkill > 0)
text += "\n#{sNeedOneSkill} " + MyGUI::utility::toString(rankData.mPrimarySkill);
if (rankData.mFavouredSkill > 0)
text += " #{sand} #{sNeedTwoSkills} " + MyGUI::utility::toString(rankData.mFavouredSkill);
}
}

@ -1,16 +1,14 @@
#ifndef MWGUI_STATS_WINDOW_H
#define MWGUI_STATS_WINDOW_H
#include "../mwmechanics/stat.hpp"
#include "statswatcher.hpp"
#include "windowpinnablebase.hpp"
#include <components/esm/loadskil.hpp>
namespace MWGui
{
class WindowManager;
class StatsWindow : public WindowPinnableBase, public NoDrop
class StatsWindow : public WindowPinnableBase, public NoDrop, public StatsListener
{
public:
typedef std::map<std::string, int> FactionList;
@ -20,24 +18,24 @@ namespace MWGui
StatsWindow(DragAndDrop* drag);
/// automatically updates all the data in the stats window, but only if it has changed.
void onFrame(float dt);
void onFrame(float dt) override;
void setBar(const std::string& name, const std::string& tname, int val, int max);
void setPlayerName(const std::string& playerName);
/// Set value for the given ID.
void setValue (const std::string& id, const MWMechanics::AttributeValue& value);
void setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value);
void setValue (const std::string& id, const std::string& value);
void setValue (const std::string& id, int value);
void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value);
void setValue (const std::string& id, const MWMechanics::AttributeValue& value) override;
void setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value) override;
void setValue (const std::string& id, const std::string& value) override;
void setValue (const std::string& id, int value) override;
void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) override;
void configureSkills(const SkillList& major, const SkillList& minor) override;
void configureSkills (const SkillList& major, const SkillList& minor);
void setReputation (int reputation) { if (reputation != mReputation) mChanged = true; this->mReputation = reputation; }
void setBounty (int bounty) { if (bounty != mBounty) mChanged = true; this->mBounty = bounty; }
void updateSkillArea();
virtual void onOpen() { onWindowResize(mMainWidget->castType<MyGUI::Window>()); }
virtual void onOpen() override { onWindowResize(mMainWidget->castType<MyGUI::Window>()); }
private:
void addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2);
@ -72,8 +70,8 @@ namespace MWGui
const int mMinFullWidth;
protected:
virtual void onPinToggled();
virtual void onTitleDoubleClicked();
virtual void onPinToggled() override;
virtual void onTitleDoubleClicked() override;
};
}
#endif

@ -1,6 +1,8 @@
#include "windowmanagerimp.hpp"
#include <cassert>
#include <chrono>
#include <thread>
#include <osgViewer/Viewer>
@ -284,6 +286,8 @@ namespace MWGui
mVideoWrapper = new SDLUtil::VideoWrapper(window, viewer);
mVideoWrapper->setGammaContrast(Settings::Manager::getFloat("gamma", "Video"),
Settings::Manager::getFloat("contrast", "Video"));
mStatsWatcher.reset(new StatsWatcher());
}
void WindowManager::loadUserFonts()
@ -487,6 +491,10 @@ namespace MWGui
// Set up visibility
updateVisible();
mStatsWatcher->addListener(mHud);
mStatsWatcher->addListener(mStatsWindow);
mStatsWatcher->addListener(mCharGen);
}
int WindowManager::getFontHeight() const
@ -499,8 +507,11 @@ namespace MWGui
if (newgame)
{
disallowAll();
mStatsWatcher->removeListener(mCharGen);
delete mCharGen;
mCharGen = new CharacterCreation(mViewer->getSceneData()->asGroup(), mResourceSystem);
mStatsWatcher->addListener(mCharGen);
}
else
allow(GW_ALL);
@ -510,6 +521,8 @@ namespace MWGui
{
try
{
mStatsWatcher.reset();
mKeyboardNavigation.reset();
MyGUI::LanguageManager::getInstance().eventRequestTag.clear();
@ -679,58 +692,11 @@ namespace MWGui
}
}
void WindowManager::setValue (const std::string& id, const MWMechanics::AttributeValue& value)
{
mStatsWindow->setValue (id, value);
mCharGen->setValue(id, value);
}
void WindowManager::setValue (int parSkill, const MWMechanics::SkillValue& value)
{
/// \todo Don't use the skill enum as a parameter type (we will have to drop it anyway, once we
/// allow custom skills.
mStatsWindow->setValue(static_cast<ESM::Skill::SkillEnum> (parSkill), value);
mCharGen->setValue(static_cast<ESM::Skill::SkillEnum> (parSkill), value);
}
void WindowManager::setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value)
{
mStatsWindow->setValue (id, value);
mHud->setValue (id, value);
mCharGen->setValue(id, value);
}
void WindowManager::setValue (const std::string& id, const std::string& value)
{
mStatsWindow->setValue (id, value);
}
void WindowManager::setValue (const std::string& id, int value)
{
mStatsWindow->setValue (id, value);
}
void WindowManager::setDrowningTimeLeft (float time, float maxTime)
{
mHud->setDrowningTimeLeft(time, maxTime);
}
void WindowManager::setPlayerClass (const ESM::Class &class_)
{
mStatsWindow->setValue("class", class_.mName);
}
void WindowManager::configureSkills (const SkillList& major, const SkillList& minor)
{
mStatsWindow->configureSkills (major, minor);
mCharGen->configureSkills(major, minor);
}
void WindowManager::updateSkillArea()
{
mStatsWindow->updateSkillArea();
}
void WindowManager::removeDialog(Layout*dialog)
{
if (!dialog)
@ -781,7 +747,7 @@ namespace MWGui
MWBase::Environment::get().getInputManager()->update(dt, true, false);
if (!mWindowVisible)
OpenThreads::Thread::microSleep(5000);
std::this_thread::sleep_for(std::chrono::milliseconds(5));
else
{
mViewer->eventTraversal();
@ -947,7 +913,9 @@ namespace MWGui
if (mCharGen)
mCharGen->onFrame(frameDuration);
updateActivatedQuickKey ();
updateActivatedQuickKey();
mStatsWatcher->update();
cleanupGarbage();
}
@ -1917,7 +1885,7 @@ namespace MWGui
if (!mWindowVisible)
{
mVideoWidget->pause();
OpenThreads::Thread::microSleep(5000);
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
else
{
@ -2368,4 +2336,14 @@ namespace MWGui
for (unsigned int i=0; i<mWindows.size(); ++i)
mWindows[i]->setVisible(visible);
}
void WindowManager::watchActor(const MWWorld::Ptr& ptr)
{
mStatsWatcher->watchActor(ptr);
}
MWWorld::Ptr WindowManager::getWatchedActor() const
{
return mStatsWatcher->getWatchedActor();
}
}

@ -13,13 +13,12 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwworld/ptr.hpp"
#include <components/sdlutil/events.hpp>
#include <components/settings/settings.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include "mapwindow.hpp"
#include "statswatcher.hpp"
#include "textcolours.hpp"
#include <MyGUI_KeyCode.h>
@ -229,22 +228,11 @@ namespace MWGui
End of tes3mp addition
*/
///< Set value for the given ID.
virtual void setValue (const std::string& id, const MWMechanics::AttributeValue& value);
virtual void setValue (int parSkill, const MWMechanics::SkillValue& value);
virtual void setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value);
virtual void setValue (const std::string& id, const std::string& value);
virtual void setValue (const std::string& id, int value);
/// Set time left for the player to start drowning (update the drowning bar)
/// @param time time left to start drowning
/// @param maxTime how long we can be underwater (in total) until drowning starts
virtual void setDrowningTimeLeft (float time, float maxTime);
virtual void setPlayerClass (const ESM::Class &class_); ///< set current class of player
virtual void configureSkills (const SkillList& major, const SkillList& minor); ///< configure skill groups, each set contains the skill ID for that group.
virtual void updateSkillArea(); ///< update display of skills, factions, birth sign, reputation and bounty
virtual void changeCell(const MWWorld::CellStore* cell); ///< change the active cell
/*
@ -374,6 +362,9 @@ namespace MWGui
virtual void windowClosed();
virtual bool isWindowVisible();
virtual void watchActor(const MWWorld::Ptr& ptr);
virtual MWWorld::Ptr getWatchedActor() const;
virtual void executeInConsole (const std::string& path);
/*
@ -486,6 +477,7 @@ namespace MWGui
osgViewer::Viewer* mViewer;
std::unique_ptr<Gui::FontLoader> mFontLoader;
std::unique_ptr<StatsWatcher> mStatsWatcher;
bool mConsoleOnlyScripts;

@ -237,6 +237,11 @@ namespace MWInput
}
}
void BindingsManager::setJoystickDeadZone(float deadZone)
{
mInputBinder->setJoystickDeadZone(deadZone);
}
float BindingsManager::getActionValue (int id) const
{
return mInputBinder->getChannel(id)->getValue();

@ -36,6 +36,8 @@ namespace MWInput
void setPlayerControlsEnabled(bool enabled);
void setJoystickDeadZone(float deadZone);
bool isLeftOrRightButton(int action, bool joystick) const;
bool actionIsActive(int id) const;

@ -72,6 +72,10 @@ namespace MWInput
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
if (uiScale != 0.f)
mInvUiScalingFactor = 1.f / uiScale;
float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input");
deadZoneRadius = std::min(std::max(deadZoneRadius, 0.0f), 0.5f);
mBindingsManager->setJoystickDeadZone(deadZoneRadius);
}
void ControllerManager::processChangedSettings(const Settings::CategorySettingVector& changed)
@ -100,10 +104,9 @@ namespace MWInput
// game mode does not move the position of the GUI cursor
float xMove = xAxis * dt * 1500.0f * mInvUiScalingFactor * mGamepadCursorSpeed;
float yMove = yAxis * dt * 1500.0f * mInvUiScalingFactor * mGamepadCursorSpeed;
if (xMove != 0 || yMove != 0 || zAxis != 0)
float mouseWheelMove = -zAxis * dt * 1500.0f;
if (xMove != 0 || yMove != 0 || mouseWheelMove != 0)
{
int mouseWheelMove = static_cast<int>(-zAxis * dt * 1500.0f);
mMouseManager->injectMouseMove(xMove, yMove, mouseWheelMove);
mMouseManager->warpMouse();
MWBase::Environment::get().getWindowManager()->setCursorActive(true);

@ -229,10 +229,10 @@ namespace MWInput
bool MouseManager::injectMouseButtonRelease(Uint8 button)
{
return MyGUI::InputManager::getInstance().injectMousePress(static_cast<int>(mGuiCursorX), static_cast<int>(mGuiCursorY), sdlButtonToMyGUI(button));
return MyGUI::InputManager::getInstance().injectMouseRelease(static_cast<int>(mGuiCursorX), static_cast<int>(mGuiCursorY), sdlButtonToMyGUI(button));
}
void MouseManager::injectMouseMove(int xMove, int yMove, int mouseWheelMove)
void MouseManager::injectMouseMove(float xMove, float yMove, float mouseWheelMove)
{
mGuiCursorX += xMove;
mGuiCursorY += yMove;
@ -242,7 +242,7 @@ namespace MWInput
mGuiCursorX = std::max(0.f, std::min(mGuiCursorX, float(viewSize.width - 1)));
mGuiCursorY = std::max(0.f, std::min(mGuiCursorY, float(viewSize.height - 1)));
MyGUI::InputManager::getInstance().injectMouseMove(static_cast<int>(mGuiCursorX), static_cast<int>(mGuiCursorY), mMouseWheel);
MyGUI::InputManager::getInstance().injectMouseMove(static_cast<int>(mGuiCursorX), static_cast<int>(mGuiCursorY), static_cast<int>(mMouseWheel));
}
void MouseManager::warpMouse()

@ -32,7 +32,7 @@ namespace MWInput
bool injectMouseButtonPress(Uint8 button);
bool injectMouseButtonRelease(Uint8 button);
void injectMouseMove(int xMove, int yMove, int mouseWheelMove);
void injectMouseMove(float xMove, float yMove, float mouseWheelMove);
void warpMouse();
void setMouseLookEnabled(bool enabled) { mMouseLookEnabled = enabled; }

@ -924,6 +924,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
, mAttackingOrSpell(false)
, mCastingManualSpell(false)
, mTimeUntilWake(0.f)
, mIsMovingBackward(false)
{
if(!mAnimation)
return;
@ -2097,41 +2098,25 @@ void CharacterController::update(float duration, bool animationOnly)
bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !flying;
bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying;
CreatureStats &stats = cls.getCreatureStats(mPtr);
Movement& movementSettings = cls.getMovementSettings(mPtr);
//Force Jump Logic
bool isMoving = (std::abs(cls.getMovementSettings(mPtr).mPosition[0]) > .5 || std::abs(cls.getMovementSettings(mPtr).mPosition[1]) > .5);
bool isMoving = (std::abs(movementSettings.mPosition[0]) > .5 || std::abs(movementSettings.mPosition[1]) > .5);
if(!inwater && !flying && solid)
{
//Force Jump
if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump))
{
if(onground)
{
cls.getMovementSettings(mPtr).mPosition[2] = 1;
}
else
cls.getMovementSettings(mPtr).mPosition[2] = 0;
}
movementSettings.mPosition[2] = onground ? 1 : 0;
//Force Move Jump, only jump if they're otherwise moving
if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving)
{
if(onground)
{
cls.getMovementSettings(mPtr).mPosition[2] = 1;
}
else
cls.getMovementSettings(mPtr).mPosition[2] = 0;
}
movementSettings.mPosition[2] = onground ? 1 : 0;
}
osg::Vec3f vec(cls.getMovementSettings(mPtr).asVec3());
osg::Vec3f rot = cls.getRotationVector(mPtr);
osg::Vec3f vec(movementSettings.asVec3());
vec.normalize();
if(mHitState != CharState_None && mJumpState == JumpState_None)
vec = osg::Vec3f(0.f, 0.f, 0.f);
/*
Start of tes3mp addition
@ -2164,30 +2149,60 @@ void CharacterController::update(float duration, bool animationOnly)
End of tes3mp addition
*/
osg::Vec3f rot = cls.getRotationVector(mPtr);
speed = cls.getSpeed(mPtr);
float analogueMult = 1.f;
if(isPlayer)
float analogueMult = 1.0f;
if (isPlayer)
{
// TODO: Move this code to mwinput.
// Joystick analogue movement.
float xAxis = std::abs(cls.getMovementSettings(mPtr).mPosition[0]);
float yAxis = std::abs(cls.getMovementSettings(mPtr).mPosition[1]);
analogueMult = ((xAxis > yAxis) ? xAxis : yAxis);
// If Strafing, our max speed is slower so multiply by X axis instead.
if(std::abs(vec.x()/2.0f) > std::abs(vec.y()))
analogueMult = xAxis;
float xAxis = std::abs(movementSettings.mPosition[0]);
float yAxis = std::abs(movementSettings.mPosition[1]);
analogueMult = std::max(xAxis, yAxis);
// Due to the half way split between walking/running, we multiply speed by 2 while walking, unless a keyboard was used.
if(!isrunning && !sneak && !flying && analogueMult <= 0.5f)
analogueMult *= 2.f;
movementSettings.mSpeedFactor = analogueMult;
}
speed *= analogueMult;
float effectiveRotation = rot.z();
static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game");
if (turnToMovementDirection && !(isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson()))
{
float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y());
movementSettings.mIsStrafing = (stats.getDrawState() != MWMechanics::DrawState_Nothing || inwater)
&& std::abs(targetMovementAngle) > osg::DegreesToRadians(60.0f);
if (movementSettings.mIsStrafing)
targetMovementAngle = 0;
float delta = targetMovementAngle - stats.getSideMovementAngle();
float cosDelta = cosf(delta);
if ((vec.y() < 0) == mIsMovingBackward)
movementSettings.mSpeedFactor *= std::min(std::max(cosDelta, 0.f) + 0.3f, 1.f); // slow down when turn
if (std::abs(delta) < osg::DegreesToRadians(20.0f))
mIsMovingBackward = vec.y() < 0;
float maxDelta = osg::PI * duration * (2.5f - cosDelta);
delta = osg::clampBetween(delta, -maxDelta, maxDelta);
stats.setSideMovementAngle(stats.getSideMovementAngle() + delta);
effectiveRotation += delta;
}
else
movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2;
mAnimation->setLegsYawRadians(stats.getSideMovementAngle());
if (stats.getDrawState() == MWMechanics::DrawState_Nothing || inwater)
mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 2);
else
mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4);
speed = cls.getSpeed(mPtr);
vec.x() *= speed;
vec.y() *= speed;
if(mHitState != CharState_None && mJumpState == JumpState_None)
vec = osg::Vec3f();
CharacterState movestate = CharState_None;
CharacterState idlestate = CharState_SpecialIdle;
JumpingState jumpstate = JumpState_None;
@ -2349,7 +2364,7 @@ void CharacterController::update(float duration, bool animationOnly)
inJump = false;
if(std::abs(vec.x()/2.0f) > std::abs(vec.y()))
if (movementSettings.mIsStrafing)
{
if(vec.x() > 0.0f)
movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight)
@ -2360,18 +2375,18 @@ void CharacterController::update(float duration, bool animationOnly)
: (sneak ? CharState_SneakLeft
: (isrunning ? CharState_RunLeft : CharState_WalkLeft)));
}
else if(vec.y() != 0.0f)
else if (vec.length2() > 0.0f)
{
if(vec.y() > 0.0f)
if (vec.y() >= 0.0f)
movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward)
: (sneak ? CharState_SneakForward
: (isrunning ? CharState_RunForward : CharState_WalkForward)));
else if(vec.y() < 0.0f)
else
movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack)
: (sneak ? CharState_SneakBack
: (isrunning ? CharState_RunBack : CharState_WalkBack)));
}
else if(rot.z() != 0.0f)
else if (effectiveRotation != 0.0f)
{
// Do not play turning animation for player if rotation speed is very slow.
// Actual threshold should take framerate in account.
@ -2384,9 +2399,9 @@ void CharacterController::update(float duration, bool animationOnly)
bool isFirstPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson();
if (!sneak && jumpstate == JumpState_None && !isFirstPlayer && mPtr.getClass().isBipedal(mPtr))
{
if(rot.z() > rotationThreshold)
if(effectiveRotation > rotationThreshold)
movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight;
else if(rot.z() < -rotationThreshold)
else if(effectiveRotation < -rotationThreshold)
movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft;
}
}
@ -2509,9 +2524,9 @@ void CharacterController::update(float duration, bool animationOnly)
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
movement = vec;
cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0;
movementSettings.mPosition[0] = movementSettings.mPosition[1] = 0;
if (movement.z() == 0.f)
cls.getMovementSettings(mPtr).mPosition[2] = 0;
movementSettings.mPosition[2] = 0;
// Can't reset jump state (mPosition[2]) here in full; 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.
@ -2547,21 +2562,18 @@ void CharacterController::update(float duration, bool animationOnly)
if(speed > 0.f)
{
float l = moved.length();
if((movement.x() < 0.0f && movement.x() < moved.x()*2.0f) ||
(movement.x() > 0.0f && movement.x() > moved.x()*2.0f))
moved.x() = movement.x();
if((movement.y() < 0.0f && movement.y() < moved.y()*2.0f) ||
(movement.y() > 0.0f && movement.y() > moved.y()*2.0f))
moved.y() = movement.y();
if((movement.z() < 0.0f && movement.z() < moved.z()*2.0f) ||
(movement.z() > 0.0f && movement.z() > moved.z()*2.0f))
moved.z() = movement.z();
// but keep the original speed
if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 ||
std::abs(movement.y() - moved.y()) > std::abs(moved.y()) / 2 ||
std::abs(movement.z() - moved.z()) > std::abs(moved.z()) / 2)
{
moved = movement;
// For some creatures getSpeed doesn't work, so we adjust speed to the animation.
// TODO: Fix Creature::getSpeed.
float newLength = moved.length();
if (newLength > 0)
if (newLength > 0 && !cls.isNpc())
moved *= (l / newLength);
}
}
if (mFloatToSurface && cls.isActor() && cls.getCreatureStats(mPtr).isDead() && cls.canSwim(mPtr))
moved.z() = 1.0;

@ -195,6 +195,8 @@ class CharacterController : public MWRender::Animation::TextKeyListener
float mTimeUntilWake;
bool mIsMovingBackward;
void setAttackTypeBasedOnMovement();
void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false);

@ -34,7 +34,7 @@ namespace MWMechanics
mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false),
mHitRecovery(false), mBlock(false), mMovementFlags(0),
mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1),
mDeathAnimation(-1), mTimeOfDeath(), mLevel (0)
mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0)
{
for (int i=0; i<4; ++i)
mAiSettings[i] = 0;

@ -80,6 +80,9 @@ namespace MWMechanics
MWWorld::TimeStamp mTimeOfDeath;
// The difference between view direction and lower body direction.
float mSideMovementAngle;
public:
typedef std::pair<int, std::string> SummonKey; // <ESM::MagicEffect index, spell ID>
private:
@ -319,6 +322,9 @@ namespace MWMechanics
void addCorprusSpell(const std::string& sourceId, CorprusStats& stats);
void removeCorprusSpell(const std::string& sourceId);
float getSideMovementAngle() const { return mSideMovementAngle; }
void setSideMovementAngle(float angle) { mSideMovementAngle = angle; }
};
}

@ -250,10 +250,8 @@ namespace MWMechanics
invStore.autoEquip(ptr);
}
// mWatchedTimeToStartDrowning = -1 for correct drowning state check,
// if stats.getTimeToStartDrowning() == 0 already on game start
MechanicsManager::MechanicsManager()
: mWatchedLevel(-1), mWatchedTimeToStartDrowning(-1), mWatchedStatsEmpty (true), mUpdatePlayer (true), mClassSelected (false),
: mUpdatePlayer (true), mClassSelected (false),
mRaceSelected (false), mAI(true)
{
//buildPlayer no longer here, needs to be done explicitly after all subsystems are up and running
@ -275,16 +273,16 @@ namespace MWMechanics
void MechanicsManager::remove(const MWWorld::Ptr& ptr)
{
if(ptr == mWatched)
mWatched = MWWorld::Ptr();
if(ptr == MWBase::Environment::get().getWindowManager()->getWatchedActor())
MWBase::Environment::get().getWindowManager()->watchActor(MWWorld::Ptr());
mActors.removeActor(ptr);
mObjects.removeObject(ptr);
}
void MechanicsManager::updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr)
{
if(old == mWatched)
mWatched = ptr;
if(old == MWBase::Environment::get().getWindowManager()->getWatchedActor())
MWBase::Environment::get().getWindowManager()->watchActor(ptr);
if(ptr.getClass().isActor())
mActors.updateActor(old, ptr);
@ -292,19 +290,12 @@ namespace MWMechanics
mObjects.updateObject(old, ptr);
}
void MechanicsManager::drop(const MWWorld::CellStore *cellStore)
{
mActors.dropActors(cellStore, mWatched);
mActors.dropActors(cellStore, getPlayer());
mObjects.dropObjects(cellStore);
}
void MechanicsManager::watchActor(const MWWorld::Ptr& ptr)
{
mWatched = ptr;
}
void MechanicsManager::restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId)
{
auto& stats = actor.getClass().getCreatureStats (actor);
@ -325,133 +316,10 @@ namespace MWMechanics
void MechanicsManager::update(float duration, bool paused)
{
if(!mWatched.isEmpty())
{
MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager();
const MWMechanics::NpcStats &stats = mWatched.getClass().getNpcStats(mWatched);
for(int i = 0;i < ESM::Attribute::Length;++i)
{
if(stats.getAttribute(i) != mWatchedAttributes[i] || mWatchedStatsEmpty)
{
std::stringstream attrname;
attrname << "AttribVal"<<(i+1);
mWatchedAttributes[i] = stats.getAttribute(i);
winMgr->setValue(attrname.str(), stats.getAttribute(i));
}
}
if(stats.getHealth() != mWatchedHealth || mWatchedStatsEmpty)
{
static const std::string hbar("HBar");
mWatchedHealth = stats.getHealth();
winMgr->setValue(hbar, stats.getHealth());
}
if(stats.getMagicka() != mWatchedMagicka || mWatchedStatsEmpty)
{
static const std::string mbar("MBar");
mWatchedMagicka = stats.getMagicka();
winMgr->setValue(mbar, stats.getMagicka());
}
if(stats.getFatigue() != mWatchedFatigue || mWatchedStatsEmpty)
{
static const std::string fbar("FBar");
mWatchedFatigue = stats.getFatigue();
winMgr->setValue(fbar, stats.getFatigue());
}
float timeToDrown = stats.getTimeToStartDrowning();
if(timeToDrown != mWatchedTimeToStartDrowning)
{
static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fHoldBreathTime")->mValue.getFloat();
mWatchedTimeToStartDrowning = timeToDrown;
if(timeToDrown >= fHoldBreathTime || timeToDrown == -1.0) // -1.0 is a special value during initialization
winMgr->setDrowningBarVisibility(false);
else
{
winMgr->setDrowningBarVisibility(true);
winMgr->setDrowningTimeLeft(stats.getTimeToStartDrowning(), fHoldBreathTime);
}
}
//Loop over ESM::Skill::SkillEnum
for(int i = 0; i < ESM::Skill::Length; ++i)
{
if(stats.getSkill(i) != mWatchedSkills[i] || mWatchedStatsEmpty)
{
mWatchedSkills[i] = stats.getSkill(i);
winMgr->setValue((ESM::Skill::SkillEnum)i, stats.getSkill(i));
}
}
if(stats.getLevel() != mWatchedLevel)
{
mWatchedLevel = stats.getLevel();
winMgr->setValue("level", mWatchedLevel);
}
mWatchedStatsEmpty = false;
// Update the equipped weapon icon
MWWorld::InventoryStore& inv = mWatched.getClass().getInventoryStore(mWatched);
MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weapon == inv.end())
winMgr->unsetSelectedWeapon();
else
winMgr->setSelectedWeapon(*weapon);
// Update the selected spell icon
MWWorld::ContainerStoreIterator enchantItem = inv.getSelectedEnchantItem();
if (enchantItem != inv.end())
winMgr->setSelectedEnchantItem(*enchantItem);
else
{
const std::string& spell = winMgr->getSelectedSpell();
if (!spell.empty())
winMgr->setSelectedSpell(spell, int(getSpellSuccessChance(spell, mWatched)));
else
winMgr->unsetSelectedSpell();
}
}
if (mUpdatePlayer)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
// basic player profile; should not change anymore after the creation phase is finished.
MWBase::WindowManager *winMgr =
MWBase::Environment::get().getWindowManager();
const ESM::NPC *player =
world->getPlayerPtr().get<ESM::NPC>()->mBase;
const ESM::Race *race =
world->getStore().get<ESM::Race>().find(player->mRace);
const ESM::Class *cls =
world->getStore().get<ESM::Class>().find(player->mClass);
winMgr->setValue ("name", player->mName);
winMgr->setValue ("race", race->mName);
winMgr->setValue ("class", cls->mName);
mUpdatePlayer = false;
MWBase::WindowManager::SkillList majorSkills (5);
MWBase::WindowManager::SkillList minorSkills (5);
for (int i=0; i<5; ++i)
{
minorSkills[i] = cls->mData.mSkills[i][0];
majorSkills[i] = cls->mData.mSkills[i][1];
}
winMgr->configureSkills (majorSkills, minorSkills);
// HACK? The player has been changed, so a new Animation object may
// have been made for them. Make sure they're properly updated.
MWWorld::Ptr ptr = getPlayer();
@ -531,7 +399,7 @@ namespace MWMechanics
int MechanicsManager::getHoursToRest() const
{
return mActors.getHoursToRest(mWatched);
return mActors.getHoursToRest(getPlayer());
}
void MechanicsManager::setPlayerName (const std::string& name)
@ -693,7 +561,9 @@ namespace MWMechanics
int MechanicsManager::getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying)
{
if (ptr.getTypeName() == typeid(ESM::Creature).name())
// Make sure zero base price items/services can't be bought/sold for 1 gold
// and return the intended base price for creature merchants
if (basePrice == 0 || ptr.getTypeName() == typeid(ESM::Creature).name())
return basePrice;
const MWMechanics::NpcStats &sellerStats = ptr.getClass().getNpcStats(ptr);
@ -811,8 +681,11 @@ namespace MWMechanics
{
if (std::abs(c) < iPerMinChange)
{
x = 0;
y = -iPerMinChange;
// Deviating from Morrowind here: it doesn't increase disposition on marginal wins,
// which seems to be a bug (MCP fixes it too).
// Original logic: x = 0, y = -iPerMinChange
x = -iPerMinChange;
y = x; // This goes unused.
}
else
{

@ -21,20 +21,6 @@ namespace MWMechanics
{
class MechanicsManager : public MWBase::MechanicsManager
{
MWWorld::Ptr mWatched;
AttributeValue mWatchedAttributes[8];
SkillValue mWatchedSkills[27];
DynamicStat<float> mWatchedHealth;
DynamicStat<float> mWatchedMagicka;
DynamicStat<float> mWatchedFatigue;
int mWatchedLevel;
float mWatchedTimeToStartDrowning;
bool mWatchedStatsEmpty;
bool mUpdatePlayer;
bool mClassSelected;
bool mRaceSelected;
@ -68,10 +54,6 @@ namespace MWMechanics
virtual void drop(const MWWorld::CellStore *cellStore) override;
///< Deregister all objects in the given cell.
virtual void watchActor(const MWWorld::Ptr& ptr) override;
///< On each update look for changes in a previously registered actor and update the
/// GUI accordingly.
virtual void update (float duration, bool paused) override;
///< Update objects
///

@ -11,12 +11,14 @@ namespace MWMechanics
float mPosition[3];
float mRotation[3];
float mSpeedFactor;
bool mIsStrafing;
Movement()
{
mPosition[0] = mPosition[1] = mPosition[2] = 0.0f;
mRotation[0] = mRotation[1] = mRotation[2] = 0.0f;
mSpeedFactor = 1.f;
mIsStrafing = false;
}
osg::Vec3f asVec3()

@ -481,13 +481,24 @@ bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int
const ESM::RankData& rankData = faction.mData.mRankData[rank];
if (*iter<rankData.mSkill1)
if (*iter<rankData.mPrimarySkill)
return false;
if (skills.size() < 2)
return true;
return *++iter>=rankData.mSkill2;
iter++;
if (*iter<rankData.mFavouredSkill)
return false;
if (skills.size() < 3)
return true;
iter++;
if (*iter<rankData.mFavouredSkill)
return false;
return true;
}
bool MWMechanics::NpcStats::isWerewolf() const

@ -1102,7 +1102,6 @@ void LocalPlayer::setClass()
{
charClass.mData.mIsPlayable = 0x1;
MWBase::Environment::get().getMechanicsManager()->setPlayerClass(charClass);
MWBase::Environment::get().getWindowManager()->setPlayerClass(charClass);
}
else
{
@ -1111,7 +1110,6 @@ void LocalPlayer::setClass()
if (existingCharClass)
{
MWBase::Environment::get().getMechanicsManager()->setPlayerClass(charClass.mId);
MWBase::Environment::get().getWindowManager()->setPlayerClass(charClass);
}
else
LOG_APPEND(TimedLog::LOG_INFO, "- Ignored invalid default class %s", charClass.mId.c_str());

@ -84,12 +84,12 @@ namespace MWPhysics
assert (mShapeInstance->getCollisionShape()->isCompound());
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->getCollisionShape());
for (std::map<int, int>::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it)
for (const auto& shape : mShapeInstance->mAnimatedShapes)
{
int recIndex = it->first;
int shapeIndex = it->second;
int recIndex = shape.first;
int shapeIndex = shape.second;
std::map<int, osg::NodePath>::iterator nodePathFound = mRecIndexToNodePath.find(recIndex);
auto nodePathFound = mRecIndexToNodePath.find(recIndex);
if (nodePathFound == mRecIndexToNodePath.end())
{
NifOsg::FindGroupByRecIndex visitor(recIndex);
@ -104,7 +104,7 @@ namespace MWPhysics
}
osg::NodePath nodePath = visitor.mFoundPath;
nodePath.erase(nodePath.begin());
nodePathFound = mRecIndexToNodePath.insert(std::make_pair(recIndex, nodePath)).first;
nodePathFound = mRecIndexToNodePath.emplace(recIndex, nodePath).first;
}
osg::NodePath& nodePath = nodePathFound->second;

@ -95,21 +95,21 @@ namespace MWPhysics
if (mWaterCollisionObject.get())
mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get());
for (HeightFieldMap::iterator it = mHeightFields.begin(); it != mHeightFields.end(); ++it)
for (auto& heightField : mHeightFields)
{
mCollisionWorld->removeCollisionObject(it->second->getCollisionObject());
delete it->second;
mCollisionWorld->removeCollisionObject(heightField.second->getCollisionObject());
delete heightField.second;
}
for (ObjectMap::iterator it = mObjects.begin(); it != mObjects.end(); ++it)
for (auto& object : mObjects)
{
mCollisionWorld->removeCollisionObject(it->second->getCollisionObject());
delete it->second;
mCollisionWorld->removeCollisionObject(object.second->getCollisionObject());
delete object.second;
}
for (ActorMap::iterator it = mActors.begin(); it != mActors.end(); ++it)
for (auto& actor : mActors)
{
delete it->second;
delete actor.second;
}
delete mCollisionWorld;
@ -498,7 +498,7 @@ namespace MWPhysics
return;
Object *obj = new Object(ptr, shapeInstance);
mObjects.insert(std::make_pair(ptr, obj));
mObjects.emplace(ptr, obj);
if (obj->isAnimated())
mAnimatedObjects.insert(obj);
@ -540,10 +540,10 @@ namespace MWPhysics
map.erase(found);
}
for (CollisionMap::iterator it = map.begin(); it != map.end(); ++it)
for (auto& collision : map)
{
if (it->second == old)
it->second = updated;
if (collision.second == old)
collision.second = updated;
}
}
@ -555,7 +555,7 @@ namespace MWPhysics
Object* obj = found->second;
obj->updatePtr(updated);
mObjects.erase(found);
mObjects.insert(std::make_pair(updated, obj));
mObjects.emplace(updated, obj);
}
ActorMap::iterator foundActor = mActors.find(old);
@ -564,7 +564,7 @@ namespace MWPhysics
Actor* actor = foundActor->second;
actor->updatePtr(updated);
mActors.erase(foundActor);
mActors.insert(std::make_pair(updated, actor));
mActors.emplace(updated, actor);
}
updateCollisionMapPtr(mStandingCollisions, old, updated);
@ -652,7 +652,8 @@ namespace MWPhysics
}
}
void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) {
void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh)
{
osg::ref_ptr<const Resource::BulletShape> shape = mShapeManager->getShape(mesh);
if (!shape)
return;
@ -668,7 +669,7 @@ namespace MWPhysics
}
Actor* actor = new Actor(ptr, shape, mCollisionWorld);
mActors.insert(std::make_pair(ptr, actor));
mActors.emplace(ptr, actor);
}
bool PhysicsSystem::toggleCollisionMode()
@ -688,17 +689,16 @@ namespace MWPhysics
void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &movement)
{
PtrVelocityList::iterator iter = mMovementQueue.begin();
for(;iter != mMovementQueue.end();++iter)
for(auto& movementItem : mMovementQueue)
{
if(iter->first == ptr)
if (movementItem.first == ptr)
{
iter->second = movement;
movementItem.second = movement;
return;
}
}
mMovementQueue.push_back(std::make_pair(ptr, movement));
mMovementQueue.emplace_back(ptr, movement);
}
void PhysicsSystem::clearQueuedMovement()
@ -727,27 +727,26 @@ namespace MWPhysics
const MWWorld::Ptr player = MWMechanics::getPlayer();
const MWBase::World *world = MWBase::Environment::get().getWorld();
PtrVelocityList::iterator iter = mMovementQueue.begin();
for(;iter != mMovementQueue.end();++iter)
for(auto& movementItem : mMovementQueue)
{
ActorMap::iterator foundActor = mActors.find(iter->first);
ActorMap::iterator foundActor = mActors.find(movementItem.first);
if (foundActor == mActors.end()) // actor was already removed from the scene
continue;
Actor* physicActor = foundActor->second;
float waterlevel = -std::numeric_limits<float>::max();
const MWWorld::CellStore *cell = iter->first.getCell();
const MWWorld::CellStore *cell = movementItem.first.getCell();
if(cell->getCell()->hasWater())
waterlevel = cell->getWaterLevel();
const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects();
const MWMechanics::MagicEffects& effects = movementItem.first.getClass().getCreatureStats(movementItem.first).getMagicEffects();
bool waterCollision = false;
if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude())
{
if (!world->isUnderwater(iter->first.getCell(), osg::Vec3f(iter->first.getRefData().getPosition().asVec3())))
if (!world->isUnderwater(movementItem.first.getCell(), osg::Vec3f(movementItem.first.getRefData().getPosition().asVec3())))
waterCollision = true;
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(iter->first, waterlevel))
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(movementItem.first, waterlevel))
{
const osg::Vec3f actorPosition = physicActor->getPosition();
physicActor->setPosition(osg::Vec3f(actorPosition.x(), actorPosition.y(), waterlevel));
@ -759,8 +758,8 @@ namespace MWPhysics
// Slow fall reduces fall speed by a factor of (effect magnitude / 200)
float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f));
bool flying = world->isFlying(iter->first);
bool swimming = world->isSwimming(iter->first);
bool flying = world->isFlying(movementItem.first);
bool swimming = world->isSwimming(movementItem.first);
bool wasOnGround = physicActor->getOnGround();
osg::Vec3f position = physicActor->getPosition();
@ -768,7 +767,7 @@ namespace MWPhysics
bool positionChanged = false;
for (int i=0; i<numSteps; ++i)
{
position = MovementSolver::move(position, physicActor->getPtr(), physicActor, iter->second, mPhysicsDt,
position = MovementSolver::move(position, physicActor->getPtr(), physicActor, movementItem.second, mPhysicsDt,
flying, waterlevel, slowFall, mCollisionWorld, mStandingCollisions);
if (position != physicActor->getPosition())
positionChanged = true;
@ -782,14 +781,14 @@ namespace MWPhysics
float heightDiff = position.z() - oldHeight;
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
MWMechanics::CreatureStats& stats = movementItem.first.getClass().getCreatureStats(movementItem.first);
bool isStillOnGround = (numSteps > 0 && wasOnGround && physicActor->getOnGround());
if (isStillOnGround || flying || swimming || slowFall < 1)
stats.land(iter->first == player && (flying || swimming));
stats.land(movementItem.first == player && (flying || swimming));
else if (heightDiff < 0)
stats.addToFallHeight(-heightDiff);
mMovementResults.push_back(std::make_pair(iter->first, interpolated));
mMovementResults.emplace_back(movementItem.first, interpolated);
}
mMovementQueue.clear();
@ -823,9 +822,9 @@ namespace MWPhysics
bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const
{
for (CollisionMap::const_iterator it = mStandingCollisions.begin(); it != mStandingCollisions.end(); ++it)
for (const auto& standingActor : mStandingCollisions)
{
if (it->first == actor && it->second == object)
if (standingActor.first == actor && standingActor.second == object)
return true;
}
return false;
@ -833,10 +832,10 @@ namespace MWPhysics
void PhysicsSystem::getActorsStandingOn(const MWWorld::ConstPtr &object, std::vector<MWWorld::Ptr> &out) const
{
for (CollisionMap::const_iterator it = mStandingCollisions.begin(); it != mStandingCollisions.end(); ++it)
for (const auto& standingActor : mStandingCollisions)
{
if (it->second == object)
out.push_back(it->first);
if (standingActor.second == object)
out.push_back(standingActor.first);
}
}

@ -621,6 +621,8 @@ namespace MWRender
, mTextKeyListener(nullptr)
, mHeadYawRadians(0.f)
, mHeadPitchRadians(0.f)
, mUpperBodyYawRadians(0.f)
, mLegsYawRadians(0.f)
, mHasMagicEffects(false)
, mAlpha(1.f)
{
@ -1334,13 +1336,36 @@ namespace MWRender
updateEffects();
const float epsilon = 0.001f;
float yawOffset = 0;
if (mRootController)
{
bool enable = std::abs(mLegsYawRadians) > epsilon;
mRootController->setEnabled(enable);
if (enable)
{
mRootController->setRotate(osg::Quat(mLegsYawRadians, osg::Vec3f(0,0,1)));
yawOffset = mLegsYawRadians;
}
}
if (mSpineController)
{
float yaw = mUpperBodyYawRadians - yawOffset;
bool enable = std::abs(yaw) > epsilon;
mSpineController->setEnabled(enable);
if (enable)
{
mSpineController->setRotate(osg::Quat(yaw, osg::Vec3f(0,0,1)));
yawOffset = mUpperBodyYawRadians;
}
}
if (mHeadController)
{
const float epsilon = 0.001f;
bool enable = (std::abs(mHeadPitchRadians) > epsilon || std::abs(mHeadYawRadians) > epsilon);
float yaw = mHeadYawRadians - yawOffset;
bool enable = (std::abs(mHeadPitchRadians) > epsilon || std::abs(yaw) > epsilon);
mHeadController->setEnabled(enable);
if (enable)
mHeadController->setRotate(osg::Quat(mHeadPitchRadians, osg::Vec3f(1,0,0)) * osg::Quat(mHeadYawRadians, osg::Vec3f(0,0,1)));
mHeadController->setRotate(osg::Quat(mHeadPitchRadians, osg::Vec3f(1,0,0)) * osg::Quat(yaw, osg::Vec3f(0,0,1)));
}
// Scripted animations should not cause movement
@ -1460,6 +1485,8 @@ namespace MWRender
{
if (mLightListCallback)
mObjectRoot->removeCullCallback(mLightListCallback);
if (mTransparencyUpdater)
mObjectRoot->removeCullCallback(mTransparencyUpdater);
previousStateset = mObjectRoot->getStateSet();
mObjectRoot->getParent(0)->removeChild(mObjectRoot);
}
@ -1551,6 +1578,8 @@ namespace MWRender
if (!mLightListCallback)
mLightListCallback = new SceneUtil::LightListCallback;
mObjectRoot->addCullCallback(mLightListCallback);
if (mTransparencyUpdater)
mObjectRoot->addCullCallback(mTransparencyUpdater);
}
osg::Group* Animation::getObjectRoot()
@ -1801,13 +1830,17 @@ namespace MWRender
void Animation::addControllers()
{
mHeadController = nullptr;
NodeMap::const_iterator found = getNodeMap().find("bip01 head");
if (found == getNodeMap().end())
return;
mHeadController = addRotateController("bip01 head");
mSpineController = addRotateController("bip01 spine1");
mRootController = addRotateController("bip01");
}
osg::MatrixTransform* node = found->second;
RotateController* Animation::addRotateController(std::string bone)
{
auto iter = getNodeMap().find(bone);
if (iter == getNodeMap().end())
return nullptr;
osg::MatrixTransform* node = iter->second;
bool foundKeyframeCtrl = false;
osg::Callback* cb = node->getUpdateCallback();
@ -1820,13 +1853,15 @@ namespace MWRender
}
cb = cb->getNestedCallback();
}
// Without KeyframeController the orientation will not be reseted each frame, so
// RotateController shouldn't be used for such nodes.
if (!foundKeyframeCtrl)
return;
return nullptr;
mHeadController = new RotateController(mObjectRoot.get());
node->addUpdateCallback(mHeadController);
mActiveControllers.emplace_back(node, mHeadController);
RotateController* controller = new RotateController(mObjectRoot.get());
node->addUpdateCallback(controller);
mActiveControllers.emplace_back(node, controller);
return controller;
}
void Animation::setHeadPitch(float pitchRadians)

@ -267,8 +267,15 @@ protected:
TextKeyListener* mTextKeyListener;
osg::ref_ptr<RotateController> mHeadController;
osg::ref_ptr<RotateController> mSpineController;
osg::ref_ptr<RotateController> mRootController;
float mHeadYawRadians;
float mHeadPitchRadians;
float mUpperBodyYawRadians;
float mLegsYawRadians;
RotateController* addRotateController(std::string bone);
bool mHasMagicEffects;
osg::ref_ptr<SceneUtil::LightSource> mGlowLight;
@ -477,6 +484,12 @@ public:
virtual void setHeadYaw(float yawRadians);
virtual float getHeadPitch() const;
virtual float getHeadYaw() const;
virtual void setUpperBodyYawRadians(float v) { mUpperBodyYawRadians = v; }
virtual void setLegsYawRadians(float v) { mLegsYawRadians = v; }
virtual float getUpperBodyYawRadians() const { return mUpperBodyYawRadians; }
virtual float getLegsYawRadians() const { return mLegsYawRadians; }
virtual void setAccurateAiming(bool enabled) {}
virtual bool canBeHarvested() const { return false; }

@ -7,9 +7,13 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/ptr.hpp"
#include "../mwworld/refdata.hpp"
#include "../mwmechanics/drawstate.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "npcanimation.hpp"
namespace
@ -52,11 +56,14 @@ namespace MWRender
mFurthest(800.f),
mIsNearest(false),
mHeight(124.f),
mMaxCameraDistance(192.f),
mBaseCameraDistance(192.f),
mVanityToggleQueued(false),
mVanityToggleQueuedValue(false),
mViewModeToggleQueued(false),
mCameraDistance(0.f)
mCameraDistance(0.f),
mThirdPersonMode(ThirdPersonViewMode::Standard),
mOverShoulderOffset(osg::Vec2f(30.0f, -10.0f)),
mSmoothTransitionToCombatMode(0.f)
{
mVanity.enabled = false;
mVanity.allowed = true;
@ -68,7 +75,7 @@ namespace MWRender
mMainCam.yaw = 0.f;
mMainCam.offset = 400.f;
mCameraDistance = mMaxCameraDistance;
mCameraDistance = mBaseCameraDistance;
mUpdateCallback = new UpdateRenderCameraCallback(this);
mCamera->addUpdateCallback(mUpdateCallback);
@ -84,7 +91,7 @@ namespace MWRender
return mTrackingPtr;
}
osg::Vec3d Camera::getFocalPoint()
osg::Vec3d Camera::getFocalPoint() const
{
const osg::Node* trackNode = mTrackingNode;
if (!trackNode)
@ -96,22 +103,54 @@ namespace MWRender
osg::Vec3d position = worldMat.getTrans();
if (!isFirstPerson())
{
position.z() += mHeight * mHeightScale;
// We subtract 10.f here and add it within focalPointOffset in order to avoid camera clipping through ceiling.
// Needed because character's head can be a bit higher than collision area.
position.z() -= 10.f;
position += getFocalPointOffset() + mFocalPointAdjustment;
}
return position;
}
osg::Vec3d Camera::getFocalPointOffset() const
{
osg::Vec3d offset(0, 0, 10.f);
if (mThirdPersonMode == ThirdPersonViewMode::OverShoulder && !mPreviewMode && !mVanity.enabled)
{
float horizontalOffset = mOverShoulderOffset.x() * (1.f - mSmoothTransitionToCombatMode);
float verticalOffset = mSmoothTransitionToCombatMode * 15.f + (1.f - mSmoothTransitionToCombatMode) * mOverShoulderOffset.y();
offset.x() += horizontalOffset * cos(getYaw());
offset.y() += horizontalOffset * sin(getYaw());
offset.z() += verticalOffset;
}
return offset;
}
void Camera::getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const
{
focal = getFocalPoint();
osg::Vec3d offset(0,0,0);
if (!isFirstPerson())
{
osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1));
offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f);
}
camera = focal + offset;
}
void Camera::updateCamera(osg::Camera *cam)
{
if (mTrackingPtr.isEmpty())
return;
osg::Vec3d position = getFocalPoint();
osg::Vec3d focal, position;
getPosition(focal, position);
osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1));
osg::Vec3d offset = orient * osg::Vec3d(0, isFirstPerson() ? 0 : -mCameraDistance, 0);
position += offset;
osg::Vec3d forward = orient * osg::Vec3d(0,1,0);
osg::Vec3d up = orient * osg::Vec3d(0,0,1);
@ -164,14 +203,38 @@ namespace MWRender
if (paused)
return;
// only show the crosshair in game mode and in first person mode.
// only show the crosshair in game mode
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
wm->showCrosshair(!wm->isGuiMode() && (mFirstPersonView && !mVanity.enabled && !mPreviewMode));
wm->showCrosshair(!wm->isGuiMode() && !mVanity.enabled && !mPreviewMode
&& (mFirstPersonView || mThirdPersonMode != ThirdPersonViewMode::Standard));
if(mVanity.enabled)
{
rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true);
}
updateSmoothTransitionToCombatMode(duration);
}
void Camera::setOverShoulderOffset(float horizontal, float vertical)
{
mOverShoulderOffset = osg::Vec2f(horizontal, vertical);
}
void Camera::updateSmoothTransitionToCombatMode(float duration)
{
bool combatMode = true;
if (mTrackingPtr.getClass().isActor())
combatMode = mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing;
float speed = ((combatMode ? 1.f : 0.f) - mSmoothTransitionToCombatMode) * 5;
if (speed != 0)
speed += speed > 0 ? 1 : -1;
mSmoothTransitionToCombatMode += speed * duration;
if (mSmoothTransitionToCombatMode > 1)
mSmoothTransitionToCombatMode = 1;
if (mSmoothTransitionToCombatMode < 0)
mSmoothTransitionToCombatMode = 0;
}
void Camera::toggleViewMode(bool force)
@ -186,6 +249,9 @@ namespace MWRender
else
mViewModeToggleQueued = false;
if (mTrackingPtr.getClass().isActor())
mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).setSideMovementAngle(0);
mFirstPersonView = !mFirstPersonView;
processViewChange();
}
@ -259,7 +325,7 @@ namespace MWRender
mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset));
}
float Camera::getYaw()
float Camera::getYaw() const
{
if(mVanity.enabled || mPreviewMode)
return mPreviewCam.yaw;
@ -280,7 +346,7 @@ namespace MWRender
}
}
float Camera::getPitch()
float Camera::getPitch() const
{
if (mVanity.enabled || mPreviewMode) {
return mPreviewCam.pitch;
@ -314,7 +380,7 @@ namespace MWRender
return mCameraDistance;
}
void Camera::setCameraDistance(float dist, bool adjust, bool override)
void Camera::setBaseCameraDistance(float dist, bool adjust)
{
if(mFirstPersonView && !mPreviewMode && !mVanity.enabled)
return;
@ -322,34 +388,55 @@ namespace MWRender
mIsNearest = false;
if (adjust)
{
if (mVanity.enabled || mPreviewMode)
dist += mCameraDistance;
else
dist += std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance);
}
if (dist >= mFurthest) {
if (dist >= mFurthest)
dist = mFurthest;
} else if (!override && dist < 10.f) {
dist = 10.f;
} else if (override && dist <= mNearest) {
else if (dist <= mNearest)
{
dist = mNearest;
mIsNearest = true;
}
mCameraDistance = dist;
if (override) {
if (mVanity.enabled || mPreviewMode) {
mPreviewCam.offset = mCameraDistance;
} else if (!mFirstPersonView) {
mMaxCameraDistance = mCameraDistance;
if (mVanity.enabled || mPreviewMode)
mPreviewCam.offset = dist;
else if (!mFirstPersonView)
mBaseCameraDistance = dist;
setCameraDistance();
}
void Camera::setCameraDistance(float dist, bool adjust)
{
if(mFirstPersonView && !mPreviewMode && !mVanity.enabled)
return;
if (adjust) dist += mCameraDistance;
if (dist >= mFurthest)
dist = mFurthest;
else if (dist < 10.f)
dist = 10.f;
mCameraDistance = dist;
}
float Camera::getCameraDistanceCorrection() const
{
return mThirdPersonMode != ThirdPersonViewMode::Standard ? std::max(-getPitch(), 0.f) * 50.f : 0;
}
void Camera::setCameraDistance()
{
if (mVanity.enabled || mPreviewMode) {
if (mVanity.enabled || mPreviewMode)
mCameraDistance = mPreviewCam.offset;
} else if (!mFirstPersonView) {
mCameraDistance = mMaxCameraDistance;
}
else if (!mFirstPersonView)
mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection();
mFocalPointAdjustment = osg::Vec3d();
}
void Camera::setAnimation(NpcAnimation *anim)
@ -382,22 +469,12 @@ namespace MWRender
rotateCamera(getPitch(), getYaw(), false);
}
void Camera::getPosition(osg::Vec3f &focal, osg::Vec3f &camera)
{
focal = getFocalPoint();
osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1));
osg::Vec3d offset = orient * osg::Vec3d(0, isFirstPerson() ? 0 : -mCameraDistance, 0);
camera = focal + offset;
}
bool Camera::isVanityOrPreviewModeEnabled()
bool Camera::isVanityOrPreviewModeEnabled() const
{
return mPreviewMode || mVanity.enabled;
}
bool Camera::isNearest()
bool Camera::isNearest() const
{
return mIsNearest;
}

@ -23,6 +23,10 @@ namespace MWRender
/// \brief Camera control
class Camera
{
public:
enum class ThirdPersonViewMode {Standard, OverShoulder};
private:
struct CamData {
float pitch, yaw, offset;
};
@ -45,7 +49,7 @@ namespace MWRender
bool enabled, allowed;
} mVanity;
float mHeight, mMaxCameraDistance;
float mHeight, mBaseCameraDistance;
CamData mMainCam, mPreviewCam;
bool mVanityToggleQueued;
@ -54,6 +58,16 @@ namespace MWRender
float mCameraDistance;
ThirdPersonViewMode mThirdPersonMode;
osg::Vec2f mOverShoulderOffset;
osg::Vec3d mFocalPointAdjustment;
// Makes sense only if mThirdPersonMode is OverShoulder. Can be in range [0, 1].
// Used for smooth transition from non-combat camera position (0) to combat camera position (1).
float mSmoothTransitionToCombatMode;
void updateSmoothTransitionToCombatMode(float duration);
float getCameraDistanceCorrection() const;
osg::ref_ptr<osg::NodeCallback> mUpdateCallback;
public:
@ -62,6 +76,9 @@ namespace MWRender
MWWorld::Ptr getTrackingPtr() const;
void setThirdPersonViewMode(ThirdPersonViewMode mode) { mThirdPersonMode = mode; }
void setOverShoulderOffset(float horizontal, float vertical);
/// Update the view matrix of \a cam
void updateCamera(osg::Camera* cam);
@ -72,10 +89,10 @@ namespace MWRender
/// \param rot Rotation angles in radians
void rotateCamera(float pitch, float yaw, bool adjust);
float getYaw();
float getYaw() const;
void setYaw(float angle);
float getPitch();
float getPitch() const;
void setPitch(float angle);
/// Attach camera to object
@ -100,27 +117,32 @@ namespace MWRender
void update(float duration, bool paused=false);
/// Set base camera distance for current mode. Don't work on 1st person view.
/// \param adjust Indicates should distance be adjusted or set.
void setBaseCameraDistance(float dist, bool adjust = false);
/// Set camera distance for current mode. Don't work on 1st person view.
/// \param adjust Indicates should distance be adjusted or set.
/// \param override If true new distance will be used as default.
/// If false, default distance can be restored with setCameraDistance().
void setCameraDistance(float dist, bool adjust = false, bool override = true);
/// Default distance can be restored with setCameraDistance().
void setCameraDistance(float dist, bool adjust = false);
/// Restore default camera distance for current mode.
/// Restore default camera distance and offset for current mode.
void setCameraDistance();
float getCameraDistance() const;
void setAnimation(NpcAnimation *anim);
osg::Vec3d getFocalPoint();
osg::Vec3d getFocalPoint() const;
osg::Vec3d getFocalPointOffset() const;
void adjustFocalPoint(osg::Vec3d adjustment) { mFocalPointAdjustment = adjustment; }
/// Stores focal and camera world positions in passed arguments
void getPosition(osg::Vec3f &focal, osg::Vec3f &camera);
void getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const;
bool isVanityOrPreviewModeEnabled();
bool isVanityOrPreviewModeEnabled() const;
bool isNearest();
bool isNearest() const;
};
}

@ -194,8 +194,8 @@ namespace MWRender
if (!matrixTransform) return;
osg::Matrix worldToLocal = osg::Matrix::identity();
for (auto node : mNodePath)
if (const osg::Transform* t = node->asTransform())
for (auto pathNode : mNodePath)
if (const osg::Transform* t = pathNode->asTransform())
t->computeWorldToLocalMatrix(worldToLocal, nullptr);
worldToLocal = osg::Matrix::orthoNormal(worldToLocal);
@ -428,7 +428,7 @@ namespace MWRender
if (activeGrid)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
std::lock_guard<std::mutex> lock(mRefTrackerMutex);
for (auto ref : getRefTracker().mBlacklist)
refs.erase(ref);
}
@ -464,7 +464,7 @@ namespace MWRender
float dSqr = (viewPoint - pos).length2();
if (!activeGrid)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mSizeCacheMutex);
std::lock_guard<std::mutex> lock(mSizeCacheMutex);
SizeCache::iterator found = mSizeCache.find(pair.first);
if (found != mSizeCache.end() && found->second < dSqr*minSize*minSize)
continue;
@ -501,7 +501,7 @@ namespace MWRender
}
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
std::lock_guard<std::mutex> lock(mRefTrackerMutex);
if (getRefTracker().mDisabled.count(pair.first))
continue;
}
@ -509,7 +509,7 @@ namespace MWRender
float radius2 = cnode->getBound().radius2() * ref.mScale*ref.mScale;
if (radius2 < dSqr*minSize*minSize && !activeGrid)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mSizeCacheMutex);
std::lock_guard<std::mutex> lock(mSizeCacheMutex);
mSizeCache[pair.first] = radius2;
continue;
}
@ -685,7 +685,7 @@ namespace MWRender
return false;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
std::lock_guard<std::mutex> lock(mRefTrackerMutex);
if (enabled && !getWritableRefTracker().mDisabled.erase(refnum)) return false;
if (!enabled && !getWritableRefTracker().mDisabled.insert(refnum).second) return false;
if (mRefTrackerLocked) return false;
@ -706,7 +706,7 @@ namespace MWRender
return false;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
std::lock_guard<std::mutex> lock(mRefTrackerMutex);
if (!getWritableRefTracker().mBlacklist.insert(refnum).second) return false;
if (mRefTrackerLocked) return false;
}
@ -724,7 +724,7 @@ namespace MWRender
void ObjectPaging::clear()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
std::lock_guard<std::mutex> lock(mRefTrackerMutex);
mRefTrackerNew.mDisabled.clear();
mRefTrackerNew.mBlacklist.clear();
mRefTrackerLocked = true;
@ -734,7 +734,7 @@ namespace MWRender
{
if (!mRefTrackerLocked) return false;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
std::lock_guard<std::mutex> lock(mRefTrackerMutex);
mRefTrackerLocked = false;
if (mRefTracker == mRefTrackerNew)
return false;

@ -5,7 +5,7 @@
#include <components/resource/resourcemanager.hpp>
#include <components/esm/loadcell.hpp>
#include <OpenThreads/Mutex>
#include <mutex>
namespace Resource
{
@ -58,7 +58,7 @@ namespace MWRender
float mMinSizeMergeFactor;
float mMinSizeCostMultiplier;
OpenThreads::Mutex mRefTrackerMutex;
std::mutex mRefTrackerMutex;
struct RefTracker
{
std::set<ESM::RefNum> mDisabled;
@ -72,7 +72,7 @@ namespace MWRender
const RefTracker& getRefTracker() const { return mRefTracker; }
RefTracker& getWritableRefTracker() { return mRefTrackerLocked ? mRefTrackerNew : mRefTracker; }
OpenThreads::Mutex mSizeCacheMutex;
std::mutex mSizeCacheMutex;
typedef std::map<ESM::RefNum, float> SizeCache;
SizeCache mSizeCache;
};

@ -2,6 +2,8 @@
#include <limits>
#include <cstdlib>
#include <condition_variable>
#include <mutex>
#include <osg/Light>
#include <osg/LightModel>
@ -364,6 +366,7 @@ namespace MWRender
float firstPersonFov = Settings::Manager::getFloat("first person field of view", "Camera");
mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f);
mStateUpdater->setFogEnd(mViewDistance);
updateThirdPersonViewMode();
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip));
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance));
@ -379,6 +382,19 @@ namespace MWRender
mWorkQueue = nullptr;
}
void RenderingManager::updateThirdPersonViewMode()
{
if (Settings::Manager::getBool("view over shoulder", "Camera"))
mCamera->setThirdPersonViewMode(Camera::ThirdPersonViewMode::OverShoulder);
else
mCamera->setThirdPersonViewMode(Camera::ThirdPersonViewMode::Standard);
std::stringstream offset(Settings::Manager::getString("view over shoulder offset", "Camera"));
float horizontal = 30.f, vertical = -10.f;
offset >> horizontal >> vertical;
mCamera->setOverShoulderOffset(horizontal, vertical);
}
osgUtil::IncrementalCompileOperation* RenderingManager::getIncrementalCompileOperation()
{
return mViewer->getIncrementalCompileOperation();
@ -616,7 +632,7 @@ namespace MWRender
mCamera->update(dt, paused);
osg::Vec3f focal, cameraPos;
osg::Vec3d focal, cameraPos;
mCamera->getPosition(focal, cameraPos);
mCurrentCameraPos = cameraPos;
@ -695,24 +711,24 @@ namespace MWRender
virtual void operator () (osg::RenderInfo& renderInfo) const
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
std::lock_guard<std::mutex> lock(mMutex);
if (renderInfo.getState()->getFrameStamp()->getFrameNumber() >= mFrame)
{
mDone = true;
mCondition.signal();
mCondition.notify_one();
}
}
void waitTillDone()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
std::unique_lock<std::mutex> lock(mMutex);
if (mDone)
return;
mCondition.wait(&mMutex);
mCondition.wait(lock);
}
mutable OpenThreads::Condition mCondition;
mutable OpenThreads::Mutex mMutex;
mutable std::condition_variable mCondition;
mutable std::mutex mMutex;
mutable bool mDone;
unsigned int mFrame;
};
@ -1323,13 +1339,18 @@ namespace MWRender
{
if(mCamera->isNearest() && dist > 0.f)
mCamera->toggleViewMode();
else if (override)
mCamera->setBaseCameraDistance(-dist / 120.f * 10, adjust);
else
mCamera->setCameraDistance(-dist / 120.f * 10, adjust, override);
mCamera->setCameraDistance(-dist / 120.f * 10, adjust);
}
else if(mCamera->isFirstPerson() && dist < 0.f)
{
mCamera->toggleViewMode();
mCamera->setCameraDistance(0.f, false, override);
if (override)
mCamera->setBaseCameraDistance(0.f, false);
else
mCamera->setCameraDistance(0.f, false);
}
}
@ -1376,7 +1397,7 @@ namespace MWRender
void RenderingManager::changeVanityModeScale(float factor)
{
if(mCamera->isVanityOrPreviewModeEnabled())
mCamera->setCameraDistance(-factor/120.f*10, true, true);
mCamera->setBaseCameraDistance(-factor/120.f*10, true);
}
void RenderingManager::overrideFieldOfView(float val)

@ -252,6 +252,7 @@ namespace MWRender
void updateTextureFiltering();
void updateAmbient();
void setFogColor(const osg::Vec4f& color);
void updateThirdPersonViewMode();
void reportStats() const;

@ -4,6 +4,10 @@
#include <memory>
#include <array>
#include <atomic>
#include <condition_variable>
#include <thread>
#include <mutex>
#include <chrono>
#include <stdint.h>
@ -11,11 +15,6 @@
#include <components/misc/constants.hpp>
#include <components/vfs/manager.hpp>
#include <OpenThreads/Thread>
#include <OpenThreads/Condition>
#include <OpenThreads/Mutex>
#include <OpenThreads/ScopedLock>
#include "openal_output.hpp"
#include "sound_decoder.hpp"
#include "sound.hpp"
@ -309,31 +308,33 @@ const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f;
//
// A background streaming thread (keeps active streams processed)
//
struct OpenAL_Output::StreamThread : public OpenThreads::Thread {
struct OpenAL_Output::StreamThread
{
typedef std::vector<OpenAL_SoundStream*> StreamVec;
StreamVec mStreams;
std::atomic<bool> mQuitNow;
OpenThreads::Mutex mMutex;
OpenThreads::Condition mCondVar;
std::mutex mMutex;
std::condition_variable mCondVar;
std::thread mThread;
StreamThread()
: mQuitNow(false)
, mThread([this] { run(); })
{
start();
}
~StreamThread()
{
mQuitNow = true;
mMutex.lock(); mMutex.unlock();
mCondVar.broadcast();
join();
mCondVar.notify_all();
mThread.join();
}
// thread entry point
virtual void run()
void run()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
std::unique_lock<std::mutex> lock(mMutex);
while(!mQuitNow)
{
StreamVec::iterator iter = mStreams.begin();
@ -345,30 +346,30 @@ struct OpenAL_Output::StreamThread : public OpenThreads::Thread {
++iter;
}
mCondVar.wait(&mMutex, 50);
mCondVar.wait_for(lock, std::chrono::milliseconds(50));
}
}
void add(OpenAL_SoundStream *stream)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
std::lock_guard<std::mutex> lock(mMutex);
if(std::find(mStreams.begin(), mStreams.end(), stream) == mStreams.end())
{
mStreams.push_back(stream);
mCondVar.broadcast();
mCondVar.notify_all();
}
}
void remove(OpenAL_SoundStream *stream)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
std::lock_guard<std::mutex> lock(mMutex);
StreamVec::iterator iter = std::find(mStreams.begin(), mStreams.end(), stream);
if(iter != mStreams.end()) mStreams.erase(iter);
}
void removeAll()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
std::lock_guard<std::mutex> lock(mMutex);
mStreams.clear();
}
@ -1341,7 +1342,7 @@ double OpenAL_Output::getStreamOffset(Stream *sound)
{
if(!sound->mHandle) return 0.0;
OpenAL_SoundStream *stream = reinterpret_cast<OpenAL_SoundStream*>(sound->mHandle);
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mStreamThread->mMutex);
std::lock_guard<std::mutex> lock(mStreamThread->mMutex);
return stream->getStreamOffset();
}
@ -1349,7 +1350,7 @@ float OpenAL_Output::getStreamLoudness(Stream *sound)
{
if(!sound->mHandle) return 0.0;
OpenAL_SoundStream *stream = reinterpret_cast<OpenAL_SoundStream*>(sound->mHandle);
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mStreamThread->mMutex);
std::lock_guard<std::mutex> lock(mStreamThread->mMutex);
return stream->getCurrentLoudness();
}
@ -1357,7 +1358,7 @@ bool OpenAL_Output::isStreamPlaying(Stream *sound)
{
if(!sound->mHandle) return false;
OpenAL_SoundStream *stream = reinterpret_cast<OpenAL_SoundStream*>(sound->mHandle);
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mStreamThread->mMutex);
std::lock_guard<std::mutex> lock(mStreamThread->mMutex);
return stream->isPlaying();
}

@ -0,0 +1,71 @@
#include "regionsoundselector.hpp"
#include <components/misc/rng.hpp>
#include <algorithm>
#include <numeric>
#include "../mwbase/world.hpp"
#include "../mwworld/esmstore.hpp"
namespace MWSound
{
namespace
{
int addChance(int result, const ESM::Region::SoundRef &v)
{
return result + v.mChance;
}
}
boost::optional<std::string> RegionSoundSelector::getNextRandom(float duration, const std::string& regionName,
const MWBase::World& world)
{
mTimePassed += duration;
if (mTimePassed < mTimeToNextEnvSound)
return {};
const float a = Misc::Rng::rollClosedProbability();
// NOTE: We should use the "Minimum Time Between Environmental Sounds" and
// "Maximum Time Between Environmental Sounds" fallback settings here.
mTimeToNextEnvSound = 5.0f * a + 15.0f * (1.0f - a);
mTimePassed = 0;
if (mLastRegionName != regionName)
{
mLastRegionName = regionName;
mSumChance = 0;
}
const ESM::Region* const region = world.getStore().get<ESM::Region>().search(mLastRegionName);
if (region == nullptr)
return {};
if (mSumChance == 0)
{
mSumChance = std::accumulate(region->mSoundList.begin(), region->mSoundList.end(), 0, addChance);
if (mSumChance == 0)
return {};
}
const int r = Misc::Rng::rollDice(mSumChance);
int pos = 0;
const auto isSelected = [&] (const ESM::Region::SoundRef& sound)
{
if (r - pos < sound.mChance)
return true;
pos += sound.mChance;
return false;
};
const auto it = std::find_if(region->mSoundList.begin(), region->mSoundList.end(), isSelected);
if (it == region->mSoundList.end())
return {};
return it->mSound;
}
}

@ -0,0 +1,29 @@
#ifndef GAME_SOUND_REGIONSOUNDSELECTOR_H
#define GAME_SOUND_REGIONSOUNDSELECTOR_H
#include <boost/optional.hpp>
#include <string>
namespace MWBase
{
class World;
}
namespace MWSound
{
class RegionSoundSelector
{
public:
boost::optional<std::string> getNextRandom(float duration, const std::string& regionName,
const MWBase::World& world);
private:
float mTimeToNextEnvSound = 0.0f;
int mSumChance = 0;
std::string mLastRegionName;
float mTimePassed = 0.0;
};
}
#endif

@ -30,17 +30,32 @@
namespace MWSound
{
namespace
{
constexpr float sMinUpdateInterval = 1.0f / 30.0f;
WaterSoundUpdaterSettings makeWaterSoundUpdaterSettings()
{
WaterSoundUpdaterSettings settings;
settings.mNearWaterRadius = Fallback::Map::getInt("Water_NearWaterRadius");
settings.mNearWaterPoints = Fallback::Map::getInt("Water_NearWaterPoints");
settings.mNearWaterIndoorTolerance = Fallback::Map::getFloat("Water_NearWaterIndoorTolerance");
settings.mNearWaterOutdoorTolerance = Fallback::Map::getFloat("Water_NearWaterOutdoorTolerance");
settings.mNearWaterIndoorID = Misc::StringUtils::lowerCase(Fallback::Map::getString("Water_NearWaterIndoorID"));
settings.mNearWaterOutdoorID = Misc::StringUtils::lowerCase(Fallback::Map::getString("Water_NearWaterOutdoorID"));
return settings;
}
}
// For combining PlayMode and Type flags
inline int operator|(PlayMode a, Type b) { return static_cast<int>(a) | static_cast<int>(b); }
SoundManager::SoundManager(const VFS::Manager* vfs, bool useSound)
: mVFS(vfs)
, mOutput(new DEFAULT_OUTPUT(*this))
, mMasterVolume(1.0f)
, mSFXVolume(1.0f)
, mMusicVolume(1.0f)
, mVoiceVolume(1.0f)
, mFootstepsVolume(1.0f)
, mWaterSoundUpdater(makeWaterSoundUpdaterSettings())
, mSoundBuffers(new SoundBufferList::element_type())
, mBufferCacheSize(0)
, mSounds(new std::deque<Sound>())
@ -54,24 +69,6 @@ namespace MWSound
, mNearWaterSound(nullptr)
, mPlaybackPaused(false)
{
mMasterVolume = Settings::Manager::getFloat("master volume", "Sound");
mMasterVolume = std::min(std::max(mMasterVolume, 0.0f), 1.0f);
mSFXVolume = Settings::Manager::getFloat("sfx volume", "Sound");
mSFXVolume = std::min(std::max(mSFXVolume, 0.0f), 1.0f);
mMusicVolume = Settings::Manager::getFloat("music volume", "Sound");
mMusicVolume = std::min(std::max(mMusicVolume, 0.0f), 1.0f);
mVoiceVolume = Settings::Manager::getFloat("voice volume", "Sound");
mVoiceVolume = std::min(std::max(mVoiceVolume, 0.0f), 1.0f);
mFootstepsVolume = Settings::Manager::getFloat("footsteps volume", "Sound");
mFootstepsVolume = std::min(std::max(mFootstepsVolume, 0.0f), 1.0f);
mNearWaterRadius = Fallback::Map::getInt("Water_NearWaterRadius");
mNearWaterPoints = Fallback::Map::getInt("Water_NearWaterPoints");
mNearWaterIndoorTolerance = Fallback::Map::getFloat("Water_NearWaterIndoorTolerance");
mNearWaterOutdoorTolerance = Fallback::Map::getFloat("Water_NearWaterOutdoorTolerance");
mNearWaterIndoorID = Misc::StringUtils::lowerCase(Fallback::Map::getString("Water_NearWaterIndoorID"));
mNearWaterOutdoorID = Misc::StringUtils::lowerCase(Fallback::Map::getString("Water_NearWaterOutdoorID"));
mBufferCacheMin = std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1);
mBufferCacheMax = std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1);
mBufferCacheMax *= 1024*1024;
@ -338,26 +335,7 @@ namespace MWSound
// Gets the combined volume settings for the given sound type
float SoundManager::volumeFromType(Type type) const
{
float volume = mMasterVolume;
switch(type)
{
case Type::Sfx:
volume *= mSFXVolume;
break;
case Type::Voice:
volume *= mVoiceVolume;
break;
case Type::Foot:
volume *= mFootstepsVolume;
break;
case Type::Music:
volume *= mMusicVolume;
break;
case Type::Movie:
case Type::Mask:
break;
}
return volume;
return mVolumeSettings.getVolumeFromType(type);
}
void SoundManager::stopMusic()
@ -767,23 +745,12 @@ namespace MWSound
}
}
void SoundManager::stopSound(const std::string& soundId)
{
if(!mOutput->isInitialized())
return;
Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId));
if (!sfx) return;
stopSound(sfx, MWWorld::ConstPtr());
}
void SoundManager::stopSound3D(const MWWorld::ConstPtr &ptr, const std::string& soundId)
{
if(!mOutput->isInitialized())
return;
Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId));
Sound_Buffer *sfx = lookupSound(Misc::StringUtils::lowerCase(soundId));
if (!sfx) return;
stopSound(sfx, ptr);
@ -907,128 +874,66 @@ namespace MWSound
void SoundManager::updateRegionSound(float duration)
{
static float sTimeToNextEnvSound = 0.0f;
static int total = 0;
static std::string regionName = "";
static float sTimePassed = 0.0;
MWBase::World *world = MWBase::Environment::get().getWorld();
const MWWorld::ConstPtr player = world->getPlayerPtr();
const ESM::Cell *cell = player.getCell()->getCell();
sTimePassed += duration;
if(!cell->isExterior() || sTimePassed < sTimeToNextEnvSound)
return;
float a = Misc::Rng::rollClosedProbability();
// NOTE: We should use the "Minimum Time Between Environmental Sounds" and
// "Maximum Time Between Environmental Sounds" fallback settings here.
sTimeToNextEnvSound = 5.0f*a + 15.0f*(1.0f-a);
sTimePassed = 0;
if(regionName != cell->mRegion)
{
regionName = cell->mRegion;
total = 0;
}
const ESM::Region *regn = world->getStore().get<ESM::Region>().search(regionName);
if(regn == nullptr)
return;
if(total == 0)
{
for(const ESM::Region::SoundRef &sndref : regn->mSoundList)
total += (int)sndref.mChance;
if(total == 0)
if (!cell->isExterior())
return;
}
int r = Misc::Rng::rollDice(total);
int pos = 0;
for(const ESM::Region::SoundRef &sndref : regn->mSoundList)
{
if(r - pos < sndref.mChance)
{
playSound(sndref.mSound, 1.0f, 1.0f);
break;
}
pos += sndref.mChance;
}
if (const auto next = mRegionSoundSelector.getNextRandom(duration, cell->mRegion, *world))
playSound(*next, 1.0f, 1.0f);
}
void SoundManager::updateWaterSound(float /*duration*/)
void SoundManager::updateWaterSound()
{
static const ESM::Cell *LastCell;
MWBase::World* world = MWBase::Environment::get().getWorld();
const MWWorld::ConstPtr player = world->getPlayerPtr();
osg::Vec3f pos = player.getRefData().getPosition().asVec3();
const ESM::Cell *curcell = player.getCell()->getCell();
const auto update = mWaterSoundUpdater.update(player, *world);
float volume = 0.0f;
const std::string& soundId = player.getCell()->isExterior() ? mNearWaterOutdoorID : mNearWaterIndoorID;
WaterSoundAction action;
Sound_Buffer* sfx;
std::tie(action, sfx) = getWaterSoundAction(update, curcell);
if (!mListenerUnderwater)
switch (action)
{
if (curcell->hasWater())
{
float dist = std::abs(player.getCell()->getWaterLevel() - pos.z());
if (player.getCell()->isExterior() && dist < mNearWaterOutdoorTolerance)
{
volume = (mNearWaterOutdoorTolerance - dist) / mNearWaterOutdoorTolerance;
if (mNearWaterPoints > 1)
{
int underwaterPoints = 0;
float step = mNearWaterRadius * 2.0f / (mNearWaterPoints - 1);
for (int x = 0; x < mNearWaterPoints; x++)
{
for (int y = 0; y < mNearWaterPoints; y++)
{
float height = world->getTerrainHeightAt(
osg::Vec3f(pos.x() - mNearWaterRadius + x*step, pos.y() - mNearWaterRadius + y*step, 0.0f));
if (height < 0)
underwaterPoints++;
}
case WaterSoundAction::DoNothing:
break;
case WaterSoundAction::SetVolume:
mNearWaterSound->setVolume(update.mVolume * sfx->mVolume);
break;
case WaterSoundAction::FinishSound:
mOutput->finishSound(mNearWaterSound);
mNearWaterSound = nullptr;
break;
case WaterSoundAction::PlaySound:
if (mNearWaterSound)
mOutput->finishSound(mNearWaterSound);
mNearWaterSound = playSound(update.mId, update.mVolume, 1.0f, Type::Sfx, PlayMode::Loop);
break;
}
volume *= underwaterPoints * 2.0f / (mNearWaterPoints*mNearWaterPoints);
}
}
else if (!player.getCell()->isExterior() && dist < mNearWaterIndoorTolerance)
{
volume = (mNearWaterIndoorTolerance - dist) / mNearWaterIndoorTolerance;
}
mLastCell = curcell;
}
}
else
volume = 1.0f;
volume = std::min(volume, 1.0f);
if (mNearWaterSound)
{
if (volume == 0.0f)
std::pair<SoundManager::WaterSoundAction, Sound_Buffer*> SoundManager::getWaterSoundAction(
const WaterSoundUpdate& update, const ESM::Cell* cell) const
{
mOutput->finishSound(mNearWaterSound);
mNearWaterSound = nullptr;
}
else
if (mNearWaterSound)
{
if (update.mVolume == 0.0f)
return {WaterSoundAction::FinishSound, nullptr};
bool soundIdChanged = false;
Sound_Buffer *sfx = lookupSound(soundId);
if(LastCell != curcell)
Sound_Buffer* sfx = lookupSound(update.mId);
if (mLastCell != cell)
{
LastCell = curcell;
SoundMap::const_iterator snditer = mActiveSounds.find(MWWorld::Ptr());
if(snditer != mActiveSounds.end())
const auto snditer = mActiveSounds.find(MWWorld::ConstPtr());
if (snditer != mActiveSounds.end())
{
SoundBufferRefPairList::const_iterator pairiter = std::find_if(
const auto pairiter = std::find_if(
snditer->second.begin(), snditer->second.end(),
[this](const SoundBufferRefPairList::value_type &item) -> bool
{ return mNearWaterSound == item.first; }
@ -1038,20 +943,16 @@ namespace MWSound
}
}
if(soundIdChanged)
{
mOutput->finishSound(mNearWaterSound);
mNearWaterSound = playSound(soundId, volume, 1.0f, Type::Sfx, PlayMode::Loop);
}
else if (sfx)
mNearWaterSound->setVolume(volume * sfx->mVolume);
}
}
else if (volume > 0.0f)
{
LastCell = curcell;
mNearWaterSound = playSound(soundId, volume, 1.0f, Type::Sfx, PlayMode::Loop);
if (soundIdChanged)
return {WaterSoundAction::PlaySound, nullptr};
if (sfx)
return {WaterSoundAction::SetVolume, sfx};
}
else if (update.mVolume > 0.0f)
return {WaterSoundAction::PlaySound, nullptr};
return {WaterSoundAction::DoNothing, nullptr};
}
void SoundManager::updateSounds(float duration)
@ -1065,13 +966,11 @@ namespace MWSound
mSaySoundsQueue.erase(queuesayiter++);
}
static float timePassed = 0.0;
timePassed += duration;
if(timePassed < (1.0f/30.0f))
mTimePassed += duration;
if (mTimePassed < sMinUpdateInterval)
return;
duration = timePassed;
timePassed = 0.0f;
duration = mTimePassed;
mTimePassed = 0.0f;
// Make sure music is still playing
if(!isMusicPlaying() && !mCurrentPlaylist.empty())
@ -1235,18 +1134,14 @@ namespace MWSound
MWBase::StateManager::State_NoGame)
{
updateRegionSound(duration);
updateWaterSound(duration);
updateWaterSound();
}
}
void SoundManager::processChangedSettings(const Settings::CategorySettingVector& settings)
{
mMasterVolume = Settings::Manager::getFloat("master volume", "Sound");
mMusicVolume = Settings::Manager::getFloat("music volume", "Sound");
mSFXVolume = Settings::Manager::getFloat("sfx volume", "Sound");
mFootstepsVolume = Settings::Manager::getFloat("footsteps volume", "Sound");
mVoiceVolume = Settings::Manager::getFloat("voice volume", "Sound");
mVolumeSettings.update();
if(!mOutput->isInitialized())
return;
@ -1292,6 +1187,8 @@ namespace MWSound
mListenerUp = up;
mListenerUnderwater = underwater;
mWaterSoundUpdater.setUnderwater(underwater);
}
void SoundManager::updatePtr(const MWWorld::ConstPtr &old, const MWWorld::ConstPtr &updated)

@ -14,6 +14,11 @@
#include "../mwbase/soundmanager.hpp"
#include "regionsoundselector.hpp"
#include "watersoundupdater.hpp"
#include "type.hpp"
#include "volumesettings.hpp"
namespace VFS
{
class Manager;
@ -22,6 +27,7 @@ namespace VFS
namespace ESM
{
struct Sound;
struct Cell;
}
namespace MWSound
@ -53,18 +59,10 @@ namespace MWSound
std::unordered_map<std::string, std::vector<int>> mMusicToPlay; // A list with music files not yet played
std::string mLastPlayedMusic; // The music file that was last played
float mMasterVolume;
float mSFXVolume;
float mMusicVolume;
float mVoiceVolume;
float mFootstepsVolume;
int mNearWaterRadius;
int mNearWaterPoints;
float mNearWaterIndoorTolerance;
float mNearWaterOutdoorTolerance;
std::string mNearWaterIndoorID;
std::string mNearWaterOutdoorID;
VolumeSettings mVolumeSettings;
WaterSoundUpdater mWaterSoundUpdater;
typedef std::unique_ptr<std::deque<Sound_Buffer> > SoundBufferList;
// List of sound buffers, grown as needed. New enties are added to the
// back, allowing existing Sound_Buffer references/pointers to remain
@ -115,6 +113,12 @@ namespace MWSound
std::string mNextMusic;
bool mPlaybackPaused;
RegionSoundSelector mRegionSoundSelector;
float mTimePassed = 0;
const ESM::Cell *mLastCell = nullptr;
Sound_Buffer *insertSound(const std::string &soundId, const ESM::Sound *sound);
Sound_Buffer *lookupSound(const std::string &soundId) const;
@ -134,11 +138,22 @@ namespace MWSound
void updateSounds(float duration);
void updateRegionSound(float duration);
void updateWaterSound(float duration);
void updateWaterSound();
void updateMusic(float duration);
float volumeFromType(Type type) const;
enum class WaterSoundAction
{
DoNothing,
SetVolume,
FinishSound,
PlaySound,
};
std::pair<WaterSoundAction, Sound_Buffer*> getWaterSoundAction(const WaterSoundUpdate& update,
const ESM::Cell* cell) const;
SoundManager(const SoundManager &rhs);
SoundManager& operator=(const SoundManager &rhs);
@ -233,9 +248,6 @@ namespace MWSound
virtual void stopSound(const MWWorld::CellStore *cell);
///< Stop all sounds for the given cell.
virtual void stopSound(const std::string& soundId);
///< Stop a non-3d looping sound
virtual void fadeOutSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, float duration);
///< Fade out given sound (that is already playing) of given object
///< @param reference Reference to object, whose sound is faded out

@ -0,0 +1,17 @@
#ifndef GAME_SOUND_TYPE_H
#define GAME_SOUND_TYPE_H
namespace MWSound
{
enum class Type
{
Sfx = 1 << 4, /* Normal SFX sound */
Voice = 1 << 5, /* Voice sound */
Foot = 1 << 6, /* Footstep sound */
Music = 1 << 7, /* Music track */
Movie = 1 << 8, /* Movie audio track */
Mask = Sfx | Voice | Foot | Music | Movie
};
}
#endif

@ -0,0 +1,56 @@
#include "volumesettings.hpp"
#include <components/settings/settings.hpp>
#include <algorithm>
namespace MWSound
{
namespace
{
float clamp(float value)
{
return std::max(0.0f, std::min(1.0f, value));
}
}
VolumeSettings::VolumeSettings()
: mMasterVolume(clamp(Settings::Manager::getFloat("master volume", "Sound"))),
mSFXVolume(clamp(Settings::Manager::getFloat("sfx volume", "Sound"))),
mMusicVolume(clamp(Settings::Manager::getFloat("music volume", "Sound"))),
mVoiceVolume(clamp(Settings::Manager::getFloat("voice volume", "Sound"))),
mFootstepsVolume(clamp(Settings::Manager::getFloat("footsteps volume", "Sound")))
{
}
float VolumeSettings::getVolumeFromType(Type type) const
{
float volume = mMasterVolume;
switch(type)
{
case Type::Sfx:
volume *= mSFXVolume;
break;
case Type::Voice:
volume *= mVoiceVolume;
break;
case Type::Foot:
volume *= mFootstepsVolume;
break;
case Type::Music:
volume *= mMusicVolume;
break;
case Type::Movie:
case Type::Mask:
break;
}
return volume;
}
void VolumeSettings::update()
{
*this = VolumeSettings();
}
}

@ -0,0 +1,26 @@
#ifndef GAME_SOUND_VOLUMESETTINGS_H
#define GAME_SOUND_VOLUMESETTINGS_H
#include "type.hpp"
namespace MWSound
{
class VolumeSettings
{
public:
VolumeSettings();
float getVolumeFromType(Type type) const;
void update();
private:
float mMasterVolume;
float mSFXVolume;
float mMusicVolume;
float mVoiceVolume;
float mFootstepsVolume;
};
}
#endif

@ -0,0 +1,71 @@
#include "watersoundupdater.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/ptr.hpp"
#include <components/esm/loadcell.hpp>
#include <osg/Vec3f>
namespace MWSound
{
WaterSoundUpdater::WaterSoundUpdater(const WaterSoundUpdaterSettings& settings)
: mSettings(settings)
{
}
WaterSoundUpdate WaterSoundUpdater::update(const MWWorld::ConstPtr& player, const MWBase::World& world) const
{
WaterSoundUpdate result;
result.mId = player.getCell()->isExterior() ? mSettings.mNearWaterOutdoorID : mSettings.mNearWaterIndoorID;
result.mVolume = std::min(1.0f, getVolume(player, world));
return result;
}
float WaterSoundUpdater::getVolume(const MWWorld::ConstPtr& player, const MWBase::World& world) const
{
if (mListenerUnderwater)
return 1.0f;
const MWWorld::CellStore& cell = *player.getCell();
if (!cell.getCell()->hasWater())
return 0.0f;
const osg::Vec3f pos = player.getRefData().getPosition().asVec3();
const float dist = std::abs(cell.getWaterLevel() - pos.z());
if (cell.isExterior() && dist < mSettings.mNearWaterOutdoorTolerance)
{
if (mSettings.mNearWaterPoints <= 1)
return (mSettings.mNearWaterOutdoorTolerance - dist) / mSettings.mNearWaterOutdoorTolerance;
const float step = mSettings.mNearWaterRadius * 2.0f / (mSettings.mNearWaterPoints - 1);
int underwaterPoints = 0;
for (int x = 0; x < mSettings.mNearWaterPoints; x++)
{
for (int y = 0; y < mSettings.mNearWaterPoints; y++)
{
const float terrainX = pos.x() - mSettings.mNearWaterRadius + x * step;
const float terrainY = pos.y() - mSettings.mNearWaterRadius + y * step;
const float height = world.getTerrainHeightAt(osg::Vec3f(terrainX, terrainY, 0.0f));
if (height < 0)
underwaterPoints++;
}
}
return underwaterPoints * 2.0f / (mSettings.mNearWaterPoints * mSettings.mNearWaterPoints);
}
if (!cell.isExterior() && dist < mSettings.mNearWaterIndoorTolerance)
return (mSettings.mNearWaterIndoorTolerance - dist) / mSettings.mNearWaterIndoorTolerance;
return 0.0f;
}
}

@ -0,0 +1,54 @@
#ifndef GAME_SOUND_WATERSOUNDUPDATER_H
#define GAME_SOUND_WATERSOUNDUPDATER_H
#include <string>
namespace MWBase
{
class World;
}
namespace MWWorld
{
class ConstPtr;
}
namespace MWSound
{
struct WaterSoundUpdaterSettings
{
int mNearWaterRadius;
int mNearWaterPoints;
float mNearWaterIndoorTolerance;
float mNearWaterOutdoorTolerance;
std::string mNearWaterIndoorID;
std::string mNearWaterOutdoorID;
};
struct WaterSoundUpdate
{
std::string mId;
float mVolume;
};
class WaterSoundUpdater
{
public:
explicit WaterSoundUpdater(const WaterSoundUpdaterSettings& settings);
WaterSoundUpdate update(const MWWorld::ConstPtr& player, const MWBase::World& world) const;
void setUnderwater(bool value)
{
mListenerUnderwater = value;
}
private:
const WaterSoundUpdaterSettings mSettings;
bool mListenerUnderwater = false;
float getVolume(const MWWorld::ConstPtr& player, const MWBase::World& world) const;
};
}
#endif

@ -1,6 +1,8 @@
#include "scene.hpp"
#include <limits>
#include <chrono>
#include <thread>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
@ -798,15 +800,12 @@ namespace MWWorld
player.getClass().adjustPosition(player, true);
}
MWBase::MechanicsManager *mechMgr =
MWBase::Environment::get().getMechanicsManager();
mechMgr->updateCell(old, player);
mechMgr->watchActor(player);
MWBase::Environment::get().getMechanicsManager()->updateCell(old, player);
MWBase::Environment::get().getWindowManager()->watchActor(player);
mPhysics->updatePtr(old, player);
MWBase::Environment::get().getWorld()->adjustSky();
world->adjustSky();
mLastPlayerPos = player.getRefData().getPosition().asVec3();
}
@ -1215,7 +1214,7 @@ namespace MWWorld
}
else
loadingListener->setProgress(0);
OpenThreads::Thread::microSleep(5000);
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
}

@ -2055,15 +2055,35 @@ namespace MWWorld
int nightEye = static_cast<int>(player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::NightEye).getMagnitude());
mRendering->setNightEyeFactor(std::min(1.f, (nightEye/100.f)));
mRendering->getCamera()->setCameraDistance();
auto* camera = mRendering->getCamera();
camera->setCameraDistance();
if(!mRendering->getCamera()->isFirstPerson())
{
osg::Vec3f focal, camera;
mRendering->getCamera()->getPosition(focal, camera);
float radius = mRendering->getNearClipDistance()*2.5f;
MWPhysics::PhysicsSystem::RayResult result = mPhysics->castSphere(focal, camera, radius);
float cameraObstacleLimit = mRendering->getNearClipDistance() * 2.5f;
float focalObstacleLimit = std::max(cameraObstacleLimit, 10.0f);
// Adjust focal point.
osg::Vec3d focal = camera->getFocalPoint();
osg::Vec3d focalOffset = camera->getFocalPointOffset();
float offsetLen = focalOffset.length();
if (offsetLen > 0)
{
MWPhysics::PhysicsSystem::RayResult result = mPhysics->castSphere(focal - focalOffset, focal, focalObstacleLimit);
if (result.mHit)
{
double adjustmentCoef = -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen;
if (adjustmentCoef < -1)
adjustmentCoef = -1;
camera->adjustFocalPoint(focalOffset * adjustmentCoef);
}
}
// Adjust camera position.
osg::Vec3d cameraPos;
camera->getPosition(focal, cameraPos);
MWPhysics::PhysicsSystem::RayResult result = mPhysics->castSphere(focal, cameraPos, cameraObstacleLimit);
if (result.mHit)
mRendering->getCamera()->setCameraDistance((result.mHitPos - focal).length() - radius, false, false);
mRendering->getCamera()->setCameraDistance((result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length(), false);
}
}
@ -2688,7 +2708,7 @@ namespace MWWorld
rotateObject(player, 0.f, 0.f, 0.f, MWBase::RotationFlag_inverseOrder | MWBase::RotationFlag_adjust);
MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr());
MWBase::Environment::get().getMechanicsManager()->watchActor(getPlayerPtr());
MWBase::Environment::get().getWindowManager()->watchActor(getPlayerPtr());
std::string model = getPlayerPtr().getClass().getModel(getPlayerPtr());
model = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS());

@ -127,7 +127,12 @@ bool Wizard::IniSettings::writeFile(const QString &path, QTextStream &stream)
QString key(fullKey.at(1));
int index = buffer.lastIndexOf(section);
if (index != -1) {
if (index == -1) {
// Add the section to the end of the file, because it's not found
buffer.append(QString("\n%1\n").arg(section));
index = buffer.lastIndexOf(section);
}
// Look for the next section
index = buffer.indexOf(QLatin1Char('['), index + 1);
@ -141,12 +146,6 @@ bool Wizard::IniSettings::writeFile(const QString &path, QTextStream &stream)
buffer.insert(index - 1, QString("\n%1=%2").arg(key, i.value().toString()));
mSettings.remove(i.key());
}
} else {
// Add the section to the end of the file, because it's not found
buffer.append(QString("\n%1\n").arg(section));
i.previous();
}
}
// Now we reopen the file, this time we write

@ -120,9 +120,10 @@ void Wizard::MainWizard::addLogText(const QString &text)
QTextStream out(&file);
if (!text.isEmpty())
out << text << endl;
// file.close();
{
out << text << "\n";
out.flush();
}
}
void Wizard::MainWizard::setupGameSettings()

@ -163,7 +163,7 @@ macro (openmw_add_executable target)
if (MSVC)
if (CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8)
set_target_properties(${target} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "$(TargetDir)")
set_target_properties(${target} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "$<TARGET_FILE_DIR:${target}>")
endif (CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8)
endif (MSVC)
endmacro (openmw_add_executable)

@ -2,11 +2,8 @@
#include "launchersettings.hpp"
#include <QTextCodec>
#include <QTextStream>
#include <QDir>
#include <QString>
#include <QRegExp>
#include <QMap>
#include <components/files/configurationmanager.hpp>
@ -105,9 +102,9 @@ bool Config::GameSettings::readUserFile(QTextStream &stream)
return readFile(stream, mUserSettings);
}
bool Config::GameSettings::readFile(QTextStream &stream, QMap<QString, QString> &settings)
bool Config::GameSettings::readFile(QTextStream &stream, QMultiMap<QString, QString> &settings)
{
QMap<QString, QString> cache;
QMultiMap<QString, QString> cache;
QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$");
while (!stream.atEnd()) {
@ -151,7 +148,7 @@ bool Config::GameSettings::readFile(QTextStream &stream, QMap<QString, QString>
values.append(settings.values(key));
if (!values.contains(value)) {
cache.insertMulti(key, value);
cache.insert(key, value);
}
}
}
@ -368,7 +365,7 @@ bool Config::GameSettings::writeFileWithComments(QFile &file)
*iter = QString(); // assume no match
QString key = settingRegex.cap(1);
QString keyVal = settingRegex.cap(1)+"="+settingRegex.cap(2);
QMap<QString, QString>::const_iterator i = mUserSettings.find(key);
QMultiMap<QString, QString>::const_iterator i = mUserSettings.find(key);
while (i != mUserSettings.end() && i.key() == key)
{
QString settingLine = i.key() + "=" + i.value();

@ -5,7 +5,7 @@
#include <QStringList>
#include <QString>
#include <QFile>
#include <QMap>
#include <QMultiMap>
#include <boost/filesystem/path.hpp>
@ -31,7 +31,9 @@ namespace Config
inline void setValue(const QString &key, const QString &value)
{
mSettings.remove(key);
mSettings.insert(key, value);
mUserSettings.remove(key);
mUserSettings.insert(key, value);
}
@ -39,11 +41,11 @@ namespace Config
{
QStringList values = mSettings.values(key);
if (!values.contains(value))
mSettings.insertMulti(key, value);
mSettings.insert(key, value);
values = mUserSettings.values(key);
if (!values.contains(value))
mUserSettings.insertMulti(key, value);
mUserSettings.insert(key, value);
}
inline void remove(const QString &key)
@ -63,7 +65,7 @@ namespace Config
QStringList values(const QString &key, const QStringList &defaultValues = QStringList()) const;
bool readFile(QTextStream &stream);
bool readFile(QTextStream &stream, QMap<QString, QString> &settings);
bool readFile(QTextStream &stream, QMultiMap<QString, QString> &settings);
bool readUserFile(QTextStream &stream);
bool writeFile(QTextStream &stream);
@ -78,8 +80,8 @@ namespace Config
Files::ConfigurationManager &mCfgMgr;
void validatePaths();
QMap<QString, QString> mSettings;
QMap<QString, QString> mUserSettings;
QMultiMap<QString, QString> mSettings;
QMultiMap<QString, QString> mUserSettings;
QStringList mDataDirs;
QString mDataLocal;

@ -3,7 +3,7 @@
#include <QTextStream>
#include <QString>
#include <QRegExp>
#include <QMap>
#include <QMultiMap>
#include <QDebug>
@ -22,7 +22,7 @@ Config::LauncherSettings::~LauncherSettings()
QStringList Config::LauncherSettings::subKeys(const QString &key)
{
QMap<QString, QString> settings = SettingsBase::getSettings();
QMultiMap<QString, QString> settings = SettingsBase::getSettings();
QStringList keys = settings.uniqueKeys();
QRegExp keyRe("(.+)/");
@ -54,7 +54,7 @@ bool Config::LauncherSettings::writeFile(QTextStream &stream)
{
QString sectionPrefix;
QRegExp sectionRe("([^/]+)/(.+)$");
QMap<QString, QString> settings = SettingsBase::getSettings();
QMultiMap<QString, QString> settings = SettingsBase::getSettings();
QMapIterator<QString, QString> i(settings);
i.toBack();

@ -6,7 +6,7 @@
namespace Config
{
class LauncherSettings : public SettingsBase<QMap<QString, QString> >
class LauncherSettings : public SettingsBase<QMultiMap<QString, QString> >
{
public:
LauncherSettings();

@ -5,7 +5,7 @@
#include <QStringList>
#include <QString>
#include <QRegExp>
#include <QMap>
#include <QMultiMap>
namespace Config
{
@ -33,7 +33,7 @@ namespace Config
{
QStringList values = mSettings.values(key);
if (!values.contains(value))
mSettings.insertMulti(key, value);
mSettings.insert(key, value);
}
inline void setMultiValueEnabled(bool enable)
@ -83,8 +83,9 @@ namespace Config
if (!values.contains(value)) {
if (mMultiValue) {
cache.insertMulti(key, value);
cache.insert(key, value);
} else {
cache.remove(key);
cache.insert(key, value);
}
}

@ -122,7 +122,7 @@ namespace ESM
for (int i=0; i<10; ++i)
{
mData.mRankData[i].mAttribute1 = mData.mRankData[i].mAttribute2 = 0;
mData.mRankData[i].mSkill1 = mData.mRankData[i].mSkill2 = 0;
mData.mRankData[i].mPrimarySkill = mData.mRankData[i].mFavouredSkill = 0;
mData.mRankData[i].mFactReaction = 0;
mRanks[i].clear();

@ -19,10 +19,11 @@ struct RankData
{
int mAttribute1, mAttribute2; // Attribute level
int mSkill1, mSkill2; // Skill level (faction skills given in
// Skill level (faction skills given in
// skillID below.) You need one skill at
// level 'skill1' and two skills at level
// 'skill2' to advance to this rank.
// level 'mPrimarySkill' and two skills at level
// 'mFavouredSkill' to advance to this rank.
int mPrimarySkill, mFavouredSkill;
int mFactReaction; // Reaction from faction members
};

@ -2,8 +2,6 @@
#include <set>
#include <OpenThreads/ScopedLock>
#include <osg/Image>
#include <osg/Plane>
@ -548,7 +546,7 @@ namespace ESMTerrain
Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mLayerInfoMutex);
std::lock_guard<std::mutex> lock(mLayerInfoMutex);
// Already have this cached?
std::map<std::string, Terrain::LayerInfo>::iterator found = mLayerInfoMap.find(texture);

@ -2,8 +2,7 @@
#define COMPONENTS_ESM_TERRAIN_STORAGE_H
#include <cassert>
#include <OpenThreads/Mutex>
#include <mutex>
#include <components/terrain/storage.hpp>
@ -138,7 +137,7 @@ namespace ESMTerrain
std::string getTextureName (UniqueTextureId id);
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;
OpenThreads::Mutex mLayerInfoMutex;
std::mutex mLayerInfoMutex;
std::string mNormalMapPattern;
std::string mNormalHeightMapPattern;

@ -1,5 +1,7 @@
#include "nifloader.hpp"
#include <mutex>
#include <osg/Matrixf>
#include <osg/MatrixTransform>
#include <osg/Geometry>
@ -923,15 +925,18 @@ namespace NifOsg
osg::BoundingBox box;
int i=0;
for (std::vector<Nif::NiParticleSystemController::Particle>::const_iterator it = partctrl->particles.begin();
i<particledata->activeCount && it != partctrl->particles.end(); ++it, ++i)
for (const auto& particle : partctrl->particles)
{
const Nif::NiParticleSystemController::Particle& particle = *it;
if (i++ >= particledata->activeCount)
break;
if (particle.lifespan <= 0)
continue;
ParticleAgeSetter particletemplate(std::max(0.f, particle.lifetime));
osgParticle::Particle* created = partsys->createParticle(&particletemplate);
created->setLifeTime(std::max(0.f, particle.lifespan));
created->setLifeTime(particle.lifespan);
// Note this position and velocity is not correct for a particle system with absolute reference frame,
// which can not be done in this loader since we are not attached to the scene yet. Will be fixed up post-load in the SceneManager.
@ -970,6 +975,8 @@ namespace NifOsg
osgParticle::ConstantRateCounter* counter = new osgParticle::ConstantRateCounter;
if (partctrl->emitFlags & Nif::NiParticleSystemController::NoAutoAdjust)
counter->setNumberOfParticlesPerSecondToCreate(partctrl->emitRate);
else if (partctrl->lifetime == 0 && partctrl->lifetimeRandom == 0)
counter->setNumberOfParticlesPerSecondToCreate(0);
else
counter->setNumberOfParticlesPerSecondToCreate(partctrl->numParticles / (partctrl->lifetime + partctrl->lifetimeRandom/2));
@ -1725,8 +1732,8 @@ namespace NifOsg
{
typedef std::set<osg::ref_ptr<Attribute>, CompareStateAttribute> Cache;
static Cache sCache;
static OpenThreads::Mutex sMutex;
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(sMutex);
static std::mutex sMutex;
std::lock_guard<std::mutex> lock(sMutex);
typename Cache::iterator found = sCache.find(attr);
if (found == sCache.end())
found = sCache.insert(attr).first;

@ -125,7 +125,7 @@ void ParticleShooter::shoot(osgParticle::Particle *particle) const
particle->setVelocity(dir * vel);
// Not supposed to set this here, but there doesn't seem to be a better way of doing it
particle->setLifeTime(mLifetime + mLifetimeRandom * Misc::Rng::rollClosedProbability());
particle->setLifeTime(std::max(std::numeric_limits<float>::epsilon(), mLifetime + mLifetimeRandom * Misc::Rng::rollClosedProbability()));
}
GrowFadeAffector::GrowFadeAffector(float growTime, float fadeTime)
@ -184,6 +184,7 @@ ParticleColorAffector::ParticleColorAffector(const ParticleColorAffector &copy,
void ParticleColorAffector::operate(osgParticle::Particle* particle, double /* dt */)
{
assert(particle->getLifeTime() > 0);
float time = static_cast<float>(particle->getAge()/particle->getLifeTime());
osg::Vec4f color = mData.interpKey(time);
float alpha = color.a();

@ -21,7 +21,7 @@ namespace Resource
{
std::vector<osg::ref_ptr<osg::Object> > objectsToRemove;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
// Remove unreferenced entries from object cache
ObjectCacheMap::iterator oitr = _objectCache.begin();
@ -45,7 +45,7 @@ namespace Resource
void MultiObjectCache::clear()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
_objectCache.clear();
}
@ -56,13 +56,13 @@ namespace Resource
OSG_ALWAYS << " trying to add NULL object to cache for " << filename << std::endl;
return;
}
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
_objectCache.insert(std::make_pair(filename, object));
}
osg::ref_ptr<osg::Object> MultiObjectCache::takeFromObjectCache(const std::string &fileName)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
ObjectCacheMap::iterator found = _objectCache.find(fileName);
if (found == _objectCache.end())
return osg::ref_ptr<osg::Object>();
@ -76,7 +76,7 @@ namespace Resource
void MultiObjectCache::releaseGLObjects(osg::State *state)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
for(ObjectCacheMap::iterator itr = _objectCache.begin();
itr != _objectCache.end();
@ -89,7 +89,7 @@ namespace Resource
unsigned int MultiObjectCache::getCacheSize() const
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
return _objectCache.size();
}

@ -3,6 +3,7 @@
#include <map>
#include <string>
#include <mutex>
#include <osg/ref_ptr>
#include <osg/Referenced>
@ -43,7 +44,7 @@ namespace Resource
typedef std::multimap<std::string, osg::ref_ptr<osg::Object> > ObjectCacheMap;
ObjectCacheMap _objectCache;
mutable OpenThreads::Mutex _objectCacheMutex;
mutable std::mutex _objectCacheMutex;
};

@ -26,6 +26,7 @@
#include <string>
#include <map>
#include <mutex>
namespace osg
{
@ -53,7 +54,7 @@ class GenericObjectCache : public osg::Referenced
void updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime)
{
// look for objects with external references and update their time stamp.
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
for(typename ObjectCacheMap::iterator itr=_objectCache.begin(); itr!=_objectCache.end(); ++itr)
{
// If ref count is greater than 1, the object has an external reference.
@ -71,7 +72,7 @@ class GenericObjectCache : public osg::Referenced
{
std::vector<osg::ref_ptr<osg::Object> > objectsToRemove;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
// Remove expired entries from object cache
typename ObjectCacheMap::iterator oitr = _objectCache.begin();
while(oitr != _objectCache.end())
@ -92,21 +93,21 @@ class GenericObjectCache : public osg::Referenced
/** Remove all objects in the cache regardless of having external references or expiry times.*/
void clear()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
_objectCache.clear();
}
/** Add a key,object,timestamp triple to the Registry::ObjectCache.*/
void addEntryToObjectCache(const KeyType& key, osg::Object* object, double timestamp = 0.0)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
_objectCache[key]=ObjectTimeStampPair(object,timestamp);
}
/** Remove Object from cache.*/
void removeFromObjectCache(const KeyType& key)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
typename ObjectCacheMap::iterator itr = _objectCache.find(key);
if (itr!=_objectCache.end()) _objectCache.erase(itr);
}
@ -114,7 +115,7 @@ class GenericObjectCache : public osg::Referenced
/** Get an ref_ptr<Object> from the object cache*/
osg::ref_ptr<osg::Object> getRefFromObjectCache(const KeyType& key)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
typename ObjectCacheMap::iterator itr = _objectCache.find(key);
if (itr!=_objectCache.end())
return itr->second.first;
@ -124,7 +125,7 @@ class GenericObjectCache : public osg::Referenced
/** Check if an object is in the cache, and if it is, update its usage time stamp. */
bool checkInObjectCache(const KeyType& key, double timeStamp)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
typename ObjectCacheMap::iterator itr = _objectCache.find(key);
if (itr!=_objectCache.end())
{
@ -137,7 +138,7 @@ class GenericObjectCache : public osg::Referenced
/** call releaseGLObjects on all objects attached to the object cache.*/
void releaseGLObjects(osg::State* state)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
for(typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr)
{
osg::Object* object = itr->second.first.get();
@ -148,7 +149,7 @@ class GenericObjectCache : public osg::Referenced
/** call node->accept(nv); for all nodes in the objectCache. */
void accept(osg::NodeVisitor& nv)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
for(typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr)
{
osg::Object* object = itr->second.first.get();
@ -165,7 +166,7 @@ class GenericObjectCache : public osg::Referenced
template <class Functor>
void call(Functor& f)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it)
f(it->first, it->second.first.get());
}
@ -173,7 +174,7 @@ class GenericObjectCache : public osg::Referenced
/** Get the number of objects in the cache. */
unsigned int getCacheSize() const
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
std::lock_guard<std::mutex> lock(_objectCacheMutex);
return _objectCache.size();
}
@ -185,7 +186,7 @@ class GenericObjectCache : public osg::Referenced
typedef std::map<KeyType, ObjectTimeStampPair > ObjectCacheMap;
ObjectCacheMap _objectCache;
mutable OpenThreads::Mutex _objectCacheMutex;
mutable std::mutex _objectCacheMutex;
};

@ -126,7 +126,7 @@ namespace Resource
void clearCache()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_listMutex);
std::lock_guard<OpenThreads::Mutex> lock(_listMutex);
_sharedTextureList.clear();
_sharedStateSetList.clear();
}
@ -625,7 +625,7 @@ namespace Resource
mShaderManager->releaseGLObjects(state);
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mSharedStateMutex);
std::lock_guard<std::mutex> lock(mSharedStateMutex);
mSharedStateManager->releaseGLObjects(state);
}
@ -717,7 +717,7 @@ namespace Resource
if (mIncrementalCompileOperation)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*mIncrementalCompileOperation->getToCompiledMutex());
std::lock_guard<OpenThreads::Mutex> lock(*mIncrementalCompileOperation->getToCompiledMutex());
osgUtil::IncrementalCompileOperation::CompileSets& sets = mIncrementalCompileOperation->getToCompile();
for(osgUtil::IncrementalCompileOperation::CompileSets::iterator it = sets.begin(); it != sets.end();)
{
@ -738,7 +738,7 @@ namespace Resource
{
ResourceManager::clearCache();
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mSharedStateMutex);
std::lock_guard<std::mutex> lock(mSharedStateMutex);
mSharedStateManager->clearCache();
mInstanceCache->clear();
}
@ -747,12 +747,12 @@ namespace Resource
{
if (mIncrementalCompileOperation)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*mIncrementalCompileOperation->getToCompiledMutex());
std::lock_guard<OpenThreads::Mutex> lock(*mIncrementalCompileOperation->getToCompiledMutex());
stats->setAttribute(frameNumber, "Compiling", mIncrementalCompileOperation->getToCompile().size());
}
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mSharedStateMutex);
std::lock_guard<std::mutex> lock(mSharedStateMutex);
stats->setAttribute(frameNumber, "Texture", mSharedStateManager->getNumSharedTextures());
stats->setAttribute(frameNumber, "StateSet", mSharedStateManager->getNumSharedStateSets());
}

@ -4,6 +4,7 @@
#include <string>
#include <map>
#include <memory>
#include <mutex>
#include <osg/ref_ptr>
#include <osg/Node>
@ -159,7 +160,7 @@ namespace Resource
osg::ref_ptr<MultiObjectCache> mInstanceCache;
osg::ref_ptr<Resource::SharedStateManager> mSharedStateManager;
mutable OpenThreads::Mutex mSharedStateMutex;
mutable std::mutex mSharedStateMutex;
Resource::ImageManager* mImageManager;
Resource::NifFileManager* mNifFileManager;

@ -25,7 +25,7 @@ StatsHandler::StatsHandler():
_statsWidth(1280.0f),
_statsHeight(1024.0f),
_font(""),
_characterSize(20.0f)
_characterSize(18.0f)
{
_camera = new osg::Camera;
_camera->getOrCreateStateSet()->setGlobalDefaults();
@ -45,6 +45,8 @@ Profiler::Profiler()
else
_font = "";
_characterSize = 18;
setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3);
}

@ -891,7 +891,7 @@ MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData
MWShadowTechnique::ViewDependentData* MWShadowTechnique::getViewDependentData(osgUtil::CullVisitor* cv)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_viewDependentDataMapMutex);
std::lock_guard<std::mutex> lock(_viewDependentDataMapMutex);
ViewDependentDataMap::iterator itr = _viewDependentDataMap.find(cv);
if (itr!=_viewDependentDataMap.end()) return itr->second.get();
@ -1343,7 +1343,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
std::string validRegionUniformName = "validRegionMatrix" + std::to_string(sm_i);
osg::ref_ptr<osg::Uniform> validRegionUniform;
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_accessUniformsAndProgramMutex);
std::lock_guard<std::mutex> lock(_accessUniformsAndProgramMutex);
for (auto uniform : _uniforms)
{
@ -1467,7 +1467,7 @@ void MWShadowTechnique::createShaders()
unsigned int _baseTextureUnit = 0;
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_accessUniformsAndProgramMutex);
std::lock_guard<std::mutex> lock(_accessUniformsAndProgramMutex);
_shadowCastingStateSet = new osg::StateSet;
@ -2980,7 +2980,7 @@ osg::StateSet* MWShadowTechnique::selectStateSetForRenderingShadow(ViewDependent
osg::ref_ptr<osg::StateSet> stateset = vdd.getStateSet();
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_accessUniformsAndProgramMutex);
std::lock_guard<std::mutex> lock(_accessUniformsAndProgramMutex);
vdd.getStateSet()->clear();
@ -3057,7 +3057,7 @@ void MWShadowTechnique::resizeGLObjectBuffers(unsigned int /*maxSize*/)
void MWShadowTechnique::releaseGLObjects(osg::State* state) const
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_viewDependentDataMapMutex);
std::lock_guard<std::mutex> lock(_viewDependentDataMapMutex);
for(ViewDependentDataMap::const_iterator itr = _viewDependentDataMap.begin();
itr != _viewDependentDataMap.end();
++itr)

@ -19,6 +19,8 @@
#ifndef COMPONENTS_SCENEUTIL_MWSHADOWTECHNIQUE_H
#define COMPONENTS_SCENEUTIL_MWSHADOWTECHNIQUE_H 1
#include <mutex>
#include <osg/Camera>
#include <osg/Material>
#include <osg/MatrixTransform>
@ -234,7 +236,7 @@ namespace SceneUtil {
virtual ~MWShadowTechnique();
typedef std::map< osgUtil::CullVisitor*, osg::ref_ptr<ViewDependentData> > ViewDependentDataMap;
mutable OpenThreads::Mutex _viewDependentDataMapMutex;
mutable std::mutex _viewDependentDataMapMutex;
ViewDependentDataMap _viewDependentDataMap;
osg::ref_ptr<osg::StateSet> _shadowRecievingPlaceholderStateSet;
@ -245,7 +247,7 @@ namespace SceneUtil {
osg::ref_ptr<osg::Texture2D> _fallbackShadowMapTexture;
typedef std::vector< osg::ref_ptr<osg::Uniform> > Uniforms;
mutable OpenThreads::Mutex _accessUniformsAndProgramMutex;
mutable std::mutex _accessUniformsAndProgramMutex;
Uniforms _uniforms;
osg::ref_ptr<osg::Program> _program;

@ -22,7 +22,7 @@ namespace SceneUtil
}
}
osg::StateSet* stateset = mStateSets[nv->getTraversalNumber()%2];
osg::ref_ptr<osg::StateSet> stateset = mStateSets[nv->getTraversalNumber()%2];
apply(stateset, nv);
if (!isCullVisitor)

@ -2,69 +2,55 @@
#include <components/debug/debuglog.hpp>
#include <numeric>
namespace SceneUtil
{
void WorkItem::waitTillDone()
{
if (mDone > 0)
if (mDone)
return;
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
while (mDone == 0)
std::unique_lock<std::mutex> lock(mMutex);
while (!mDone)
{
mCondition.wait(&mMutex);
mCondition.wait(lock);
}
}
void WorkItem::signalDone()
{
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
mDone.exchange(1);
std::unique_lock<std::mutex> lock(mMutex);
mDone = true;
}
mCondition.broadcast();
}
WorkItem::WorkItem()
{
}
WorkItem::~WorkItem()
{
mCondition.notify_all();
}
bool WorkItem::isDone() const
{
return (mDone > 0);
return mDone;
}
WorkQueue::WorkQueue(int workerThreads)
: mIsReleased(false)
{
for (int i=0; i<workerThreads; ++i)
{
WorkThread* thread = new WorkThread(this);
mThreads.push_back(thread);
thread->startThread();
}
mThreads.emplace_back(std::make_unique<WorkThread>(*this));
}
WorkQueue::~WorkQueue()
{
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
std::unique_lock<std::mutex> lock(mMutex);
while (!mQueue.empty())
mQueue.pop_back();
mIsReleased = true;
mCondition.broadcast();
mCondition.notify_all();
}
for (unsigned int i=0; i<mThreads.size(); ++i)
{
mThreads[i]->join();
delete mThreads[i];
}
mThreads.clear();
}
void WorkQueue::addWorkItem(osg::ref_ptr<WorkItem> item, bool front)
@ -75,20 +61,20 @@ void WorkQueue::addWorkItem(osg::ref_ptr<WorkItem> item, bool front)
return;
}
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
std::unique_lock<std::mutex> lock(mMutex);
if (front)
mQueue.push_front(item);
else
mQueue.push_back(item);
mCondition.signal();
mCondition.notify_one();
}
osg::ref_ptr<WorkItem> WorkQueue::removeWorkItem()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
std::unique_lock<std::mutex> lock(mMutex);
while (mQueue.empty() && !mIsReleased)
{
mCondition.wait(&mMutex);
mCondition.wait(lock);
}
if (!mQueue.empty())
{
@ -102,25 +88,26 @@ osg::ref_ptr<WorkItem> WorkQueue::removeWorkItem()
unsigned int WorkQueue::getNumItems() const
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
std::unique_lock<std::mutex> lock(mMutex);
return mQueue.size();
}
unsigned int WorkQueue::getNumActiveThreads() const
{
unsigned int count = 0;
for (unsigned int i=0; i<mThreads.size(); ++i)
{
if (mThreads[i]->isActive())
++count;
}
return count;
return std::accumulate(mThreads.begin(), mThreads.end(), 0u,
[] (auto r, const auto& t) { return r + t->isActive(); });
}
WorkThread::WorkThread(WorkQueue *workQueue)
: mWorkQueue(workQueue)
WorkThread::WorkThread(WorkQueue& workQueue)
: mWorkQueue(&workQueue)
, mActive(false)
, mThread([this] { run(); })
{
}
WorkThread::~WorkThread()
{
mThread.join();
}
void WorkThread::run()

@ -1,16 +1,14 @@
#ifndef OPENMW_COMPONENTS_SCENEUTIL_WORKQUEUE_H
#define OPENMW_COMPONENTS_SCENEUTIL_WORKQUEUE_H
#include <OpenThreads/Atomic>
#include <OpenThreads/Mutex>
#include <OpenThreads/Condition>
#include <OpenThreads/Thread>
#include <osg/Referenced>
#include <osg/ref_ptr>
#include <atomic>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
namespace SceneUtil
{
@ -18,9 +16,6 @@ namespace SceneUtil
class WorkItem : public osg::Referenced
{
public:
WorkItem();
virtual ~WorkItem();
/// Override in a derived WorkItem to perform actual work.
virtual void doWork() {}
@ -35,10 +30,10 @@ namespace SceneUtil
/// Set abort flag in order to return from doWork() as soon as possible. May not be respected by all WorkItems.
virtual void abort() {}
protected:
OpenThreads::Atomic mDone;
OpenThreads::Mutex mMutex;
OpenThreads::Condition mCondition;
private:
std::atomic_bool mDone {false};
std::mutex mMutex;
std::condition_variable mCondition;
};
class WorkThread;
@ -70,25 +65,28 @@ namespace SceneUtil
bool mIsReleased;
std::deque<osg::ref_ptr<WorkItem> > mQueue;
mutable OpenThreads::Mutex mMutex;
OpenThreads::Condition mCondition;
mutable std::mutex mMutex;
std::condition_variable mCondition;
std::vector<WorkThread*> mThreads;
std::vector<std::unique_ptr<WorkThread>> mThreads;
};
/// Internally used by WorkQueue.
class WorkThread : public OpenThreads::Thread
class WorkThread
{
public:
WorkThread(WorkQueue* workQueue);
WorkThread(WorkQueue& workQueue);
virtual void run();
~WorkThread();
bool isActive() const;
private:
WorkQueue* mWorkQueue;
std::atomic<bool> mActive;
std::thread mThread;
void run();
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save