Add OpenMW commits up to 25 Jul 2020

# Conflicts:
#   .travis.yml
pull/593/head
David Cernat 5 years ago
commit ff10aa816b

@ -46,26 +46,184 @@ MacOS:
paths:
- build/OpenMW-*.dmg
Windows:
variables: &engine-targets
targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard"
variables: &cs-targets
targets: "openmw-cs,bsatool,esmtool,niftest"
.Windows_Ninja_Base:
tags:
- windows
before_script:
- Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
- choco install git --force --params "/GitAndUnixToolsOnPath" -y
- choco install 7zip -y
- choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y
- choco install vswhere -y
- choco install ninja -y
- choco install python -y
- refreshenv
stage: build
script:
- $time = (Get-Date -Format "HH:mm:ss")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N
- cd MSVC2019_64_Ninja
- .\ActivateMSVC.ps1
- cmake --build . --config $config --target ($targets.Split(','))
- cd $config
- |
if (Get-ChildItem -Recurse *.pdb) {
7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb'
Get-ChildItem -Recurse *.pdb | Remove-Item
}
- 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.zip '*'
cache:
key: ninja-v2
paths:
- deps
- MSVC2019_64_Ninja/deps/Qt
artifacts:
when: always
paths:
- "*.zip"
- "*.log"
- MSVC2019_64_Ninja/*.log
- MSVC2019_64_Ninja/*/*.log
- MSVC2019_64_Ninja/*/*/*.log
- MSVC2019_64_Ninja/*/*/*/*.log
- MSVC2019_64_Ninja/*/*/*/*/*.log
- MSVC2019_64_Ninja/*/*/*/*/*/*.log
- MSVC2019_64_Ninja/*/*/*/*/*/*/*.log
- MSVC2019_64_Ninja/*/*/*/*/*/*/*/*.log
Windows_Ninja_Engine_Release:
extends:
- .Windows_Ninja_Base
variables:
<<: *engine-targets
config: "Release"
Windows_Ninja_Engine_Debug:
extends:
- .Windows_Ninja_Base
variables:
<<: *engine-targets
config: "Debug"
Windows_Ninja_Engine_RelWithDebInfo:
extends:
- .Windows_Ninja_Base
variables:
<<: *engine-targets
config: "RelWithDebInfo"
Windows_Ninja_CS_Release:
extends:
- .Windows_Ninja_Base
variables:
<<: *cs-targets
config: "Release"
Windows_Ninja_CS_Debug:
extends:
- .Windows_Ninja_Base
variables:
<<: *cs-targets
config: "Debug"
Windows_Ninja_CS_RelWithDebInfo:
extends:
- .Windows_Ninja_Base
variables:
<<: *cs-targets
config: "RelWithDebInfo"
.Windows_MSBuild_Base:
tags:
- windows
before_script:
- Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
- choco install git --force --params "/GitAndUnixToolsOnPath" -y
- choco install 7zip -y
- choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y
- choco install vswhere -y
- choco install python -y
- refreshenv
stage: build
allow_failure: true
script:
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
- $time = (Get-Date -Format "HH:mm:ss")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
# TODO: to anyone wanting to do further work here, we need to figure out how to get the below working
# TODO: on gitlab's new shared windows runners. They currently don't have bash or anything else installed
# TODO: it is currently just a bare windows 10 with powershell.
# - env # turn on for debugging
# - sh %CI_PROJECT_DIR%/CI/before_script.msvc.sh -c Release -p x64 -v 2017 -V
# - SET msBuildLocation="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe"
# - call %msBuildLocation% MSVC2017_64\OpenMW.sln /t:Build /p:Configuration=Release /m:%NUMBER_OF_PROCESSORS%
# - 7z a OpenMW_MSVC2017_64_%CI_BUILD_REF_NAME%_%CI_BUILD_ID%.zip %CI_PROJECT_DIR%\MSVC2017_64\Release\
- sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V
- cd MSVC2019_64
- cmake --build . --config $config --target ($targets.Split(','))
- cd $config
- |
if (Get-ChildItem -Recurse *.pdb) {
7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb'
Get-ChildItem -Recurse *.pdb | Remove-Item
}
- 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.zip '*'
cache:
key: msbuild-v2
paths:
- deps
- MSVC2019_64/deps/Qt
artifacts:
when: always
paths:
- "*.zip"
- "*.log"
- MSVC2019_64/*.log
- MSVC2019_64/*/*.log
- MSVC2019_64/*/*/*.log
- MSVC2019_64/*/*/*/*.log
- MSVC2019_64/*/*/*/*/*.log
- MSVC2019_64/*/*/*/*/*/*.log
- MSVC2019_64/*/*/*/*/*/*/*.log
- MSVC2019_64/*/*/*/*/*/*/*/*.log
Windows_MSBuild_Engine_Release:
extends:
- .Windows_MSBuild_Base
variables:
<<: *engine-targets
config: "Release"
Windows_MSBuild_Engine_Debug:
extends:
- .Windows_MSBuild_Base
variables:
<<: *engine-targets
config: "Debug"
Windows_MSBuild_Engine_RelWithDebInfo:
extends:
- .Windows_MSBuild_Base
variables:
<<: *engine-targets
config: "RelWithDebInfo"
Windows_MSBuild_CS_Release:
extends:
- .Windows_MSBuild_Base
variables:
<<: *cs-targets
config: "Release"
Windows_MSBuild_CS_Debug:
extends:
- .Windows_MSBuild_Base
variables:
<<: *cs-targets
config: "Debug"
Windows_MSBuild_CS_RelWithDebInfo:
extends:
- .Windows_MSBuild_Base
variables:
<<: *cs-targets
config: "RelWithDebInfo"

@ -72,8 +72,8 @@ before_script:
script:
- cd ./build
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ${ANALYZE} make -j3; fi
# - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
# - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
- cd "${TRAVIS_BUILD_DIR}"

@ -8,6 +8,7 @@
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
Bug #4021: Attributes and skills are not stored as floats
Bug #4623: Corprus implementation is incorrect
Bug #4764: Data race in osg ParticleSystem
Bug #4774: Guards are ignorant of an invisible player that tries to attack them
Bug #5108: Savegame bloating due to inefficient fog textures format
Bug #5165: Active spells should use real time intead of timestamps
@ -18,6 +19,7 @@
Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures
Bug #5370: Opening an unlocked but trapped door uses the key
Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor
Bug #5387: Move/MoveWorld don't update the object's cell properly
Bug #5397: NPC greeting does not reset if you leave + reenter area
Bug #5400: Editor: Verifier checks race of non-skin bodyparts
Bug #5403: Enchantment effect doesn't show on an enemy during death animation
@ -38,6 +40,7 @@
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
Bug #5531: Actors flee using current rotation by axis x
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
@ -45,7 +48,10 @@
Feature #5445: Handle NiLines
Feature #5457: Realistic diagonal movement
Feature #5486: Fixes trainers to choose their training skills based on their base skill points
Feature #5524: Resume failed script execution after reload
Feature #5525: Search fields tweaks (utf-8)
Task #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation
0.46.0
------

@ -39,6 +39,10 @@ originalIFS="$IFS"
IFS=$'\n\r'
for pair in $(cmd //c "set"); do
IFS='=' read -r -a separatedPair <<< "${pair}"
if [ ${#separatedPair[@]} -ne 2 ]; then
echo "Parsed '$pair' as ${#separatedPair[@]} parts, expected 2."
continue
fi
originalCmdEnv["${separatedPair[0]}"]="${separatedPair[1]}"
done
@ -49,6 +53,10 @@ declare -A cmdEnvChanges
for pair in $cmdEnv; do
if [ -n "$pair" ]; then
IFS='=' read -r -a separatedPair <<< "${pair}"
if [ ${#separatedPair[@]} -ne 2 ]; then
echo "Parsed '$pair' as ${#separatedPair[@]} parts, expected 2."
continue
fi
key="${separatedPair[0]}"
value="${separatedPair[1]}"
if ! [ ${originalCmdEnv[$key]+_} ] || [ "${originalCmdEnv[$key]}" != "$value" ]; then

@ -20,7 +20,7 @@ cmake \
-D CMAKE_BUILD_TYPE=RELEASE \
-D OPENMW_OSX_DEPLOYMENT=TRUE \
-D BUILD_OPENMW=TRUE \
-D BUILD_OPENCS=FALSE \
-D BUILD_OPENCS=TRUE \
-D BUILD_ESMTOOL=TRUE \
-D BUILD_BSATOOL=TRUE \
-D BUILD_ESSIMPORTER=TRUE \

@ -19,15 +19,15 @@ Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg,
setupUi(this);
loadSettings();
mCellNameCompleter.setModel(&mCellNameCompleterModel);
startDefaultCharacterAtField->setCompleter(&mCellNameCompleter);
}
void Launcher::AdvancedPage::loadCellsForAutocomplete(QStringList cellNames) {
// Set up an auto-completer for the "Start default character at" field
auto *completer = new QCompleter(cellNames);
completer->setCompletionMode(QCompleter::PopupCompletion);
completer->setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
startDefaultCharacterAtField->setCompleter(completer);
// Update the list of suggestions for the "Start default character at" field
mCellNameCompleterModel.setStringList(cellNames);
mCellNameCompleter.setCompletionMode(QCompleter::PopupCompletion);
mCellNameCompleter.setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
}
void Launcher::AdvancedPage::on_skipMenuCheckBox_stateChanged(int state) {

@ -2,6 +2,8 @@
#define ADVANCEDPAGE_H
#include <QWidget>
#include <QCompleter>
#include <QStringListModel>
#include "ui_advancedpage.h"
@ -35,6 +37,8 @@ namespace Launcher
Files::ConfigurationManager &mCfgMgr;
Config::GameSettings &mGameSettings;
Settings::Manager &mEngineSettings;
QCompleter mCellNameCompleter;
QStringListModel mCellNameCompleterModel;
/**
* Load the cells associated with the given content files for use in autocomplete

@ -20,7 +20,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER})
add_openmw_dir (mwrender
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging
)

@ -1072,7 +1072,14 @@ void OMW::Engine::go()
if (stats)
{
const auto frameNumber = mViewer->getFrameStamp()->getFrameNumber();
mViewer->getViewerStats()->report(stats, frameNumber);
if (frameNumber >= 2)
{
mViewer->getViewerStats()->report(stats, frameNumber - 2);
osgViewer::Viewer::Cameras cameras;
mViewer->getCameras(cameras);
for (auto camera : cameras)
camera->getStats()->report(stats, frameNumber - 2);
}
}
mEnvironment.limitFrameRate(frameTimer.time_s());

@ -35,6 +35,8 @@ namespace MWBase
virtual ~ScriptManager() {}
virtual void clear() = 0;
virtual bool run (const std::string& name, Interpreter::Context& interpreterContext) = 0;
///< Run the script with the given name (compile first, if not compiled yet)

@ -312,6 +312,8 @@ namespace MWBase
virtual void update (float duration) = 0;
virtual void updateConsoleObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr) = 0;
/**
* Fetches a GMST string from the store, if there is no setting with the given
* ID or it is not a string the default string is returned.

@ -543,6 +543,8 @@ namespace MWBase
virtual void changeVanityModeScale(float factor) = 0;
virtual bool vanityRotateCamera(float * rot) = 0;
virtual void setCameraDistance(float dist, bool adjust = false, bool override = true)=0;
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
virtual void disableDeferredPreviewRotation() = 0;
virtual void setupPlayer() = 0;
virtual void renderPlayer() = 0;

@ -516,6 +516,12 @@ namespace MWGui
setCoord(10,10, width-10, height/2);
}
void Console::updateSelectedObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr)
{
if (mPtr == currentPtr)
mPtr = newPtr;
}
void Console::setSelectedObject(const MWWorld::Ptr& object)
{
if (!object.isEmpty())

@ -70,6 +70,8 @@ namespace MWGui
void executeFile (const std::string& path);
void updateSelectedObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr);
void clear();
virtual void resetReference ();

@ -69,8 +69,8 @@ namespace
return compareType(leftName, rightName);
// compare items by name
leftName = Misc::StringUtils::lowerCase(left.mBase.getClass().getName(left.mBase));
rightName = Misc::StringUtils::lowerCase(right.mBase.getClass().getName(right.mBase));
leftName = Misc::StringUtils::lowerCaseUtf8(left.mBase.getClass().getName(left.mBase));
rightName = Misc::StringUtils::lowerCaseUtf8(right.mBase.getClass().getName(right.mBase));
result = leftName.compare(rightName);
if (result != 0)
@ -213,7 +213,7 @@ namespace MWGui
if (!mNameFilter.empty())
{
const auto itemName = Misc::StringUtils::lowerCase(base.getClass().getName(base));
const auto itemName = Misc::StringUtils::lowerCaseUtf8(base.getClass().getName(base));
return itemName.find(mNameFilter) != std::string::npos;
}
@ -226,7 +226,7 @@ namespace MWGui
for (const auto& effect : effects)
{
const auto ciEffect = Misc::StringUtils::lowerCase(effect);
const auto ciEffect = Misc::StringUtils::lowerCaseUtf8(effect);
if (ciEffect.find(mEffectFilter) != std::string::npos)
return true;
@ -285,7 +285,7 @@ namespace MWGui
return false;
}
std::string compare = Misc::StringUtils::lowerCase(item.mBase.getClass().getName(item.mBase));
std::string compare = Misc::StringUtils::lowerCaseUtf8(item.mBase.getClass().getName(item.mBase));
if(compare.find(mNameFilter) == std::string::npos)
return false;
@ -318,12 +318,12 @@ namespace MWGui
void SortFilterItemModel::setNameFilter (const std::string& filter)
{
mNameFilter = Misc::StringUtils::lowerCase(filter);
mNameFilter = Misc::StringUtils::lowerCaseUtf8(filter);
}
void SortFilterItemModel::setEffectFilter (const std::string& filter)
{
mEffectFilter = Misc::StringUtils::lowerCase(filter);
mEffectFilter = Misc::StringUtils::lowerCaseUtf8(filter);
}
void SortFilterItemModel::update()

@ -136,27 +136,6 @@ namespace MWGui
}
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)

@ -595,6 +595,11 @@ namespace MWGui
}
}
void WindowManager::updateConsoleObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr)
{
mConsole->updateSelectedObjectPtr(currentPtr, newPtr);
}
void WindowManager::updateVisible()
{
bool loading = (getMode() == GM_Loading || getMode() == GM_LoadingWallpaper);

@ -308,6 +308,8 @@ namespace MWGui
virtual void unsetSelectedSpell();
virtual void unsetSelectedWeapon();
virtual void updateConsoleObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr);
virtual void showCrosshair(bool show);
virtual bool getSubtitlesEnabled();

@ -121,23 +121,21 @@ namespace MWInput
if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch"))
{
const float switchLimit = 0.25;
MWBase::World* world = MWBase::Environment::get().getWorld();
if (mBindingsManager->actionIsActive(A_TogglePOV))
{
if (mPreviewPOVDelay <= 0.5 &&
(mPreviewPOVDelay += dt) > 0.5)
{
mPreviewPOVDelay = 1.f;
MWBase::Environment::get().getWorld()->togglePreviewMode(true);
}
if (world->isFirstPerson() ? mPreviewPOVDelay > switchLimit : mPreviewPOVDelay == 0)
world->togglePreviewMode(true);
mPreviewPOVDelay += dt;
}
else
{
//disable preview mode
MWBase::Environment::get().getWorld()->togglePreviewMode(false);
if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= 0.5)
{
MWBase::Environment::get().getWorld()->togglePOV();
}
if (mPreviewPOVDelay > 0)
world->togglePreviewMode(false);
if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= switchLimit)
world->togglePOV();
mPreviewPOVDelay = 0.f;
}
}

@ -10,6 +10,7 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/esmstore.hpp"
@ -102,6 +103,8 @@ namespace MWInput
mMouseManager->update(dt);
mSensorManager->update(dt);
mActionManager->update(dt, controllerMove);
MWBase::Environment::get().getWorld()->applyDeferredPreviewRotationToPlayer(dt);
}
void InputManager::setDragDrop(bool dragDrop)

@ -108,6 +108,8 @@ namespace MWInput
player.yaw(x);
player.pitch(y);
}
else if (!input->getControlSwitch("playerlooking"))
MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation();
if (arg.zrel && input->getControlSwitch("playerviewswitch") && input->getControlSwitch("playercontrols")) //Check to make sure you are allowed to zoomout and there is a change
{
@ -207,17 +209,20 @@ namespace MWInput
return;
float rot[3];
rot[0] = yAxis * dt * 1000.0f * mCameraSensitivity * (mInvertY ? -1 : 1) * mCameraYMultiplier / 256.f;
rot[0] = -yAxis * dt * 1000.0f * mCameraSensitivity * (mInvertY ? -1 : 1) * mCameraYMultiplier / 256.f;
rot[1] = 0.0f;
rot[2] = xAxis * dt * 1000.0f * mCameraSensitivity * (mInvertX ? -1 : 1) / 256.f;
rot[2] = -xAxis * dt * 1000.0f * mCameraSensitivity * (mInvertX ? -1 : 1) / 256.f;
// Only actually turn player when we're not in vanity mode
if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols"))
bool controls = MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols");
if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && controls)
{
MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
player.yaw(rot[2]);
player.pitch(rot[0]);
player.yaw(-rot[2]);
player.pitch(-rot[0]);
}
else if (!controls)
MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation();
MWBase::Environment::get().getInputManager()->resetIdleTime();
}

@ -249,17 +249,20 @@ namespace MWInput
if (!mGuiCursorEnabled)
{
float rot[3];
rot[0] = mGyroYSpeed * dt * mGyroVSensitivity * 4 * (mInvertY ? -1 : 1);
rot[0] = -mGyroYSpeed * dt * mGyroVSensitivity * 4 * (mInvertY ? -1 : 1);
rot[1] = 0.0f;
rot[2] = mGyroXSpeed * dt * mGyroHSensitivity * 4 * (mInvertX ? -1 : 1);
rot[2] = -mGyroXSpeed * dt * mGyroHSensitivity * 4 * (mInvertX ? -1 : 1);
// Only actually turn player when we're not in vanity mode
if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"))
bool playerLooking = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking");
if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && playerLooking)
{
MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
player.yaw(rot[2]);
player.pitch(rot[0]);
player.yaw(-rot[2]);
player.pitch(-rot[0]);
}
else if (!playerLooking)
MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation();
MWBase::Environment::get().getInputManager()->resetIdleTime();
}

@ -60,13 +60,14 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterCont
// Make all nearby actors also avoid the door
std::vector<MWWorld::Ptr> actors;
MWBase::Environment::get().getMechanicsManager()->getActorsInRange(pos.asVec3(),100,actors);
for(std::vector<MWWorld::Ptr>::iterator it = actors.begin(); it != actors.end(); ++it) {
if(*it != getPlayer()) { //Not the player
MWMechanics::AiSequence& seq = it->getClass().getCreatureStats(*it).getAiSequence();
if(seq.getTypeId() != MWMechanics::AiPackageTypeId::AvoidDoor) { //Only add it once
seq.stack(MWMechanics::AiAvoidDoor(mDoorPtr),*it);
}
}
for(auto& actor : actors)
{
if (actor == getPlayer())
continue;
MWMechanics::AiSequence& seq = actor.getClass().getCreatureStats(actor).getAiSequence();
if (seq.getTypeId() != MWMechanics::AiPackageTypeId::AvoidDoor)
seq.stack(MWMechanics::AiAvoidDoor(mDoorPtr), actor);
}
return false;

@ -412,6 +412,7 @@ namespace MWMechanics
{
storage.mFleeBlindRunTimer += duration;
storage.mMovement.mRotation[0] = -actor.getRefData().getPosition().rot[0];
storage.mMovement.mRotation[2] = osg::PI + getZAngleToDir(target.getRefData().getPosition().asVec3()-actor.getRefData().getPosition().asVec3());
storage.mMovement.mPosition[1] = 1;
updateActorsMovement(actor, duration, storage);

@ -135,9 +135,9 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
followDistance = 313;
short i = 0;
followers.sort();
for (std::list<int>::iterator it = followers.begin(); it != followers.end(); ++it)
for (int followIndex : followers)
{
if (*it == mFollowIndex)
if (followIndex == mFollowIndex)
followDistance += 130 * i;
++i;
}

@ -406,36 +406,36 @@ void AiSequence::fill(const ESM::AIPackageList &list)
if (!list.mList.empty() && list.mList.begin() != (list.mList.end()-1))
mRepeat = true;
for (std::vector<ESM::AIPackage>::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it)
for (const auto& esmPackage : list.mList)
{
std::unique_ptr<MWMechanics::AiPackage> package;
if (it->mType == ESM::AI_Wander)
if (esmPackage.mType == ESM::AI_Wander)
{
ESM::AIWander data = it->mWander;
ESM::AIWander data = esmPackage.mWander;
std::vector<unsigned char> idles;
idles.reserve(8);
for (int i=0; i<8; ++i)
idles.push_back(data.mIdle[i]);
package = std::make_unique<MWMechanics::AiWander>(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mShouldRepeat != 0);
}
else if (it->mType == ESM::AI_Escort)
else if (esmPackage.mType == ESM::AI_Escort)
{
ESM::AITarget data = it->mTarget;
ESM::AITarget data = esmPackage.mTarget;
package = std::make_unique<MWMechanics::AiEscort>(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ);
}
else if (it->mType == ESM::AI_Travel)
else if (esmPackage.mType == ESM::AI_Travel)
{
ESM::AITravel data = it->mTravel;
ESM::AITravel data = esmPackage.mTravel;
package = std::make_unique<MWMechanics::AiTravel>(data.mX, data.mY, data.mZ);
}
else if (it->mType == ESM::AI_Activate)
else if (esmPackage.mType == ESM::AI_Activate)
{
ESM::AIActivate data = it->mActivate;
ESM::AIActivate data = esmPackage.mActivate;
package = std::make_unique<MWMechanics::AiActivate>(data.mName.toString());
}
else //if (it->mType == ESM::AI_Follow)
else //if (esmPackage.mType == ESM::AI_Follow)
{
ESM::AITarget data = it->mTarget;
ESM::AITarget data = esmPackage.mTarget;
package = std::make_unique<MWMechanics::AiFollow>(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ);
}
mPackages.push_back(std::move(package));
@ -457,10 +457,9 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
// If there is more than one non-combat, non-pursue package in the list, enable repeating.
int count = 0;
for (std::vector<ESM::AiSequence::AiPackageContainer>::const_iterator it = sequence.mPackages.begin();
it != sequence.mPackages.end(); ++it)
for (auto& container : sequence.mPackages)
{
if (isActualAiPackage(static_cast<AiPackageTypeId>(it->mType)))
if (isActualAiPackage(static_cast<AiPackageTypeId>(container.mType)))
count++;
}
@ -468,20 +467,19 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
mRepeat = true;
// Load packages
for (std::vector<ESM::AiSequence::AiPackageContainer>::const_iterator it = sequence.mPackages.begin();
it != sequence.mPackages.end(); ++it)
for (auto& container : sequence.mPackages)
{
std::unique_ptr<MWMechanics::AiPackage> package;
switch (it->mType)
switch (container.mType)
{
case ESM::AiSequence::Ai_Wander:
{
package.reset(new AiWander(static_cast<ESM::AiSequence::AiWander*>(it->mPackage)));
package.reset(new AiWander(static_cast<ESM::AiSequence::AiWander*>(container.mPackage)));
break;
}
case ESM::AiSequence::Ai_Travel:
{
const auto source = static_cast<const ESM::AiSequence::AiTravel*>(it->mPackage);
const auto source = static_cast<const ESM::AiSequence::AiTravel*>(container.mPackage);
if (source->mHidden)
package.reset(new AiInternalTravel(source));
else
@ -490,27 +488,27 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
}
case ESM::AiSequence::Ai_Escort:
{
package.reset(new AiEscort(static_cast<ESM::AiSequence::AiEscort*>(it->mPackage)));
package.reset(new AiEscort(static_cast<ESM::AiSequence::AiEscort*>(container.mPackage)));
break;
}
case ESM::AiSequence::Ai_Follow:
{
package.reset(new AiFollow(static_cast<ESM::AiSequence::AiFollow*>(it->mPackage)));
package.reset(new AiFollow(static_cast<ESM::AiSequence::AiFollow*>(container.mPackage)));
break;
}
case ESM::AiSequence::Ai_Activate:
{
package.reset(new AiActivate(static_cast<ESM::AiSequence::AiActivate*>(it->mPackage)));
package.reset(new AiActivate(static_cast<ESM::AiSequence::AiActivate*>(container.mPackage)));
break;
}
case ESM::AiSequence::Ai_Combat:
{
package.reset(new AiCombat(static_cast<ESM::AiSequence::AiCombat*>(it->mPackage)));
package.reset(new AiCombat(static_cast<ESM::AiSequence::AiCombat*>(container.mPackage)));
break;
}
case ESM::AiSequence::Ai_Pursue:
{
package.reset(new AiPursue(static_cast<ESM::AiSequence::AiPursue*>(it->mPackage)));
package.reset(new AiPursue(static_cast<ESM::AiSequence::AiPursue*>(container.mPackage)));
break;
}
default:

@ -809,11 +809,11 @@ namespace MWMechanics
void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage)
{
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(npcPos));
for (std::vector<ESM::Pathgrid::Edge>::const_iterator it = pathGrid->mEdges.begin(); it != pathGrid->mEdges.end(); ++it)
for (auto& edge : pathGrid->mEdges)
{
if (it->mV0 == pointIndex)
if (edge.mV0 == pointIndex)
{
AddPointBetweenPathGridPoints(pathGrid->mPoints[it->mV0], pathGrid->mPoints[it->mV1], storage);
AddPointBetweenPathGridPoints(pathGrid->mPoints[edge.mV0], pathGrid->mPoints[edge.mV1], storage);
}
}
}

@ -61,38 +61,36 @@ namespace MWMechanics
// Note: the algorithm heavily depends on the traversal order of the spells. For vanilla-compatible results the
// Store must preserve the record ordering as it was in the content files.
for (MWWorld::Store<ESM::Spell>::iterator iter = spells.begin(); iter != spells.end(); ++iter)
for (const ESM::Spell& spell : spells)
{
const ESM::Spell* spell = &*iter;
if (spell->mData.mType != ESM::Spell::ST_Spell)
if (spell.mData.mType != ESM::Spell::ST_Spell)
continue;
if (!(spell->mData.mFlags & ESM::Spell::F_Autocalc))
if (!(spell.mData.mFlags & ESM::Spell::F_Autocalc))
continue;
static const int iAutoSpellTimesCanCast = gmst.find("iAutoSpellTimesCanCast")->mValue.getInteger();
if (baseMagicka < iAutoSpellTimesCanCast * spell->mData.mCost)
if (baseMagicka < iAutoSpellTimesCanCast * spell.mData.mCost)
continue;
if (race && race->mPowers.exists(spell->mId))
if (race && race->mPowers.exists(spell.mId))
continue;
if (!attrSkillCheck(spell, actorSkills, actorAttributes))
if (!attrSkillCheck(&spell, actorSkills, actorAttributes))
continue;
int school;
float skillTerm;
calcWeakestSchool(spell, actorSkills, school, skillTerm);
calcWeakestSchool(&spell, actorSkills, school, skillTerm);
assert(school >= 0 && school < 6);
SchoolCaps& cap = schoolCaps[school];
if (cap.mReachedLimit && spell->mData.mCost <= cap.mMinCost)
if (cap.mReachedLimit && spell.mData.mCost <= cap.mMinCost)
continue;
static const float fAutoSpellChance = gmst.find("fAutoSpellChance")->mValue.getFloat();
if (calcAutoCastChance(spell, actorSkills, actorAttributes, school) < fAutoSpellChance)
if (calcAutoCastChance(&spell, actorSkills, actorAttributes, school) < fAutoSpellChance)
continue;
selectedSpells.push_back(spell->mId);
selectedSpells.push_back(spell.mId);
if (cap.mReachedLimit)
{
@ -101,9 +99,9 @@ namespace MWMechanics
selectedSpells.erase(found);
cap.mMinCost = std::numeric_limits<int>::max();
for (std::vector<std::string>::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt)
for (const std::string& testSpellName : selectedSpells)
{
const ESM::Spell* testSpell = spells.find(*weakIt);
const ESM::Spell* testSpell = spells.find(testSpellName);
//int testSchool;
//float dummySkillTerm;
@ -130,10 +128,10 @@ namespace MWMechanics
if (cap.mCount == cap.mLimit)
cap.mReachedLimit = true;
if (spell->mData.mCost < cap.mMinCost)
if (spell.mData.mCost < cap.mMinCost)
{
cap.mWeakestSpell = spell->mId;
cap.mMinCost = spell->mData.mCost;
cap.mWeakestSpell = spell.mId;
cap.mMinCost = spell.mData.mCost;
}
}
}
@ -154,32 +152,28 @@ namespace MWMechanics
std::vector<std::string> selectedSpells;
const MWWorld::Store<ESM::Spell> &spells =
esmStore.get<ESM::Spell>();
for (MWWorld::Store<ESM::Spell>::iterator iter = spells.begin(); iter != spells.end(); ++iter)
const MWWorld::Store<ESM::Spell> &spells = esmStore.get<ESM::Spell>();
for (const ESM::Spell& spell : spells)
{
const ESM::Spell* spell = &*iter;
if (spell->mData.mType != ESM::Spell::ST_Spell)
if (spell.mData.mType != ESM::Spell::ST_Spell)
continue;
if (!(spell->mData.mFlags & ESM::Spell::F_PCStart))
if (!(spell.mData.mFlags & ESM::Spell::F_PCStart))
continue;
if (reachedLimit && spell->mData.mCost <= minCost)
if (reachedLimit && spell.mData.mCost <= minCost)
continue;
if (race && std::find(race->mPowers.mList.begin(), race->mPowers.mList.end(), spell->mId) != race->mPowers.mList.end())
if (race && std::find(race->mPowers.mList.begin(), race->mPowers.mList.end(), spell.mId) != race->mPowers.mList.end())
continue;
if (baseMagicka < spell->mData.mCost)
if (baseMagicka < spell.mData.mCost)
continue;
static const float fAutoPCSpellChance = esmStore.get<ESM::GameSetting>().find("fAutoPCSpellChance")->mValue.getFloat();
if (calcAutoCastChance(spell, actorSkills, actorAttributes, -1) < fAutoPCSpellChance)
if (calcAutoCastChance(&spell, actorSkills, actorAttributes, -1) < fAutoPCSpellChance)
continue;
if (!attrSkillCheck(spell, actorSkills, actorAttributes))
if (!attrSkillCheck(&spell, actorSkills, actorAttributes))
continue;
selectedSpells.push_back(spell->mId);
selectedSpells.push_back(spell.mId);
if (reachedLimit)
{
@ -188,9 +182,9 @@ namespace MWMechanics
selectedSpells.erase(it);
minCost = std::numeric_limits<int>::max();
for (std::vector<std::string>::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt)
for (const std::string& testSpellName : selectedSpells)
{
const ESM::Spell* testSpell = esmStore.get<ESM::Spell>().find(*weakIt);
const ESM::Spell* testSpell = esmStore.get<ESM::Spell>().find(testSpellName);
if (testSpell->mData.mCost < minCost)
{
minCost = testSpell->mData.mCost;
@ -200,9 +194,9 @@ namespace MWMechanics
}
else
{
if (spell->mData.mCost < minCost)
if (spell.mData.mCost < minCost)
{
weakestSpell = spell;
weakestSpell = &spell;
minCost = weakestSpell->mData.mCost;
}
static const unsigned int iAutoPCSpellMax = esmStore.get<ESM::GameSetting>().find("iAutoPCSpellMax")->mValue.getInteger();
@ -216,23 +210,22 @@ namespace MWMechanics
bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes)
{
const std::vector<ESM::ENAMstruct>& effects = spell->mEffects.mList;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt)
for (const auto& spellEffect : spell->mEffects.mList)
{
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectIt->mEffectID);
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(spellEffect.mEffectID);
static const int iAutoSpellAttSkillMin = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("iAutoSpellAttSkillMin")->mValue.getInteger();
if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill))
{
assert (effectIt->mSkill >= 0 && effectIt->mSkill < ESM::Skill::Length);
if (actorSkills[effectIt->mSkill] < iAutoSpellAttSkillMin)
assert (spellEffect.mSkill >= 0 && spellEffect.mSkill < ESM::Skill::Length);
if (actorSkills[spellEffect.mSkill] < iAutoSpellAttSkillMin)
return false;
}
if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute))
{
assert (effectIt->mAttribute >= 0 && effectIt->mAttribute < ESM::Attribute::Length);
if (actorAttributes[effectIt->mAttribute] < iAutoSpellAttSkillMin)
assert (spellEffect.mAttribute >= 0 && spellEffect.mAttribute < ESM::Attribute::Length);
if (actorAttributes[spellEffect.mAttribute] < iAutoSpellAttSkillMin)
return false;
}
}
@ -244,11 +237,8 @@ namespace MWMechanics
{
// Morrowind for some reason uses a formula slightly different from magicka cost calculation
float minChance = std::numeric_limits<float>::max();
const ESM::EffectList& effects = spell->mEffects;
for (std::vector<ESM::ENAMstruct>::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it)
for (const ESM::ENAMstruct& effect : spell->mEffects.mList)
{
const ESM::ENAMstruct& effect = *it;
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectID);
int minMagn = 1;

@ -2873,11 +2873,11 @@ void CharacterController::updateContinuousVfx()
std::vector<int> effects;
mAnimation->getLoopingEffects(effects);
for (std::vector<int>::iterator it = effects.begin(); it != effects.end(); ++it)
for (int effectId : effects)
{
if (mPtr.getClass().getCreatureStats(mPtr).isDeathAnimationFinished()
|| mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(*it)).getMagnitude() <= 0)
mAnimation->removeEffect(*it);
|| mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(effectId)).getMagnitude() <= 0)
mAnimation->removeEffect(effectId);
}
}

@ -31,10 +31,10 @@ namespace MWMechanics
std::vector<std::string> candidates;
int highestLevel = 0;
for (std::vector<ESM::LevelledListBase::LevelItem>::const_iterator it = items.begin(); it != items.end(); ++it)
for (const auto& levelledItem : items)
{
if (it->mLevel > highestLevel && it->mLevel <= playerLevel)
highestLevel = it->mLevel;
if (levelledItem.mLevel > highestLevel && levelledItem.mLevel <= playerLevel)
highestLevel = levelledItem.mLevel;
}
// For levelled creatures, the flags are swapped. This file format just makes so much sense.
@ -43,14 +43,14 @@ namespace MWMechanics
allLevels = levItem->mFlags & ESM::CreatureLevList::AllLevels;
std::pair<int, std::string> highest = std::make_pair(-1, "");
for (std::vector<ESM::LevelledListBase::LevelItem>::const_iterator it = items.begin(); it != items.end(); ++it)
for (const auto& levelledItem : items)
{
if (playerLevel >= it->mLevel
&& (allLevels || it->mLevel == highestLevel))
if (playerLevel >= levelledItem.mLevel
&& (allLevels || levelledItem.mLevel == highestLevel))
{
candidates.push_back(it->mId);
if (it->mLevel >= highest.first)
highest = std::make_pair(it->mLevel, it->mId);
candidates.push_back(levelledItem.mId);
if (levelledItem.mLevel >= highest.first)
highest = std::make_pair(levelledItem.mLevel, levelledItem.mId);
}
}
if (candidates.empty())

@ -316,13 +316,37 @@ namespace MWMechanics
void MechanicsManager::update(float duration, bool paused)
{
// Note: we should do it here since game mechanics and world updates use these values
MWWorld::Ptr ptr = getPlayer();
MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager();
// Update the equipped weapon icon
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
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, ptr)));
else
winMgr->unsetSelectedSpell();
}
if (mUpdatePlayer)
{
mUpdatePlayer = false;
// 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();
mActors.removeActor(ptr);
mActors.addActor(ptr, true);
}

@ -19,11 +19,10 @@ Objects::Objects()
Objects::~Objects()
{
PtrControllerMap::iterator it(mObjects.begin());
for (; it != mObjects.end();++it)
for(auto& object : mObjects)
{
delete it->second;
it->second = nullptr;
delete object.second;
object.second = nullptr;
}
}
@ -77,8 +76,8 @@ void Objects::update(float duration, bool paused)
{
if(!paused)
{
for(PtrControllerMap::iterator iter(mObjects.begin());iter != mObjects.end();++iter)
iter->second->update(duration);
for(auto& object : mObjects)
object.second->update(duration);
}
else
{
@ -87,15 +86,15 @@ void Objects::update(float duration, bool paused)
if(mode != MWGui::GM_Container)
return;
for(PtrControllerMap::iterator iter(mObjects.begin());iter != mObjects.end();++iter)
for(auto& object : mObjects)
{
if (iter->first.getTypeName() != typeid(ESM::Container).name())
if (object.first.getTypeName() != typeid(ESM::Container).name())
continue;
if (iter->second->isAnimPlaying("containeropen"))
if (object.second->isAnimPlaying("containeropen"))
{
iter->second->update(duration);
MWBase::Environment::get().getWorld()->updateAnimatedCollisionShape(iter->first);
object.second->update(duration);
MWBase::Environment::get().getWorld()->updateAnimatedCollisionShape(object.first);
}
}
}

@ -36,17 +36,13 @@ namespace MWMechanics
// Check all the doors in this cell
const MWWorld::CellRefList<ESM::Door>& doors = cell->getReadOnlyDoors();
const MWWorld::CellRefList<ESM::Door>::List& refList = doors.mList;
MWWorld::CellRefList<ESM::Door>::List::const_iterator it = refList.begin();
osg::Vec3f pos(actor.getRefData().getPosition().asVec3());
pos.z() = 0;
osg::Vec3f actorDir = (actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0));
for (; it != refList.end(); ++it)
for (const auto& ref : doors.mList)
{
const MWWorld::LiveCellRef<ESM::Door>& ref = *it;
osg::Vec3f doorPos(ref.mData.getPosition().asVec3());
// FIXME: cast

@ -13,6 +13,7 @@
#include "../mwworld/refdata.hpp"
#include "../mwmechanics/drawstate.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "npcanimation.hpp"
@ -52,7 +53,10 @@ namespace MWRender
mCamera(camera),
mAnimation(nullptr),
mFirstPersonView(true),
mPreviewMode(false),
mMode(Mode::Normal),
mVanityAllowed(true),
mStandingPreviewAllowed(Settings::Manager::getBool("preview if stand still", "Camera")),
mDeferredRotationAllowed(Settings::Manager::getBool("deferred preview rotation", "Camera")),
mNearest(30.f),
mFurthest(800.f),
mIsNearest(false),
@ -62,20 +66,19 @@ namespace MWRender
mVanityToggleQueuedValue(false),
mViewModeToggleQueued(false),
mCameraDistance(0.f),
mThirdPersonMode(ThirdPersonViewMode::Standard),
mOverShoulderOffset(osg::Vec2f(30.0f, -10.0f)),
mSmoothTransitionToCombatMode(0.f)
mMaxNextCameraDistance(800.f),
mFocalPointCurrentOffset(osg::Vec2d()),
mFocalPointTargetOffset(osg::Vec2d()),
mFocalPointTransitionSpeedCoef(1.f),
mSkipFocalPointTransition(true),
mPreviousTransitionInfluence(0.f),
mSmoothedSpeed(0.f),
mZoomOutWhenMoveCoef(Settings::Manager::getFloat("zoom out when move coef", "Camera")),
mDynamicCameraDistanceEnabled(false),
mShowCrosshairInThirdPersonMode(false),
mDeferredRotation(osg::Vec3f()),
mDeferredRotationDisabled(false)
{
mVanity.enabled = false;
mVanity.allowed = true;
mPreviewCam.pitch = 0.f;
mPreviewCam.yaw = 0.f;
mPreviewCam.offset = 400.f;
mMainCam.pitch = 0.f;
mMainCam.yaw = 0.f;
mMainCam.offset = 400.f;
mCameraDistance = mBaseCameraDistance;
mUpdateCallback = new UpdateRenderCameraCallback(this);
@ -87,17 +90,11 @@ namespace MWRender
mCamera->removeUpdateCallback(mUpdateCallback);
}
MWWorld::Ptr Camera::getTrackingPtr() const
{
return mTrackingPtr;
}
osg::Vec3d Camera::getFocalPoint() const
{
const osg::Node* trackNode = mTrackingNode;
if (!trackNode)
if (!mTrackingNode)
return osg::Vec3d();
osg::NodePathList nodepaths = trackNode->getParentalNodePaths();
osg::NodePathList nodepaths = mTrackingNode->getParentalNodePaths();
if (nodepaths.empty())
return osg::Vec3d();
osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]);
@ -119,15 +116,9 @@ namespace MWRender
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;
}
offset.x() += mFocalPointCurrentOffset.x() * cos(getYaw());
offset.y() += mFocalPointCurrentOffset.x() * sin(getYaw());
offset.z() += mFocalPointCurrentOffset.y();
return offset;
}
@ -145,9 +136,6 @@ namespace MWRender
void Camera::updateCamera(osg::Camera *cam)
{
if (mTrackingPtr.isEmpty())
return;
osg::Vec3d focal, position;
getPosition(focal, position);
@ -177,11 +165,6 @@ namespace MWRender
setPitch(pitch);
}
void Camera::attachTo(const MWWorld::Ptr &ptr)
{
mTrackingPtr = ptr;
}
void Camera::update(float duration, bool paused)
{
if (mAnimation->upperBodyReady())
@ -194,7 +177,6 @@ namespace MWRender
}
if (mViewModeToggleQueued)
{
togglePreviewMode(false);
toggleViewMode();
mViewModeToggleQueued = false;
@ -206,36 +188,86 @@ namespace MWRender
// only show the crosshair in game mode
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
wm->showCrosshair(!wm->isGuiMode() && !mVanity.enabled && !mPreviewMode
&& (mFirstPersonView || mThirdPersonMode != ThirdPersonViewMode::Standard));
wm->showCrosshair(!wm->isGuiMode() && mMode != Mode::Preview && mMode != Mode::Vanity
&& (mFirstPersonView || mShowCrosshairInThirdPersonMode));
if(mVanity.enabled)
{
if(mMode == Mode::Vanity)
rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true);
}
updateSmoothTransitionToCombatMode(duration);
updateFocalPointOffset(duration);
float speed = mTrackingPtr.getClass().getSpeed(mTrackingPtr);
speed /= (1.f + speed / 500.f);
float maxDelta = 300.f * duration;
mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta);
mMaxNextCameraDistance = mCameraDistance + duration * (100.f + mBaseCameraDistance);
updateStandingPreviewMode();
}
void Camera::setOverShoulderOffset(float horizontal, float vertical)
void Camera::updateStandingPreviewMode()
{
mOverShoulderOffset = osg::Vec2f(horizontal, vertical);
if (!mStandingPreviewAllowed)
return;
float speed = mTrackingPtr.getClass().getSpeed(mTrackingPtr);
bool combat = mTrackingPtr.getClass().isActor() &&
mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing;
bool standingStill = speed == 0 && !combat && !mFirstPersonView;
if (!standingStill && mMode == Mode::StandingPreview)
{
mMode = Mode::Normal;
calculateDeferredRotation();
}
else if (standingStill && mMode == Mode::Normal)
mMode = Mode::StandingPreview;
}
void Camera::updateSmoothTransitionToCombatMode(float duration)
void Camera::setFocalPointTargetOffset(osg::Vec2d v)
{
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;
mFocalPointTargetOffset = v;
mPreviousTransitionSpeed = mFocalPointTransitionSpeed;
mPreviousTransitionInfluence = 1.0f;
}
void Camera::updateFocalPointOffset(float duration)
{
if (duration <= 0)
return;
mSmoothTransitionToCombatMode += speed * duration;
if (mSmoothTransitionToCombatMode > 1)
mSmoothTransitionToCombatMode = 1;
if (mSmoothTransitionToCombatMode < 0)
mSmoothTransitionToCombatMode = 0;
if (mSkipFocalPointTransition)
{
mSkipFocalPointTransition = false;
mPreviousExtraOffset = osg::Vec2d();
mPreviousTransitionInfluence = 0.f;
mFocalPointCurrentOffset = mFocalPointTargetOffset;
}
osg::Vec2d oldOffset = mFocalPointCurrentOffset;
if (mPreviousTransitionInfluence > 0)
{
mFocalPointCurrentOffset -= mPreviousExtraOffset;
mPreviousExtraOffset = mPreviousExtraOffset / mPreviousTransitionInfluence + mPreviousTransitionSpeed * duration;
mPreviousTransitionInfluence =
std::max(0.f, mPreviousTransitionInfluence - duration * mFocalPointTransitionSpeedCoef);
mPreviousExtraOffset *= mPreviousTransitionInfluence;
mFocalPointCurrentOffset += mPreviousExtraOffset;
}
osg::Vec2d delta = mFocalPointTargetOffset - mFocalPointCurrentOffset;
if (delta.length2() > 0)
{
float coef = duration * (1.0 + 5.0 / delta.length()) *
mFocalPointTransitionSpeedCoef * (1.0f - mPreviousTransitionInfluence);
mFocalPointCurrentOffset += delta * std::min(coef, 1.0f);
}
else
{
mPreviousExtraOffset = osg::Vec2d();
mPreviousTransitionInfluence = 0.f;
}
mFocalPointTransitionSpeed = (mFocalPointCurrentOffset - oldOffset) / duration;
}
void Camera::toggleViewMode(bool force)
@ -254,14 +286,19 @@ namespace MWRender
mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).setSideMovementAngle(0);
mFirstPersonView = !mFirstPersonView;
updateStandingPreviewMode();
instantTransition();
processViewChange();
}
void Camera::allowVanityMode(bool allow)
{
if (!allow && mVanity.enabled)
if (!allow && mMode == Mode::Vanity)
{
disableDeferredPreviewRotation();
toggleVanityMode(false);
mVanity.allowed = allow;
}
mVanityAllowed = allow;
}
bool Camera::toggleVanityMode(bool enable)
@ -275,26 +312,18 @@ namespace MWRender
return false;
}
if(!mVanity.allowed && enable)
if (!mVanityAllowed && enable)
return false;
if(mVanity.enabled == enable)
if ((mMode == Mode::Vanity) == enable)
return true;
mVanity.enabled = enable;
mMode = enable ? Mode::Vanity : Mode::Normal;
if (!mDeferredRotationAllowed)
disableDeferredPreviewRotation();
if (!enable)
calculateDeferredRotation();
processViewChange();
float offset = mPreviewCam.offset;
if (mVanity.enabled) {
setPitch(osg::DegreesToRadians(-30.f));
mMainCam.offset = mCameraDistance;
} else {
offset = mMainCam.offset;
}
mCameraDistance = offset;
return true;
}
@ -303,22 +332,21 @@ namespace MWRender
if (mFirstPersonView && !mAnimation->upperBodyReady())
return;
if(mPreviewMode == enable)
if((mMode == Mode::Preview) == enable)
return;
mPreviewMode = enable;
processViewChange();
float offset = mCameraDistance;
if (mPreviewMode) {
mMainCam.offset = offset;
offset = mPreviewCam.offset;
} else {
mPreviewCam.offset = offset;
offset = mMainCam.offset;
mMode = enable ? Mode::Preview : Mode::Normal;
if (mMode == Mode::Normal)
updateStandingPreviewMode();
else if (mFirstPersonView)
instantTransition();
if (mMode == Mode::Normal)
{
if (!mDeferredRotationAllowed)
disableDeferredPreviewRotation();
calculateDeferredRotation();
}
mCameraDistance = offset;
processViewChange();
}
void Camera::setSneakOffset(float offset)
@ -326,13 +354,6 @@ namespace MWRender
mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset));
}
float Camera::getYaw() const
{
if(mVanity.enabled || mPreviewMode)
return mPreviewCam.yaw;
return mMainCam.yaw;
}
void Camera::setYaw(float angle)
{
if (angle > osg::PI) {
@ -340,38 +361,14 @@ namespace MWRender
} else if (angle < -osg::PI) {
angle += osg::PI*2;
}
if (mVanity.enabled || mPreviewMode) {
mPreviewCam.yaw = angle;
} else {
mMainCam.yaw = angle;
}
}
float Camera::getPitch() const
{
if (mVanity.enabled || mPreviewMode) {
return mPreviewCam.pitch;
}
return mMainCam.pitch;
mYaw = angle;
}
void Camera::setPitch(float angle)
{
const float epsilon = 0.000001f;
float limit = osg::PI_2 - epsilon;
if(mPreviewMode)
limit /= 2;
if(angle > limit)
angle = limit;
else if(angle < -limit)
angle = -limit;
if (mVanity.enabled || mPreviewMode) {
mPreviewCam.pitch = angle;
} else {
mMainCam.pitch = angle;
}
mPitch = osg::clampBetween(angle, -limit, limit);
}
float Camera::getCameraDistance() const
@ -383,70 +380,53 @@ namespace MWRender
void Camera::updateBaseCameraDistance(float dist, bool adjust)
{
if(mFirstPersonView && !mPreviewMode && !mVanity.enabled)
if (isFirstPerson())
return;
mIsNearest = false;
if (adjust)
{
if (mVanity.enabled || mPreviewMode)
dist += mCameraDistance;
else
dist += std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance);
}
dist += std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance);
if (dist >= mFurthest)
dist = mFurthest;
else if (dist <= mNearest)
{
dist = mNearest;
mIsNearest = true;
}
if (mVanity.enabled || mPreviewMode)
mPreviewCam.offset = dist;
else if (!mFirstPersonView)
{
mBaseCameraDistance = dist;
Settings::Manager::setFloat("third person camera distance", "Camera", dist);
}
mIsNearest = dist <= mNearest;
mBaseCameraDistance = osg::clampBetween(dist, mNearest, mFurthest);
Settings::Manager::setFloat("third person camera distance", "Camera", mBaseCameraDistance);
setCameraDistance();
}
void Camera::setCameraDistance(float dist, bool adjust)
{
if(mFirstPersonView && !mPreviewMode && !mVanity.enabled)
if (isFirstPerson())
return;
if (adjust) dist += mCameraDistance;
if (dist >= mFurthest)
dist = mFurthest;
else if (dist < 10.f)
dist = 10.f;
mCameraDistance = dist;
if (adjust)
dist += mCameraDistance;
mCameraDistance = osg::clampBetween(dist, 10.f, mFurthest);
}
float Camera::getCameraDistanceCorrection() const
{
return mThirdPersonMode != ThirdPersonViewMode::Standard ? std::max(-getPitch(), 0.f) * 50.f : 0;
if (!mDynamicCameraDistanceEnabled)
return 0;
float pitchCorrection = std::max(-getPitch(), 0.f) * 50.f;
float smoothedSpeedSqr = mSmoothedSpeed * mSmoothedSpeed;
float speedCorrection = smoothedSpeedSqr / (smoothedSpeedSqr + 300.f*300.f) * mZoomOutWhenMoveCoef;
return pitchCorrection + speedCorrection;
}
void Camera::setCameraDistance()
{
if (mVanity.enabled || mPreviewMode)
mCameraDistance = mPreviewCam.offset;
else if (!mFirstPersonView)
mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection();
mFocalPointAdjustment = osg::Vec3d();
if (isFirstPerson())
return;
mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection();
if (mDynamicCameraDistanceEnabled)
mCameraDistance = std::min(mCameraDistance, mMaxNextCameraDistance);
}
void Camera::setAnimation(NpcAnimation *anim)
{
mAnimation = anim;
processViewChange();
}
@ -473,13 +453,74 @@ namespace MWRender
rotateCamera(getPitch(), getYaw(), false);
}
bool Camera::isVanityOrPreviewModeEnabled() const
void Camera::applyDeferredPreviewRotationToPlayer(float dt)
{
return mPreviewMode || mVanity.enabled;
if (isVanityOrPreviewModeEnabled() || mTrackingPtr.isEmpty())
return;
osg::Vec3f rot = mDeferredRotation;
float delta = rot.normalize();
delta = std::min(delta, (delta + 1.f) * 3 * dt);
rot *= delta;
mDeferredRotation -= rot;
if (mDeferredRotationDisabled)
{
mDeferredRotationDisabled = delta > 0.0001;
rotateCameraToTrackingPtr();
return;
}
auto& movement = mTrackingPtr.getClass().getMovementSettings(mTrackingPtr);
movement.mRotation[0] += rot.x();
movement.mRotation[1] += rot.y();
movement.mRotation[2] += rot.z();
if (std::abs(mDeferredRotation.z()) > 0.0001)
{
float s = std::sin(mDeferredRotation.z());
float c = std::cos(mDeferredRotation.z());
float x = movement.mPosition[0];
float y = movement.mPosition[1];
movement.mPosition[0] = x * c + y * s;
movement.mPosition[1] = x * -s + y * c;
}
}
bool Camera::isNearest() const
void Camera::rotateCameraToTrackingPtr()
{
return mIsNearest;
setPitch(-mTrackingPtr.getRefData().getPosition().rot[0] - mDeferredRotation.x());
setYaw(-mTrackingPtr.getRefData().getPosition().rot[2] - mDeferredRotation.z());
}
void Camera::instantTransition()
{
mSkipFocalPointTransition = true;
mDeferredRotationDisabled = false;
mDeferredRotation = osg::Vec3f();
rotateCameraToTrackingPtr();
}
void Camera::calculateDeferredRotation()
{
MWWorld::Ptr ptr = mTrackingPtr;
if (isVanityOrPreviewModeEnabled() || ptr.isEmpty())
return;
if (mFirstPersonView)
{
instantTransition();
return;
}
mDeferredRotation.x() = -ptr.getRefData().getPosition().rot[0] - mPitch;
mDeferredRotation.z() = -ptr.getRefData().getPosition().rot[2] - mYaw;
if (mDeferredRotation.x() > osg::PI)
mDeferredRotation.x() -= 2 * osg::PI;
if (mDeferredRotation.x() < -osg::PI)
mDeferredRotation.x() += 2 * osg::PI;
if (mDeferredRotation.z() > osg::PI)
mDeferredRotation.z() -= 2 * osg::PI;
if (mDeferredRotation.z() < -osg::PI)
mDeferredRotation.z() += 2 * osg::PI;
}
}

@ -24,13 +24,9 @@ namespace MWRender
class Camera
{
public:
enum class ThirdPersonViewMode {Standard, OverShoulder};
enum class Mode { Normal, Vanity, Preview, StandingPreview };
private:
struct CamData {
float pitch, yaw, offset;
};
MWWorld::Ptr mTrackingPtr;
osg::ref_ptr<const osg::Node> mTrackingNode;
float mHeightScale;
@ -40,44 +36,66 @@ namespace MWRender
NpcAnimation *mAnimation;
bool mFirstPersonView;
bool mPreviewMode;
Mode mMode;
bool mVanityAllowed;
bool mStandingPreviewAllowed;
bool mDeferredRotationAllowed;
float mNearest;
float mFurthest;
bool mIsNearest;
struct {
bool enabled, allowed;
} mVanity;
float mHeight, mBaseCameraDistance;
CamData mMainCam, mPreviewCam;
float mPitch, mYaw;
bool mVanityToggleQueued;
bool mVanityToggleQueuedValue;
bool mViewModeToggleQueued;
float mCameraDistance;
float mMaxNextCameraDistance;
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);
osg::Vec2d mFocalPointCurrentOffset;
osg::Vec2d mFocalPointTargetOffset;
float mFocalPointTransitionSpeedCoef;
bool mSkipFocalPointTransition;
// This fields are used to make focal point transition smooth if previous transition was not finished.
float mPreviousTransitionInfluence;
osg::Vec2d mFocalPointTransitionSpeed;
osg::Vec2d mPreviousTransitionSpeed;
osg::Vec2d mPreviousExtraOffset;
float mSmoothedSpeed;
float mZoomOutWhenMoveCoef;
bool mDynamicCameraDistanceEnabled;
bool mShowCrosshairInThirdPersonMode;
void updateFocalPointOffset(float duration);
float getCameraDistanceCorrection() const;
osg::ref_ptr<osg::NodeCallback> mUpdateCallback;
// Used to rotate player to the direction of view after exiting preview or vanity mode.
osg::Vec3f mDeferredRotation;
bool mDeferredRotationDisabled;
void calculateDeferredRotation();
void updateStandingPreviewMode();
public:
Camera(osg::Camera* camera);
~Camera();
MWWorld::Ptr getTrackingPtr() const;
/// Attach camera to object
void attachTo(const MWWorld::Ptr &ptr) { mTrackingPtr = ptr; }
MWWorld::Ptr getTrackingPtr() const { return mTrackingPtr; }
void setThirdPersonViewMode(ThirdPersonViewMode mode) { mThirdPersonMode = mode; }
void setOverShoulderOffset(float horizontal, float vertical);
void setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; }
void setFocalPointTargetOffset(osg::Vec2d v);
void instantTransition();
void enableDynamicCameraDistance(bool v) { mDynamicCameraDistanceEnabled = v; }
void enableCrosshairInThirdPersonMode(bool v) { mShowCrosshairInThirdPersonMode = v; }
/// Update the view matrix of \a cam
void updateCamera(osg::Camera* cam);
@ -88,16 +106,14 @@ namespace MWRender
/// Set where the camera is looking at. Uses Morrowind (euler) angles
/// \param rot Rotation angles in radians
void rotateCamera(float pitch, float yaw, bool adjust);
void rotateCameraToTrackingPtr();
float getYaw() const;
float getYaw() const { return mYaw; }
void setYaw(float angle);
float getPitch() const;
float getPitch() const { return mPitch; }
void setPitch(float angle);
/// Attach camera to object
void attachTo(const MWWorld::Ptr &);
/// @param Force view mode switch, even if currently not allowed by the animation.
void toggleViewMode(bool force=false);
@ -107,11 +123,13 @@ namespace MWRender
/// @note this may be ignored if an important animation is currently playing
void togglePreviewMode(bool enable);
void applyDeferredPreviewRotationToPlayer(float dt);
void disableDeferredPreviewRotation() { mDeferredRotationDisabled = true; }
/// \brief Lowers the camera for sneak.
void setSneakOffset(float offset);
bool isFirstPerson() const
{ return !(mVanity.enabled || mPreviewMode || !mFirstPersonView); }
bool isFirstPerson() const { return mFirstPersonView && mMode == Mode::Normal; }
void processViewChange();
@ -140,9 +158,10 @@ namespace MWRender
/// Stores focal and camera world positions in passed arguments
void getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const;
bool isVanityOrPreviewModeEnabled() const;
bool isVanityOrPreviewModeEnabled() const { return mMode != Mode::Normal; }
Mode getMode() const { return mMode; }
bool isNearest() const;
bool isNearest() const { return mIsNearest; }
};
}

@ -65,6 +65,7 @@
#include "vismask.hpp"
#include "pathgrid.hpp"
#include "camera.hpp"
#include "viewovershoulder.hpp"
#include "water.hpp"
#include "terrainstorage.hpp"
#include "util.hpp"
@ -306,6 +307,8 @@ namespace MWRender
mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath));
mCamera.reset(new Camera(mViewer->getCamera()));
if (Settings::Manager::getBool("view over shoulder", "Camera"))
mViewOverShoulderController.reset(new ViewOverShoulderController(mCamera.get()));
mViewer->setLightingMode(osgViewer::View::NO_LIGHT);
@ -366,7 +369,6 @@ 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));
@ -382,19 +384,6 @@ 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();
@ -630,6 +619,8 @@ namespace MWRender
updateNavMesh();
updateRecastMesh();
if (mViewOverShoulderController)
mViewOverShoulderController->update();
mCamera->update(dt, paused);
osg::Vec3d focal, cameraPos;
@ -662,7 +653,7 @@ namespace MWRender
if(ptr == mCamera->getTrackingPtr() &&
!mCamera->isVanityOrPreviewModeEnabled())
{
mCamera->rotateCamera(-ptr.getRefData().getPosition().rot[0], -ptr.getRefData().getPosition().rot[2], false);
mCamera->rotateCameraToTrackingPtr();
}
ptr.getRefData().getBaseNode()->setAttitude(rot);

@ -79,6 +79,7 @@ namespace MWRender
class NpcAnimation;
class Pathgrid;
class Camera;
class ViewOverShoulderController;
class Water;
class TerrainStorage;
class LandManager;
@ -294,6 +295,7 @@ namespace MWRender
osg::ref_ptr<NpcAnimation> mPlayerAnimation;
osg::ref_ptr<SceneUtil::PositionAttitudeTransform> mPlayerNode;
std::unique_ptr<Camera> mCamera;
std::unique_ptr<ViewOverShoulderController> mViewOverShoulderController;
osg::Vec3f mCurrentCameraPos;
osg::ref_ptr<StateUpdater> mStateUpdater;

@ -0,0 +1,106 @@
#include "viewovershoulder.hpp"
#include <osg/Quat>
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/ptr.hpp"
#include "../mwworld/refdata.hpp"
#include "../mwmechanics/drawstate.hpp"
namespace MWRender
{
ViewOverShoulderController::ViewOverShoulderController(Camera* camera) :
mCamera(camera), mMode(Mode::RightShoulder),
mAutoSwitchShoulder(Settings::Manager::getBool("auto switch shoulder", "Camera")),
mOverShoulderHorizontalOffset(30.f), mOverShoulderVerticalOffset(-10.f)
{
osg::Vec2f offset = Settings::Manager::getVector2("view over shoulder offset", "Camera");
mOverShoulderHorizontalOffset = std::abs(offset.x());
mOverShoulderVerticalOffset = offset.y();
mDefaultShoulderIsRight = offset.x() >= 0;
mCamera->enableDynamicCameraDistance(true);
mCamera->enableCrosshairInThirdPersonMode(true);
mCamera->setFocalPointTargetOffset(offset);
}
void ViewOverShoulderController::update()
{
if (mCamera->isFirstPerson())
return;
Mode oldMode = mMode;
auto ptr = mCamera->getTrackingPtr();
bool combat = ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getDrawState() != MWMechanics::DrawState_Nothing;
if (combat && !mCamera->isVanityOrPreviewModeEnabled())
mMode = Mode::Combat;
else if (MWBase::Environment::get().getWorld()->isSwimming(ptr))
mMode = Mode::Swimming;
else if (oldMode == Mode::Combat || oldMode == Mode::Swimming)
mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder;
if (mAutoSwitchShoulder && (mMode == Mode::LeftShoulder || mMode == Mode::RightShoulder))
trySwitchShoulder();
if (oldMode == mMode)
return;
if (mCamera->getMode() == Camera::Mode::Vanity)
// Player doesn't touch controls for a long time. Transition should be very slow.
mCamera->setFocalPointTransitionSpeed(0.2f);
else if ((oldMode == Mode::Combat || mMode == Mode::Combat) && mCamera->getMode() == Camera::Mode::Normal)
// Transition to/from combat mode and we are not it preview mode. Should be fast.
mCamera->setFocalPointTransitionSpeed(5.f);
else
mCamera->setFocalPointTransitionSpeed(1.f); // Default transition speed.
switch (mMode)
{
case Mode::RightShoulder:
mCamera->setFocalPointTargetOffset({mOverShoulderHorizontalOffset, mOverShoulderVerticalOffset});
break;
case Mode::LeftShoulder:
mCamera->setFocalPointTargetOffset({-mOverShoulderHorizontalOffset, mOverShoulderVerticalOffset});
break;
case Mode::Combat:
case Mode::Swimming:
default:
mCamera->setFocalPointTargetOffset({0, 15});
}
}
void ViewOverShoulderController::trySwitchShoulder()
{
if (mCamera->getMode() != Camera::Mode::Normal)
return;
const float limitToSwitch = 120; // switch to other shoulder if wall is closer than this limit
const float limitToSwitchBack = 300; // switch back to default shoulder if there is no walls at this distance
auto orient = osg::Quat(mCamera->getYaw(), osg::Vec3d(0,0,1));
osg::Vec3d playerPos = mCamera->getFocalPoint() - mCamera->getFocalPointOffset();
MWBase::World* world = MWBase::Environment::get().getWorld();
osg::Vec3d sideOffset = orient * osg::Vec3d(world->getHalfExtents(mCamera->getTrackingPtr()).x() - 1, 0, 0);
float rayRight = world->getDistToNearestRayHit(
playerPos + sideOffset, orient * osg::Vec3d(1, 1, 0), limitToSwitchBack + 1);
float rayLeft = world->getDistToNearestRayHit(
playerPos - sideOffset, orient * osg::Vec3d(-1, 1, 0), limitToSwitchBack + 1);
float rayForward = world->getDistToNearestRayHit(
playerPos, orient * osg::Vec3d(0, 1, 0), limitToSwitchBack + 1);
if (rayLeft < limitToSwitch && rayRight > limitToSwitchBack)
mMode = Mode::RightShoulder;
else if (rayRight < limitToSwitch && rayLeft > limitToSwitchBack)
mMode = Mode::LeftShoulder;
else if (rayLeft > limitToSwitchBack && rayRight > limitToSwitchBack && rayForward > limitToSwitchBack)
mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder;
}
}

@ -0,0 +1,30 @@
#ifndef VIEWOVERSHOULDER_H
#define VIEWOVERSHOULDER_H
#include "camera.hpp"
namespace MWRender
{
class ViewOverShoulderController
{
public:
ViewOverShoulderController(Camera* camera);
void update();
private:
void trySwitchShoulder();
enum class Mode { RightShoulder, LeftShoulder, Combat, Swimming };
Camera* mCamera;
Mode mMode;
bool mAutoSwitchShoulder;
float mOverShoulderHorizontalOffset;
float mOverShoulderVerticalOffset;
bool mDefaultShoulderIsRight;
};
}
#endif // VIEWOVERSHOULDER_H

@ -79,8 +79,8 @@ namespace MWScript
if (Success)
{
std::vector<Interpreter::Type_Code> code;
mParser.getCode (code);
mScripts.insert (std::make_pair (name, std::make_pair (code, mParser.getLocals())));
mParser.getCode(code);
mScripts.emplace(name, CompiledScript(code, mParser.getLocals()));
return true;
}
@ -100,7 +100,7 @@ namespace MWScript
{
// failed -> ignore script from now on.
std::vector<Interpreter::Type_Code> empty;
mScripts.insert (std::make_pair (name, std::make_pair (empty, Compiler::Locals())));
mScripts.emplace(name, CompiledScript(empty, Compiler::Locals()));
return false;
}
@ -109,7 +109,7 @@ namespace MWScript
}
// execute script
if (!iter->second.first.empty())
if (!iter->second.mByteCode.empty() && iter->second.mActive)
try
{
if (!mOpcodesInstalled)
@ -118,7 +118,7 @@ namespace MWScript
mOpcodesInstalled = true;
}
mInterpreter.run (&iter->second.first[0], iter->second.first.size(), interpreterContext);
mInterpreter.run (&iter->second.mByteCode[0], iter->second.mByteCode.size(), interpreterContext);
return true;
}
catch (const MissingImplicitRefError& e)
@ -129,11 +129,21 @@ namespace MWScript
{
Log(Debug::Error) << "Execution of script " << name << " failed: " << e.what();
iter->second.first.clear(); // don't execute again.
iter->second.mActive = false; // don't execute again.
}
return false;
}
void ScriptManager::clear()
{
for (auto& script : mScripts)
{
script.second.mActive = true;
}
mGlobalScripts.clear();
}
std::pair<int, int> ScriptManager::compileAll()
{
int count = 0;
@ -163,7 +173,7 @@ namespace MWScript
ScriptCollection::iterator iter = mScripts.find (name2);
if (iter!=mScripts.end())
return iter->second.second;
return iter->second.mLocals;
}
{

@ -41,7 +41,20 @@ namespace MWScript
Interpreter::Interpreter mInterpreter;
bool mOpcodesInstalled;
typedef std::pair<std::vector<Interpreter::Type_Code>, Compiler::Locals> CompiledScript;
struct CompiledScript
{
std::vector<Interpreter::Type_Code> mByteCode;
Compiler::Locals mLocals;
bool mActive;
CompiledScript(const std::vector<Interpreter::Type_Code>& code, const Compiler::Locals& locals)
{
mByteCode = code;
mLocals = locals;
mActive = true;
}
};
typedef std::map<std::string, CompiledScript> ScriptCollection;
ScriptCollection mScripts;
@ -55,6 +68,8 @@ namespace MWScript
Compiler::Context& compilerContext, int warningsMode,
const std::vector<std::string>& scriptBlacklist);
virtual void clear();
virtual bool run (const std::string& name, Interpreter::Context& interpreterContext);
///< Run the script with the given name (compile first, if not compiled yet)

@ -897,7 +897,8 @@ namespace MWScript
// We should move actors, standing on moving object, too.
// This approach can be used to create elevators.
moveStandingActors(ptr, diff);
MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x(), worldPos.y(), worldPos.z());
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x(), worldPos.y(), worldPos.z()));
}
};
@ -933,7 +934,8 @@ namespace MWScript
// We should move actors, standing on moving object, too.
// This approach can be used to create elevators.
moveStandingActors(ptr, diff);
MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+diff.x(), objPos[1]+diff.y(), objPos[2]+diff.z());
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+diff.x(), objPos[1]+diff.y(), objPos[2]+diff.z()));
}
};

@ -11,85 +11,65 @@ namespace MWSound
inline int operator&(int a, PlayMode b) { return a & static_cast<int>(b); }
inline int operator&(PlayMode a, PlayMode b) { return static_cast<int>(a) & static_cast<int>(b); }
struct SoundParams
{
osg::Vec3f mPos;
float mVolume = 1;
float mBaseVolume = 1;
float mPitch = 1;
float mMinDistance = 1;
float mMaxDistance = 1000;
int mFlags = 0;
float mFadeOutTime = 0;
};
class SoundBase {
SoundBase& operator=(const SoundBase&) = delete;
SoundBase(const SoundBase&) = delete;
SoundBase(SoundBase&&) = delete;
osg::Vec3f mPos;
float mVolume; /* NOTE: Real volume = mVolume*mBaseVolume */
float mBaseVolume;
float mPitch;
float mMinDistance;
float mMaxDistance;
int mFlags;
float mFadeOutTime;
SoundParams mParams;
protected:
Sound_Instance mHandle;
Sound_Instance mHandle = nullptr;
friend class OpenAL_Output;
public:
void setPosition(const osg::Vec3f &pos) { mPos = pos; }
void setVolume(float volume) { mVolume = volume; }
void setBaseVolume(float volume) { mBaseVolume = volume; }
void setFadeout(float duration) { mFadeOutTime = duration; }
void setPosition(const osg::Vec3f &pos) { mParams.mPos = pos; }
void setVolume(float volume) { mParams.mVolume = volume; }
void setBaseVolume(float volume) { mParams.mBaseVolume = volume; }
void setFadeout(float duration) { mParams.mFadeOutTime = duration; }
void updateFade(float duration)
{
if(mFadeOutTime > 0.0f)
if (mParams.mFadeOutTime > 0.0f)
{
float soundDuration = std::min(duration, mFadeOutTime);
mVolume *= (mFadeOutTime-soundDuration) / mFadeOutTime;
mFadeOutTime -= soundDuration;
float soundDuration = std::min(duration, mParams.mFadeOutTime);
mParams.mVolume *= (mParams.mFadeOutTime - soundDuration) / mParams.mFadeOutTime;
mParams.mFadeOutTime -= soundDuration;
}
}
const osg::Vec3f &getPosition() const { return mPos; }
float getRealVolume() const { return mVolume * mBaseVolume; }
float getPitch() const { return mPitch; }
float getMinDistance() const { return mMinDistance; }
float getMaxDistance() const { return mMaxDistance; }
const osg::Vec3f &getPosition() const { return mParams.mPos; }
float getRealVolume() const { return mParams.mVolume * mParams.mBaseVolume; }
float getPitch() const { return mParams.mPitch; }
float getMinDistance() const { return mParams.mMinDistance; }
float getMaxDistance() const { return mParams.mMaxDistance; }
MWSound::Type getPlayType() const
{ return static_cast<MWSound::Type>(mFlags&MWSound::Type::Mask); }
bool getUseEnv() const { return !(mFlags&MWSound::PlayMode::NoEnv); }
bool getIsLooping() const { return mFlags&MWSound::PlayMode::Loop; }
bool getDistanceCull() const { return mFlags&MWSound::PlayMode::RemoveAtDistance; }
bool getIs3D() const { return mFlags&Play_3D; }
void init(const osg::Vec3f& pos, float vol, float basevol, float pitch, float mindist, float maxdist, int flags)
{
mPos = pos;
mVolume = vol;
mBaseVolume = basevol;
mPitch = pitch;
mMinDistance = mindist;
mMaxDistance = maxdist;
mFlags = flags;
mFadeOutTime = 0.0f;
mHandle = nullptr;
}
{ return static_cast<MWSound::Type>(mParams.mFlags & MWSound::Type::Mask); }
bool getUseEnv() const { return !(mParams.mFlags & MWSound::PlayMode::NoEnv); }
bool getIsLooping() const { return mParams.mFlags & MWSound::PlayMode::Loop; }
bool getDistanceCull() const { return mParams.mFlags & MWSound::PlayMode::RemoveAtDistance; }
bool getIs3D() const { return mParams.mFlags & Play_3D; }
void init(float vol, float basevol, float pitch, int flags)
void init(const SoundParams& params)
{
mPos = osg::Vec3f(0.0f, 0.0f, 0.0f);
mVolume = vol;
mBaseVolume = basevol;
mPitch = pitch;
mMinDistance = 1.0f;
mMaxDistance = 1000.0f;
mFlags = flags;
mFadeOutTime = 0.0f;
mParams = params;
mHandle = nullptr;
}
SoundBase()
: mPos(0.0f, 0.0f, 0.0f), mVolume(1.0f), mBaseVolume(1.0f), mPitch(1.0f)
, mMinDistance(1.0f), mMaxDistance(1000.0f), mFlags(0), mFadeOutTime(0.0f)
, mHandle(nullptr)
{ }
SoundBase() = default;
};
class Sound : public SoundBase {

@ -290,13 +290,25 @@ namespace MWSound
StreamPtr sound = getStreamRef();
if(playlocal)
{
sound->init(1.0f, basevol, 1.0f, PlayMode::NoEnv|Type::Voice|Play_2D);
sound->init([&] {
SoundParams params;
params.mBaseVolume = basevol;
params.mFlags = PlayMode::NoEnv | Type::Voice | Play_2D;
return params;
} ());
played = mOutput->streamSound(decoder, sound.get(), true);
}
else
{
sound->init(pos, 1.0f, basevol, 1.0f, minDistance, maxDistance,
PlayMode::Normal|Type::Voice|Play_3D);
sound->init([&] {
SoundParams params;
params.mPos = pos;
params.mBaseVolume = basevol;
params.mMinDistance = minDistance;
params.mMaxDistance = maxDistance;
params.mFlags = PlayMode::Normal | Type::Voice | Play_3D;
return params;
} ());
played = mOutput->streamSound3D(decoder, sound.get(), true);
}
if(!played)
@ -332,8 +344,12 @@ namespace MWSound
decoder->open(filename);
mMusic = getStreamRef();
mMusic->init(1.0f, volumeFromType(Type::Music), 1.0f,
PlayMode::NoEnv|Type::Music|Play_2D);
mMusic->init([&] {
SoundParams params;
params.mBaseVolume = volumeFromType(Type::Music);
params.mFlags = PlayMode::NoEnv | Type::Music | Play_2D;
return params;
} ());
mOutput->streamSound(decoder, mMusic.get());
}
@ -561,7 +577,12 @@ namespace MWSound
return nullptr;
StreamPtr track = getStreamRef();
track->init(1.0f, volumeFromType(type), 1.0f, PlayMode::NoEnv|type|Play_2D);
track->init([&] {
SoundParams params;
params.mBaseVolume = volumeFromType(type);
params.mFlags = PlayMode::NoEnv | type | Play_2D;
return params;
} ());
if(!mOutput->streamSound(decoder, track.get()))
return nullptr;
@ -598,7 +619,14 @@ namespace MWSound
stopSound(sfx, MWWorld::ConstPtr());
SoundPtr sound = getSoundRef();
sound->init(volume * sfx->mVolume, volumeFromType(type), pitch, mode|type|Play_2D);
sound->init([&] {
SoundParams params;
params.mVolume = volume * sfx->mVolume;
params.mBaseVolume = volumeFromType(type);
params.mPitch = pitch;
params.mFlags = mode | type | Play_2D;
return params;
} ());
if(!mOutput->playSound(sound.get(), sfx->mHandle, offset))
return nullptr;
@ -635,13 +663,29 @@ namespace MWSound
SoundPtr sound = getSoundRef();
if(!(mode&PlayMode::NoPlayerLocal) && ptr == MWMechanics::getPlayer())
{
sound->init(volume * sfx->mVolume, volumeFromType(type), pitch, mode|type|Play_2D);
sound->init([&] {
SoundParams params;
params.mVolume = volume * sfx->mVolume;
params.mBaseVolume = volumeFromType(type);
params.mPitch = pitch;
params.mFlags = mode | type | Play_2D;
return params;
} ());
played = mOutput->playSound(sound.get(), sfx->mHandle, offset);
}
else
{
sound->init(objpos, volume * sfx->mVolume, volumeFromType(type), pitch,
sfx->mMinDist, sfx->mMaxDist, mode|type|Play_3D);
sound->init([&] {
SoundParams params;
params.mPos = objpos;
params.mVolume = volume * sfx->mVolume;
params.mBaseVolume = volumeFromType(type);
params.mPitch = pitch;
params.mMinDistance = sfx->mMinDist;
params.mMaxDistance = sfx->mMaxDist;
params.mFlags = mode | type | Play_3D;
return params;
} ());
played = mOutput->playSound3D(sound.get(), sfx->mHandle, offset);
}
if(!played)
@ -670,8 +714,17 @@ namespace MWSound
if(!sfx) return nullptr;
SoundPtr sound = getSoundRef();
sound->init(initialPos, volume * sfx->mVolume, volumeFromType(type), pitch,
sfx->mMinDist, sfx->mMaxDist, mode|type|Play_3D);
sound->init([&] {
SoundParams params;
params.mPos = initialPos;
params.mVolume = volume * sfx->mVolume;
params.mBaseVolume = volumeFromType(type);
params.mPitch = pitch;
params.mMinDistance = sfx->mMinDist;
params.mMaxDistance = sfx->mMaxDist;
params.mFlags = mode | type | Play_3D;
return params;
} ());
if(!mOutput->playSound3D(sound.get(), sfx->mHandle, offset))
return nullptr;

@ -47,7 +47,7 @@ void MWState::StateManager::cleanup (bool force)
MWBase::Environment::get().getSoundManager()->clear();
MWBase::Environment::get().getDialogueManager()->clear();
MWBase::Environment::get().getJournal()->clear();
MWBase::Environment::get().getScriptManager()->getGlobalScripts().clear();
MWBase::Environment::get().getScriptManager()->clear();
MWBase::Environment::get().getWorld()->clear();
MWBase::Environment::get().getWindowManager()->clear();
MWBase::Environment::get().getInputManager()->clear();

@ -1062,6 +1062,7 @@ namespace MWWorld
removeContainerScripts(getPlayerPtr());
mWorldScene->changeToInteriorCell(cellName, position, adjustPlayerPos, changeEvent);
addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
mRendering->getCamera()->instantTransition();
}
void World::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
@ -1077,6 +1078,7 @@ namespace MWWorld
removeContainerScripts(getPlayerPtr());
mWorldScene->changeToExteriorCell(position, adjustPlayerPos, changeEvent);
addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
mRendering->getCamera()->instantTransition();
}
void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
@ -1335,7 +1337,6 @@ namespace MWWorld
mRendering->updatePtr(ptr, newPtr);
MWBase::Environment::get().getSoundManager()->updatePtr (ptr, newPtr);
mPhysics->updatePtr(ptr, newPtr);
MWBase::Environment::get().getScriptManager()->getGlobalScripts().updatePtrs(ptr, newPtr);
MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager();
mechMgr->updateCell(ptr, newPtr);
@ -1366,6 +1367,9 @@ namespace MWWorld
End of tes3mp addition
*/
}
MWBase::Environment::get().getWindowManager()->updateConsoleObjectPtr(ptr, newPtr);
MWBase::Environment::get().getScriptManager()->getGlobalScripts().updatePtrs(ptr, newPtr);
}
if (haveToMove && newPtr.getRefData().getBaseNode())
{
@ -2105,7 +2109,7 @@ namespace MWWorld
std::string enchantId = selectedEnchantItem.getClass().getEnchantment(selectedEnchantItem);
if (!enchantId.empty())
{
const ESM::Enchantment* ench = mStore.get<ESM::Enchantment>().search(selectedEnchantItem.getClass().getEnchantment(selectedEnchantItem));
const ESM::Enchantment* ench = mStore.get<ESM::Enchantment>().search(enchantId);
if (ench)
preloadEffects(&ench->mEffects);
}
@ -2656,6 +2660,16 @@ namespace MWWorld
return mRendering->toggleVanityMode(enable);
}
void World::disableDeferredPreviewRotation()
{
mRendering->getCamera()->disableDeferredPreviewRotation();
}
void World::applyDeferredPreviewRotationToPlayer(float dt)
{
mRendering->getCamera()->applyDeferredPreviewRotationToPlayer(dt);
}
void World::allowVanityMode(bool allow)
{
mRendering->allowVanityMode(allow);

@ -646,6 +646,9 @@ namespace MWWorld
bool vanityRotateCamera(float * rot) override;
void setCameraDistance(float dist, bool adjust = false, bool override = true) override;
void applyDeferredPreviewRotationToPlayer(float dt) override;
void disableDeferredPreviewRotation() override;
void setupPlayer() override;
void renderPlayer() override;

@ -8,6 +8,7 @@ ContentSelectorView::ComboBox::ComboBox(QWidget *parent) :
{
mValidator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore
setValidator(mValidator);
setEditable(true);
setCompleter(0);
setEnabled (true);

@ -76,6 +76,28 @@ bool Manager::getBool (const std::string& setting, const std::string& category)
return Misc::StringUtils::ciEqual(string, "true");
}
osg::Vec2f Manager::getVector2 (const std::string& setting, const std::string& category)
{
const std::string& value = getString(setting, category);
std::stringstream stream(value);
float x, y;
stream >> x >> y;
if (stream.fail())
throw std::runtime_error(std::string("Can't parse 2d vector: " + value));
return osg::Vec2f(x, y);
}
osg::Vec3f Manager::getVector3 (const std::string& setting, const std::string& category)
{
const std::string& value = getString(setting, category);
std::stringstream stream(value);
float x, y, z;
stream >> x >> y >> z;
if (stream.fail())
throw std::runtime_error(std::string("Can't parse 3d vector: " + value));
return osg::Vec3f(x, y, z);
}
void Manager::setString(const std::string &setting, const std::string &category, const std::string &value)
{
CategorySettingValueMap::key_type key = std::make_pair(category, setting);
@ -111,6 +133,20 @@ void Manager::setBool(const std::string &setting, const std::string &category, c
setString(setting, category, value ? "true" : "false");
}
void Manager::setVector2 (const std::string &setting, const std::string &category, const osg::Vec2f value)
{
std::ostringstream stream;
stream << value.x() << " " << value.y();
setString(setting, category, stream.str());
}
void Manager::setVector3 (const std::string &setting, const std::string &category, const osg::Vec3f value)
{
std::ostringstream stream;
stream << value.x() << ' ' << value.y() << ' ' << value.z();
setString(setting, category, stream.str());
}
void Manager::resetPendingChange(const std::string &setting, const std::string &category)
{
CategorySettingValueMap::key_type key = std::make_pair(category, setting);

@ -6,6 +6,8 @@
#include <set>
#include <map>
#include <string>
#include <osg/Vec2f>
#include <osg/Vec3f>
namespace Settings
{
@ -44,11 +46,15 @@ namespace Settings
static float getFloat (const std::string& setting, const std::string& category);
static std::string getString (const std::string& setting, const std::string& category);
static bool getBool (const std::string& setting, const std::string& category);
static osg::Vec2f getVector2 (const std::string& setting, const std::string& category);
static osg::Vec3f getVector3 (const std::string& setting, const std::string& category);
static void setInt (const std::string& setting, const std::string& category, const int value);
static void setFloat (const std::string& setting, const std::string& category, const float value);
static void setString (const std::string& setting, const std::string& category, const std::string& value);
static void setBool (const std::string& setting, const std::string& category, const bool value);
static void setVector2 (const std::string& setting, const std::string& category, const osg::Vec2f value);
static void setVector3 (const std::string& setting, const std::string& category, const osg::Vec3f value);
};
}

@ -150,3 +150,51 @@ Recommened values: 30 -10 for the right shoulder, -30 -10 for the left shoulder.
This setting can only be configured by editing the settings configuration file.
auto switch shoulder
--------------------
:Type: boolean
:Range: True/False
:Default: True
This setting makes difference only in third person mode if 'view over shoulder' is enabled.
When player is close to an obstacle, automatically switches camera to the shoulder that is farther away from the obstacle.
This setting can only be configured by editing the settings configuration file.
zoom out when move coef
-----------------------
:Type: floating point
:Range: Any
:Default: 20
This setting makes difference only in third person mode if 'view over shoulder' is enabled.
Slightly pulls camera away (or closer in case of negative value) when the character moves. To disable set it to zero.
This setting can only be configured by editing the settings configuration file.
preview if stand still
----------------------
:Type: boolean
:Range: True/False
:Default: False
If enabled then the character rotation is not synchonized with the camera rotation while the character doesn't move and not in combat mode.
This setting can only be configured by editing the settings configuration file.
deferred preview rotation
-------------------------
:Type: boolean
:Range: True/False
:Default: True
Makes difference only in third person mode.
If enabled then the character smoothly rotates to the view direction after exiting preview or vanity mode.
If disabled then the camera rotates rather than the character.
This setting can only be configured by editing the settings configuration file.

@ -42,6 +42,18 @@ view over shoulder = false
# Makes sense only if 'view over shoulder' is true. First number is horizontal offset (negative value means offset to the left), second number is vertical offset.
view over shoulder offset = 30 -10
# Switch shoulder automatically when player is close to an obstacle.
auto switch shoulder = true
# Slightly pulls camera away when the character moves. Works only in 'view over shoulder' mode. Set to 0 to disable.
zoom out when move coef = 20
# Automatically enable preview mode when player doesn't move.
preview if stand still = false
# Rotate the character to the view direction after exiting preview mode.
deferred preview rotation = true
[Cells]
# Preload cells in a background thread. All settings starting with 'preload' have no effect unless this is enabled.

Loading…
Cancel
Save