1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-04-01 08:06:45 +00:00

Merge branch 'master' into 'resize_breaks_window'

This commit is contained in:
Mads Buvik Sandvei 2020-08-05 17:10:16 +00:00
commit 045b4566dd
92 changed files with 2512 additions and 1389 deletions

View file

@ -46,26 +46,184 @@ MacOS:
paths: paths:
- build/OpenMW-*.dmg - 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: tags:
- windows - 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' --version=3.18.0 -y
- choco install vswhere -y
- choco install ninja -y
- choco install python -y
- refreshenv
stage: build stage: build
allow_failure: true
script: script:
- Set-Variable -Name "time" -Value (date -Format "%H:%m") - $time = (Get-Date -Format "HH:mm:ss")
- echo ${time} - echo ${time}
- echo "started by ${GITLAB_USER_NAME}" - 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 - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N
# TODO: on gitlab's new shared windows runners. They currently don't have bash or anything else installed - cd MSVC2019_64_Ninja
# TODO: it is currently just a bare windows 10 with powershell. - .\ActivateMSVC.ps1
# - env # turn on for debugging - cmake --build . --config $config --target ($targets.Split(','))
# - sh %CI_PROJECT_DIR%/CI/before_script.msvc.sh -c Release -p x64 -v 2017 -V - cd $config
# - 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% if (Get-ChildItem -Recurse *.pdb) {
# - 7z a OpenMW_MSVC2017_64_%CI_BUILD_REF_NAME%_%CI_BUILD_ID%.zip %CI_PROJECT_DIR%\MSVC2017_64\Release\ 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: cache:
key: ninja-v2
paths: paths:
- deps - deps
- MSVC2019_64_Ninja/deps/Qt
artifacts: artifacts:
when: always
paths: paths:
- "*.zip" - "*.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
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
- 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"

View file

@ -37,9 +37,9 @@ addons:
build_command: "make VERBOSE=1 -j3" build_command: "make VERBOSE=1 -j3"
matrix: matrix:
include: include:
- name: OpenMW (all) on MacOS 10.15 with Xcode 12 - name: OpenMW (all) on MacOS 10.15 with Xcode 11.6
os: osx os: osx
osx_image: xcode12 osx_image: xcode11.6
if: branch != coverity_scan if: branch != coverity_scan
- name: OpenMW (all) on Ubuntu Focal with GCC - name: OpenMW (all) on Ubuntu Focal with GCC
os: linux os: linux
@ -71,8 +71,8 @@ before_script:
script: script:
- cd ./build - cd ./build
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ${ANALYZE} make -j3; fi - 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 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 ../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 ./openmw_test_suite; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
- cd "${TRAVIS_BUILD_DIR}" - cd "${TRAVIS_BUILD_DIR}"

View file

@ -10,7 +10,10 @@ If you feel your name is missing from this list, please notify a developer.
Programmers Programmers
----------- -----------
Marc Zinnschlag (Zini) - Lead Programmer/Project Manager Bret Curtis (psi29a) - Project leader 2019-present
Marc Zinnschlag (Zini) - Project leader 2010-2018
Nicolay Korslund - Project leader 2008-2010
scrawl - Top contributor
Adam Hogan (aurix) Adam Hogan (aurix)
Aesylwinn Aesylwinn
@ -39,7 +42,6 @@ Programmers
Austin Salgat (Salgat) Austin Salgat (Salgat)
Ben Shealy (bentsherman) Ben Shealy (bentsherman)
Berulacks Berulacks
Bret Curtis (psi29a)
Britt Mathis (galdor557) Britt Mathis (galdor557)
Capostrophic Capostrophic
Carl Maxwell Carl Maxwell
@ -146,7 +148,6 @@ Programmers
Nathan Jeffords (blunted2night) Nathan Jeffords (blunted2night)
NeveHanter NeveHanter
Nialsy Nialsy
Nicolay Korslund
Nikolay Kasyanov (corristo) Nikolay Kasyanov (corristo)
nobrakal nobrakal
Nolan Poe (nopoe) Nolan Poe (nopoe)
@ -175,7 +176,6 @@ Programmers
Roman Siromakha (elsid) Roman Siromakha (elsid)
Sandy Carter (bwrsandman) Sandy Carter (bwrsandman)
Scott Howard (maqifrnswa) Scott Howard (maqifrnswa)
scrawl
Sebastian Wick (swick) Sebastian Wick (swick)
Sergey Fukanchik Sergey Fukanchik
Sergey Shambir (sergey-shambir) Sergey Shambir (sergey-shambir)

View file

@ -7,6 +7,7 @@
Bug #3676: NiParticleColorModifier isn't applied properly Bug #3676: NiParticleColorModifier isn't applied properly
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
Bug #4021: Attributes and skills are not stored as floats Bug #4021: Attributes and skills are not stored as floats
Bug #4055: Local scripts don't inherit variables from their base record
Bug #4623: Corprus implementation is incorrect Bug #4623: Corprus implementation is incorrect
Bug #4764: Data race in osg ParticleSystem Bug #4764: Data race in osg ParticleSystem
Bug #4774: Guards are ignorant of an invisible player that tries to attack them Bug #4774: Guards are ignorant of an invisible player that tries to attack them
@ -40,7 +41,9 @@
Bug #5499: Faction advance is available when requirements not met Bug #5499: Faction advance is available when requirements not met
Bug #5502: Dead zone for analogue stick movement is too small Bug #5502: Dead zone for analogue stick movement is too small
Bug #5507: Sound volume is not clamped on ingame settings update Bug #5507: Sound volume is not clamped on ingame settings update
Bug #5531: Actors flee using current rotation by axis x
Bug #5539: Window resize breaks when going from a lower resolution to full screen resolution Bug #5539: Window resize breaks when going from a lower resolution to full screen resolution
Bug #5548: Certain exhausted topics can be highlighted again even though there's no new dialogue
Feature #390: 3rd person look "over the shoulder" Feature #390: 3rd person look "over the shoulder"
Feature #2386: Distant Statics in the form of Object Paging 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 #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher
@ -48,8 +51,12 @@
Feature #5445: Handle NiLines Feature #5445: Handle NiLines
Feature #5457: Realistic diagonal movement Feature #5457: Realistic diagonal movement
Feature #5486: Fixes trainers to choose their training skills based on their base skill points Feature #5486: Fixes trainers to choose their training skills based on their base skill points
Feature #5519: Code Patch tab in launcher
Feature #5524: Resume failed script execution after reload Feature #5524: Resume failed script execution after reload
Feature #5525: Search fields tweaks (utf-8)
Feature #5545: Option to allow stealing from an unconscious NPC during combat
Task #5480: Drop Qt4 support Task #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation
0.46.0 0.46.0
------ ------

View file

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

View file

@ -258,10 +258,10 @@ download() {
if [ -z $VERBOSE ]; then if [ -z $VERBOSE ]; then
RET=0 RET=0
curl --silent --retry 10 -kLy 5 -o $FILE $URL || RET=$? curl --silent --retry 10 -Ly 5 -o $FILE $URL || RET=$?
else else
RET=0 RET=0
curl --retry 10 -kLy 5 -o $FILE $URL || RET=$? curl --retry 10 -Ly 5 -o $FILE $URL || RET=$?
fi fi
if [ $RET -ne 0 ]; then if [ $RET -ne 0 ]; then

View file

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

View file

@ -487,9 +487,7 @@ if(WIN32)
INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt")
INSTALL(FILES INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/mygui/DejaVuFontLicense.txt" DESTINATION ".")
"${OpenMW_SOURCE_DIR}/files/mygui/DejaVu Font License.txt"
DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/settings-default.cfg" DESTINATION "." CONFIGURATIONS Debug) INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/settings-default.cfg" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/settings-default.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/settings-default.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Debug) INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Debug)

View file

@ -13,7 +13,7 @@ OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set.
* IRC: #openmw on irc.freenode.net * IRC: #openmw on irc.freenode.net
Font Licenses: Font Licenses:
* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVu Font License.txt](https://github.com/OpenMW/openmw/blob/master/files/mygui/DejaVu%20Font%20License.txt) for more information) * DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVuFontLicense.txt](https://github.com/OpenMW/openmw/blob/master/files/mygui/DejaVuFontLicense.txt) for more information)
Current Status Current Status
-------------- --------------

View file

@ -4,9 +4,43 @@
#include <components/config/launchersettings.hpp> #include <components/config/launchersettings.hpp>
#include <QFileDialog> #include <QFileDialog>
#include <QCompleter> #include <QCompleter>
#include <QProxyStyle>
#include <components/contentselector/view/contentselector.hpp> #include <components/contentselector/view/contentselector.hpp>
#include <components/contentselector/model/esmfile.hpp> #include <components/contentselector/model/esmfile.hpp>
#include <cmath>
class HorizontalTextWestTabStyle : public QProxyStyle
{
public:
QSize sizeFromContents(ContentsType type, const QStyleOption* option, const QSize& size, const QWidget* widget) const
{
QSize s = QProxyStyle::sizeFromContents(type, option, size, widget);
if (type == QStyle::CT_TabBarTab)
{
s.transpose();
s.setHeight(s.height() + 20);
}
return s;
}
void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const
{
if (element == CE_TabBarTabLabel)
{
if (const QStyleOptionTab* tab = qstyleoption_cast<const QStyleOptionTab*>(option))
{
QStyleOptionTab opt(*tab);
opt.shape = QTabBar::RoundedNorth;
QProxyStyle::drawControl(element, &opt, painter, widget);
return;
}
}
QProxyStyle::drawControl(element, option, painter, widget);
}
};
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg,
Config::GameSettings &gameSettings, Config::GameSettings &gameSettings,
Settings::Manager &engineSettings, QWidget *parent) Settings::Manager &engineSettings, QWidget *parent)
@ -19,15 +53,16 @@ Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg,
setupUi(this); setupUi(this);
loadSettings(); loadSettings();
AdvancedTabWidget->tabBar()->setStyle(new HorizontalTextWestTabStyle);
mCellNameCompleter.setModel(&mCellNameCompleterModel);
startDefaultCharacterAtField->setCompleter(&mCellNameCompleter);
} }
void Launcher::AdvancedPage::loadCellsForAutocomplete(QStringList cellNames) { void Launcher::AdvancedPage::loadCellsForAutocomplete(QStringList cellNames) {
// Set up an auto-completer for the "Start default character at" field // Update the list of suggestions for the "Start default character at" field
auto *completer = new QCompleter(cellNames); mCellNameCompleterModel.setStringList(cellNames);
completer->setCompletionMode(QCompleter::PopupCompletion); mCellNameCompleter.setCompletionMode(QCompleter::PopupCompletion);
completer->setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); mCellNameCompleter.setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
startDefaultCharacterAtField->setCompleter(completer);
} }
void Launcher::AdvancedPage::on_skipMenuCheckBox_stateChanged(int state) { void Launcher::AdvancedPage::on_skipMenuCheckBox_stateChanged(int state) {
@ -55,33 +90,45 @@ void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked()
runScriptAfterStartupField->setText(path); runScriptAfterStartupField->setText(path);
} }
namespace
{
constexpr double CellSizeInUnits = 8192;
double convertToCells(double unitRadius)
{
return std::round((unitRadius / 0.93 + 1024) / CellSizeInUnits);
}
double convertToUnits(double CellGridRadius)
{
return (CellSizeInUnits * CellGridRadius - 1024) * 0.93;
}
}
bool Launcher::AdvancedPage::loadSettings() bool Launcher::AdvancedPage::loadSettings()
{ {
// Testing // Game mechanics
bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1; {
if (skipMenu) { loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
skipMenuCheckBox->setCheckState(Qt::Checked);
}
startDefaultCharacterAtLabel->setEnabled(skipMenu);
startDefaultCharacterAtField->setEnabled(skipMenu);
startDefaultCharacterAtField->setText(mGameSettings.value("start"));
runScriptAfterStartupField->setText(mGameSettings.value("script-run"));
// Game Settings
loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
loadSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game");
loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
loadSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game"); loadSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game");
loadSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game"); loadSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game");
loadSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game");
loadSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game");
loadSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
int unarmedFactorsStrengthIndex = mEngineSettings.getInt("strength influences hand to hand", "Game"); int unarmedFactorsStrengthIndex = mEngineSettings.getInt("strength influences hand to hand", "Game");
if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2) if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2)
unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex); unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex);
loadSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game"); loadSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game");
}
// Visuals
{
loadSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders");
loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool))); connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool)));
loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
if (animSourcesCheckBox->checkState()) if (animSourcesCheckBox->checkState())
@ -89,18 +136,21 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
} }
loadSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game"); loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
loadSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game"); loadSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game");
// Input Settings const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain");
loadSettingBool(grabCursorCheckBox, "grab cursor", "Input"); const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain");
loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); if (distantTerrain && objectPaging) {
distantLandCheckBox->setCheckState(Qt::Checked);
}
// Saves Settings loadSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain");
loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); viewingDistanceComboBox->setValue(convertToCells(mEngineSettings.getInt("viewing distance", "Camera")));
maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves")); }
// User Interface Settings // Interface Changes
{
loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game");
loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
@ -110,66 +160,93 @@ bool Launcher::AdvancedPage::loadSettings()
// Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid. // 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) if (showOwnedIndex >= 0 && showOwnedIndex <= 3)
showOwnedComboBox->setCurrentIndex(showOwnedIndex); showOwnedComboBox->setCurrentIndex(showOwnedIndex);
}
// Bug fixes
{
loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
loadSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game");
}
// Miscellaneous
{
// Saves
loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves"));
// Other Settings // Other Settings
QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper(); QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper();
if (screenshotFormatComboBox->findText(screenshotFormatString) == -1) if (screenshotFormatComboBox->findText(screenshotFormatString) == -1)
screenshotFormatComboBox->addItem(screenshotFormatString); screenshotFormatComboBox->addItem(screenshotFormatString);
screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString)); screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString));
}
// Testing
{
loadSettingBool(grabCursorCheckBox, "grab cursor", "Input");
bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1;
if (skipMenu)
{
skipMenuCheckBox->setCheckState(Qt::Checked);
}
startDefaultCharacterAtLabel->setEnabled(skipMenu);
startDefaultCharacterAtField->setEnabled(skipMenu);
startDefaultCharacterAtField->setText(mGameSettings.value("start"));
runScriptAfterStartupField->setText(mGameSettings.value("script-run"));
}
return true; return true;
} }
void Launcher::AdvancedPage::saveSettings() void Launcher::AdvancedPage::saveSettings()
{ {
// Ensure we only set the new settings if they changed. This is to avoid cluttering the // Game mechanics
// user settings file (which by definition should only contain settings the user has touched) {
saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
// Testing
int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked;
if (skipMenu != mGameSettings.value("skip-menu").toInt())
mGameSettings.setValue("skip-menu", QString::number(skipMenu));
QString startCell = startDefaultCharacterAtField->text();
if (startCell != mGameSettings.value("start")) {
mGameSettings.setValue("start", startCell);
}
QString scriptRun = runScriptAfterStartupField->text();
if (scriptRun != mGameSettings.value("script-run"))
mGameSettings.setValue("script-run", scriptRun);
// Game Settings
saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
saveSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game");
saveSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game"); saveSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game");
saveSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game"); saveSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game");
saveSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game");
saveSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game");
saveSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex(); int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex();
if (unarmedFactorsStrengthIndex != mEngineSettings.getInt("strength influences hand to hand", "Game")) if (unarmedFactorsStrengthIndex != mEngineSettings.getInt("strength influences hand to hand", "Game"))
mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex); mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex);
saveSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game"); saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game");
}
// Visuals
{
saveSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders");
saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
saveSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); saveSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
saveSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game"); saveSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
saveSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game"); saveSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game");
// Input Settings const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain");
saveSettingBool(grabCursorCheckBox, "grab cursor", "Input"); const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain");
saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); const bool wantDistantLand = distantLandCheckBox->checkState();
if (wantDistantLand != (distantTerrain && objectPaging)) {
// Saves Settings mEngineSettings.setBool("distant terrain", "Terrain", wantDistantLand);
saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); mEngineSettings.setBool("object paging", "Terrain", wantDistantLand);
int maximumQuicksaves = maximumQuicksavesComboBox->value();
if (maximumQuicksaves != mEngineSettings.getInt("max quicksaves", "Saves")) {
mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves);
} }
// User Interface Settings saveSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain");
double viewingDistance = viewingDistanceComboBox->value();
if (viewingDistance != convertToCells(mEngineSettings.getInt("viewing distance", "Camera")))
{
mEngineSettings.setInt("viewing distance", "Camera", convertToUnits(viewingDistance));
}
}
// Interface Changes
{
saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game");
saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
@ -178,6 +255,23 @@ void Launcher::AdvancedPage::saveSettings()
int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); int showOwnedCurrentIndex = showOwnedComboBox->currentIndex();
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))
mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex); mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex);
}
// Bug fixes
{
saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
saveSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game");
}
// Miscellaneous
{
// Saves Settings
saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
int maximumQuicksaves = maximumQuicksavesComboBox->value();
if (maximumQuicksaves != mEngineSettings.getInt("max quicksaves", "Saves"))
{
mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves);
}
// Other Settings // Other Settings
std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString();
@ -185,12 +279,33 @@ void Launcher::AdvancedPage::saveSettings()
mEngineSettings.setString("screenshot format", "General", screenshotFormatString); mEngineSettings.setString("screenshot format", "General", screenshotFormatString);
} }
void Launcher::AdvancedPage::loadSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) { // Testing
{
saveSettingBool(grabCursorCheckBox, "grab cursor", "Input");
int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked;
if (skipMenu != mGameSettings.value("skip-menu").toInt())
mGameSettings.setValue("skip-menu", QString::number(skipMenu));
QString startCell = startDefaultCharacterAtField->text();
if (startCell != mGameSettings.value("start"))
{
mGameSettings.setValue("start", startCell);
}
QString scriptRun = runScriptAfterStartupField->text();
if (scriptRun != mGameSettings.value("script-run"))
mGameSettings.setValue("script-run", scriptRun);
}
}
void Launcher::AdvancedPage::loadSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group)
{
if (mEngineSettings.getBool(setting, group)) if (mEngineSettings.getBool(setting, group))
checkbox->setCheckState(Qt::Checked); checkbox->setCheckState(Qt::Checked);
} }
void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) { void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group)
{
bool cValue = checkbox->checkState(); bool cValue = checkbox->checkState();
if (cValue != mEngineSettings.getBool(setting, group)) if (cValue != mEngineSettings.getBool(setting, group))
mEngineSettings.setBool(setting, group, cValue); mEngineSettings.setBool(setting, group, cValue);

View file

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

View file

@ -82,7 +82,7 @@ add_openmw_dir (mwclass
) )
add_openmw_dir (mwmechanics add_openmw_dir (mwmechanics
mechanicsmanagerimp stat creaturestats magiceffects movement actorutil mechanicsmanagerimp stat creaturestats magiceffects movement actorutil spelllist
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning

View file

@ -416,12 +416,15 @@ namespace MWBase
virtual void togglePOV(bool force = false) = 0; virtual void togglePOV(bool force = false) = 0;
virtual bool isFirstPerson() const = 0; virtual bool isFirstPerson() const = 0;
virtual bool isPreviewModeEnabled() const = 0;
virtual void togglePreviewMode(bool enable) = 0; virtual void togglePreviewMode(bool enable) = 0;
virtual bool toggleVanityMode(bool enable) = 0; virtual bool toggleVanityMode(bool enable) = 0;
virtual void allowVanityMode(bool allow) = 0; virtual void allowVanityMode(bool allow) = 0;
virtual void changeVanityModeScale(float factor) = 0; virtual void changeVanityModeScale(float factor) = 0;
virtual bool vanityRotateCamera(float * rot) = 0; virtual bool vanityRotateCamera(float * rot) = 0;
virtual void setCameraDistance(float dist, bool adjust = false, bool override = true)=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 setupPlayer() = 0;
virtual void renderPlayer() = 0; virtual void renderPlayer() = 0;

View file

@ -141,14 +141,9 @@ namespace MWClass
data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr)); data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr));
// spells // spells
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin()); bool spellsInitialised = data->mCreatureStats.getSpells().setSpells(ref->mBase->mId);
iter!=ref->mBase->mSpells.mList.end(); ++iter) if (!spellsInitialised)
{ data->mCreatureStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList);
if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
data->mCreatureStats.getSpells().add (spell);
else /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility
Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << *iter << "' on creature '" << ref->mBase->mId << "'";
}
// inventory // inventory
bool hasInventory = hasInventoryStore(ptr); bool hasInventory = hasInventoryStore(ptr);
@ -781,6 +776,9 @@ namespace MWClass
CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
const ESM::CreatureState& creatureState = state.asCreatureState(); const ESM::CreatureState& creatureState = state.asCreatureState();
customData.mContainerStore->readState (creatureState.mInventory); customData.mContainerStore->readState (creatureState.mInventory);
bool spellsInitialised = customData.mCreatureStats.getSpells().setSpells(ptr.get<ESM::Creature>()->mBase->mId);
if(spellsInitialised)
customData.mCreatureStats.getSpells().clear();
customData.mCreatureStats.readState (creatureState.mCreatureStats); customData.mCreatureStats.readState (creatureState.mCreatureStats);
} }
@ -874,6 +872,11 @@ namespace MWClass
MWMechanics::setBaseAISetting<ESM::Creature>(id, setting, value); MWMechanics::setBaseAISetting<ESM::Creature>(id, setting, value);
} }
void Creature::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const
{
MWMechanics::modifyBaseInventory<ESM::Creature>(actorId, itemId, amount);
}
float Creature::getWalkSpeed(const MWWorld::Ptr& ptr) const float Creature::getWalkSpeed(const MWWorld::Ptr& ptr) const
{ {
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);

View file

@ -132,6 +132,8 @@ namespace MWClass
virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const; virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const;
virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const;
float getWalkSpeed(const MWWorld::Ptr& ptr) const final; float getWalkSpeed(const MWWorld::Ptr& ptr) const final;
float getRunSpeed(const MWWorld::Ptr& ptr) const final; float getRunSpeed(const MWWorld::Ptr& ptr) const final;

View file

@ -158,7 +158,7 @@ namespace
* *
* and by adding class, race, specialization bonus. * and by adding class, race, specialization bonus.
*/ */
void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr) void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr, bool spellsInitialised)
{ {
const ESM::Class *class_ = const ESM::Class *class_ =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass); MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);
@ -235,9 +235,11 @@ namespace
for (int i=0; i<ESM::Attribute::Length; ++i) for (int i=0; i<ESM::Attribute::Length; ++i)
attributes[i] = npcStats.getAttribute(i).getBase(); attributes[i] = npcStats.getAttribute(i).getBase();
if (!spellsInitialised)
{
std::vector<std::string> spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); std::vector<std::string> spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race);
for (std::vector<std::string>::iterator it = spells.begin(); it != spells.end(); ++it) npcStats.getSpells().addAllToInstance(spells);
npcStats.getSpells().add(*it); }
} }
} }
@ -311,6 +313,8 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
bool spellsInitialised = data->mNpcStats.getSpells().setSpells(ref->mBase->mId);
// creature stats // creature stats
int gold=0; int gold=0;
if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
@ -351,7 +355,7 @@ namespace MWClass
data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation); data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation);
autoCalculateAttributes(ref->mBase, data->mNpcStats); autoCalculateAttributes(ref->mBase, data->mNpcStats);
autoCalculateSkills(ref->mBase, data->mNpcStats, ptr); autoCalculateSkills(ref->mBase, data->mNpcStats, ptr, spellsInitialised);
data->mNpcStats.setNeedRecalcDynamicStats(true); data->mNpcStats.setNeedRecalcDynamicStats(true);
} }
@ -362,14 +366,7 @@ namespace MWClass
// race powers // race powers
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace); const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
for (std::vector<std::string>::const_iterator iter (race->mPowers.mList.begin()); data->mNpcStats.getSpells().addAllToInstance(race->mPowers.mList);
iter!=race->mPowers.mList.end(); ++iter)
{
if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
data->mNpcStats.getSpells().add (spell);
else
Log(Debug::Warning) << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'";
}
if (!ref->mBase->mFaction.empty()) if (!ref->mBase->mFaction.empty())
{ {
@ -390,17 +387,8 @@ namespace MWClass
data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm);
// spells // spells
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin()); if (!spellsInitialised)
iter!=ref->mBase->mSpells.mList.end(); ++iter) data->mNpcStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList);
{
if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
data->mNpcStats.getSpells().add (spell);
else
{
/// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility
Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << *iter << "' on NPC '" << ref->mBase->mId << "'";
}
}
// inventory // inventory
// setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items
@ -904,13 +892,19 @@ namespace MWClass
} }
else if (!stats.getAiSequence().isInCombat()) else if (!stats.getAiSequence().isInCombat())
{ {
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak) || stats.getKnockedDown()) if (stats.getKnockedDown() || MWBase::Environment::get().getMechanicsManager()->isSneaking(actor))
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
// Can't talk to werewolves // Can't talk to werewolves
if (!getNpcStats(ptr).isWerewolf()) if (!getNpcStats(ptr).isWerewolf())
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr)); return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
} }
else // In combat
{
const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game");
if (stealingInCombat && stats.getKnockedDown())
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
}
// Tribunal and some mod companions oddly enough must use open action as fallback // Tribunal and some mod companions oddly enough must use open action as fallback
if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion"))
@ -1062,7 +1056,14 @@ namespace MWClass
if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished()) if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished())
return true; return true;
return !customData.mNpcStats.getAiSequence().isInCombat(); if (!customData.mNpcStats.getAiSequence().isInCombat())
return true;
const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game");
if (stealingInCombat && customData.mNpcStats.getKnockedDown())
return true;
return false;
} }
MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const
@ -1313,6 +1314,9 @@ namespace MWClass
const ESM::NpcState& npcState = state.asNpcState(); const ESM::NpcState& npcState = state.asNpcState();
customData.mInventoryStore.readState (npcState.mInventory); customData.mInventoryStore.readState (npcState.mInventory);
customData.mNpcStats.readState (npcState.mNpcStats); customData.mNpcStats.readState (npcState.mNpcStats);
bool spellsInitialised = customData.mNpcStats.getSpells().setSpells(ptr.get<ESM::NPC>()->mBase->mId);
if(spellsInitialised)
customData.mNpcStats.getSpells().clear();
customData.mNpcStats.readState (npcState.mCreatureStats); customData.mNpcStats.readState (npcState.mCreatureStats);
} }
@ -1447,6 +1451,11 @@ namespace MWClass
MWMechanics::setBaseAISetting<ESM::NPC>(id, setting, value); MWMechanics::setBaseAISetting<ESM::NPC>(id, setting, value);
} }
void Npc::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const
{
MWMechanics::modifyBaseInventory<ESM::NPC>(actorId, itemId, amount);
}
float Npc::getWalkSpeed(const MWWorld::Ptr& ptr) const float Npc::getWalkSpeed(const MWWorld::Ptr& ptr) const
{ {
const GMST& gmst = getGmst(); const GMST& gmst = getGmst();

View file

@ -167,6 +167,8 @@ namespace MWClass
virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const; virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const;
virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const;
float getWalkSpeed(const MWWorld::Ptr& ptr) const final; float getWalkSpeed(const MWWorld::Ptr& ptr) const final;
float getRunSpeed(const MWWorld::Ptr& ptr) const final; float getRunSpeed(const MWWorld::Ptr& ptr) const final;

View file

@ -121,6 +121,7 @@ namespace MWDialogue
mTalkedTo = creatureStats.hasTalkedToPlayer(); mTalkedTo = creatureStats.hasTalkedToPlayer();
mActorKnownTopics.clear(); mActorKnownTopics.clear();
mActorKnownTopicsFlag.clear();
//greeting //greeting
const MWWorld::Store<ESM::Dialogue> &dialogs = const MWWorld::Store<ESM::Dialogue> &dialogs =
@ -300,11 +301,11 @@ namespace MWDialogue
} }
} }
mLastTopic = topic;
executeScript (info->mResultScript, mActor); executeScript (info->mResultScript, mActor);
parseText (info->mResponse); parseText (info->mResponse);
mLastTopic = topic;
} }
} }
@ -323,6 +324,7 @@ namespace MWDialogue
updateGlobals(); updateGlobals();
mActorKnownTopics.clear(); mActorKnownTopics.clear();
mActorKnownTopicsFlag.clear();
const auto& dialogs = MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>(); const auto& dialogs = MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();

View file

@ -678,7 +678,6 @@ namespace MWGui
{ {
mHistoryContents.push_back(new Response(text, title, needMargin)); mHistoryContents.push_back(new Response(text, title, needMargin));
updateHistory(); updateHistory();
updateTopics();
} }
void DialogueWindow::addMessageBox(const std::string& text) void DialogueWindow::addMessageBox(const std::string& text)

View file

@ -110,23 +110,21 @@ namespace MWInput
if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch")) 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 (mBindingsManager->actionIsActive(A_TogglePOV))
{ {
if (mPreviewPOVDelay <= 0.5 && if (world->isFirstPerson() ? mPreviewPOVDelay > switchLimit : mPreviewPOVDelay == 0)
(mPreviewPOVDelay += dt) > 0.5) world->togglePreviewMode(true);
{ mPreviewPOVDelay += dt;
mPreviewPOVDelay = 1.f;
MWBase::Environment::get().getWorld()->togglePreviewMode(true);
}
} }
else else
{ {
//disable preview mode //disable preview mode
MWBase::Environment::get().getWorld()->togglePreviewMode(false); if (mPreviewPOVDelay > 0)
if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= 0.5) world->togglePreviewMode(false);
{ if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= switchLimit)
MWBase::Environment::get().getWorld()->togglePOV(); world->togglePOV();
}
mPreviewPOVDelay = 0.f; mPreviewPOVDelay = 0.f;
} }
} }
@ -170,6 +168,11 @@ namespace MWInput
mAttemptJump = false; mAttemptJump = false;
} }
bool ActionManager::isPreviewModeEnabled()
{
return MWBase::Environment::get().getWorld()->isPreviewModeEnabled();
}
void ActionManager::resetIdleTime() void ActionManager::resetIdleTime()
{ {
if (mTimeIdle < 0) if (mTimeIdle < 0)

View file

@ -54,7 +54,7 @@ namespace MWInput
void setAttemptJump(bool enabled) { mAttemptJump = enabled; } void setAttemptJump(bool enabled) { mAttemptJump = enabled; }
float getPreviewDelay() const { return mPreviewPOVDelay; }; bool isPreviewModeEnabled();
private: private:
void handleGuiArrowKey(int action); void handleGuiArrowKey(int action);

View file

@ -89,7 +89,7 @@ namespace MWInput
bool ControllerManager::update(float dt) bool ControllerManager::update(float dt)
{ {
mGamepadPreviewMode = mActionManager->getPreviewDelay() == 1.f; mGamepadPreviewMode = mActionManager->isPreviewModeEnabled();
if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled)) if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled))
{ {
@ -287,7 +287,7 @@ namespace MWInput
} }
else else
{ {
if (mGamepadPreviewMode && arg.value) // Preview Mode Gamepad Zooming if (mGamepadPreviewMode) // Preview Mode Gamepad Zooming
{ {
if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT)
{ {

View file

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

View file

@ -108,6 +108,8 @@ namespace MWInput
player.yaw(x); player.yaw(x);
player.pitch(y); 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 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; return;
float rot[3]; 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[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 // 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(); MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
player.yaw(rot[2]); player.yaw(-rot[2]);
player.pitch(rot[0]); player.pitch(-rot[0]);
} }
else if (!controls)
MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation();
MWBase::Environment::get().getInputManager()->resetIdleTime(); MWBase::Environment::get().getInputManager()->resetIdleTime();
} }

View file

@ -249,17 +249,20 @@ namespace MWInput
if (!mGuiCursorEnabled) if (!mGuiCursorEnabled)
{ {
float rot[3]; 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[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 // 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(); MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
player.yaw(rot[2]); player.yaw(-rot[2]);
player.pitch(rot[0]); player.pitch(-rot[0]);
} }
else if (!playerLooking)
MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation();
MWBase::Environment::get().getInputManager()->resetIdleTime(); MWBase::Environment::get().getInputManager()->resetIdleTime();
} }

View file

@ -1973,10 +1973,6 @@ namespace MWMechanics
// One case where we need this is to make sure bound items are removed upon death // One case where we need this is to make sure bound items are removed upon death
stats.modifyMagicEffects(MWMechanics::MagicEffects()); stats.modifyMagicEffects(MWMechanics::MagicEffects());
stats.getActiveSpells().clear(); stats.getActiveSpells().clear();
if (!isPlayer)
stats.getSpells().clear();
// Make sure spell effects are removed // Make sure spell effects are removed
purgeSpellEffects(stats.getActorId()); purgeSpellEffects(stats.getActorId());

View file

@ -1,6 +1,8 @@
#ifndef OPENMW_MWMECHANICS_ACTORUTIL_H #ifndef OPENMW_MWMECHANICS_ACTORUTIL_H
#define OPENMW_MWMECHANICS_ACTORUTIL_H #define OPENMW_MWMECHANICS_ACTORUTIL_H
#include <algorithm>
#include <components/esm/loadcrea.hpp> #include <components/esm/loadcrea.hpp>
#include <components/esm/loadnpc.hpp> #include <components/esm/loadnpc.hpp>
@ -53,8 +55,34 @@ namespace MWMechanics
MWBase::Environment::get().getWorld()->createOverrideRecord(copy); MWBase::Environment::get().getWorld()->createOverrideRecord(copy);
} }
template<class T>
void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount)
{
ESM::NPC copy = *MWBase::Environment::get().getWorld()->getStore().get<ESM::NPC>().find(actorId);
for(auto& it : copy.mInventory.mList)
{
if(Misc::StringUtils::ciEqual(it.mItem, itemId))
{
int sign = it.mCount < 1 ? -1 : 1;
it.mCount = sign * std::max(it.mCount * sign + amount, 0);
MWBase::Environment::get().getWorld()->createOverrideRecord(copy);
return;
}
}
if(amount > 0)
{
ESM::ContItem cont;
cont.mItem = itemId;
cont.mCount = amount;
copy.mInventory.mList.push_back(cont);
MWBase::Environment::get().getWorld()->createOverrideRecord(copy);
}
}
template void setBaseAISetting<ESM::Creature>(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value); template void setBaseAISetting<ESM::Creature>(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value);
template void setBaseAISetting<ESM::NPC>(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value); template void setBaseAISetting<ESM::NPC>(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value);
template void modifyBaseInventory<ESM::Creature>(const std::string& actorId, const std::string& itemId, int amount);
template void modifyBaseInventory<ESM::NPC>(const std::string& actorId, const std::string& itemId, int amount);
} }
#endif #endif

View file

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

View file

@ -344,6 +344,7 @@ namespace MWMechanics
{ {
storage.mFleeBlindRunTimer += duration; 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.mRotation[2] = osg::PI + getZAngleToDir(target.getRefData().getPosition().asVec3()-actor.getRefData().getPosition().asVec3());
storage.mMovement.mPosition[1] = 1; storage.mMovement.mPosition[1] = 1;
updateActorsMovement(actor, duration, storage); updateActorsMovement(actor, duration, storage);

View file

@ -124,9 +124,9 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
followDistance = 313; followDistance = 313;
short i = 0; short i = 0;
followers.sort(); 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; followDistance += 130 * i;
++i; ++i;
} }

View file

@ -406,36 +406,36 @@ void AiSequence::fill(const ESM::AIPackageList &list)
if (!list.mList.empty() && list.mList.begin() != (list.mList.end()-1)) if (!list.mList.empty() && list.mList.begin() != (list.mList.end()-1))
mRepeat = true; 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; 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; std::vector<unsigned char> idles;
idles.reserve(8); idles.reserve(8);
for (int i=0; i<8; ++i) for (int i=0; i<8; ++i)
idles.push_back(data.mIdle[i]); idles.push_back(data.mIdle[i]);
package = std::make_unique<MWMechanics::AiWander>(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mShouldRepeat != 0); 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); 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); 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()); 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); package = std::make_unique<MWMechanics::AiFollow>(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ);
} }
mPackages.push_back(std::move(package)); 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. // If there is more than one non-combat, non-pursue package in the list, enable repeating.
int count = 0; int count = 0;
for (std::vector<ESM::AiSequence::AiPackageContainer>::const_iterator it = sequence.mPackages.begin(); for (auto& container : sequence.mPackages)
it != sequence.mPackages.end(); ++it)
{ {
if (isActualAiPackage(static_cast<AiPackageTypeId>(it->mType))) if (isActualAiPackage(static_cast<AiPackageTypeId>(container.mType)))
count++; count++;
} }
@ -468,20 +467,19 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
mRepeat = true; mRepeat = true;
// Load packages // Load packages
for (std::vector<ESM::AiSequence::AiPackageContainer>::const_iterator it = sequence.mPackages.begin(); for (auto& container : sequence.mPackages)
it != sequence.mPackages.end(); ++it)
{ {
std::unique_ptr<MWMechanics::AiPackage> package; std::unique_ptr<MWMechanics::AiPackage> package;
switch (it->mType) switch (container.mType)
{ {
case ESM::AiSequence::Ai_Wander: 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; break;
} }
case ESM::AiSequence::Ai_Travel: 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) if (source->mHidden)
package.reset(new AiInternalTravel(source)); package.reset(new AiInternalTravel(source));
else else
@ -490,27 +488,27 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
} }
case ESM::AiSequence::Ai_Escort: 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; break;
} }
case ESM::AiSequence::Ai_Follow: 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; break;
} }
case ESM::AiSequence::Ai_Activate: 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; break;
} }
case ESM::AiSequence::Ai_Combat: 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; break;
} }
case ESM::AiSequence::Ai_Pursue: 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; break;
} }
default: default:

View file

@ -809,11 +809,11 @@ namespace MWMechanics
void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage) void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage)
{ {
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(npcPos)); 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);
} }
} }
} }

View file

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

View file

@ -2653,11 +2653,11 @@ void CharacterController::updateContinuousVfx()
std::vector<int> effects; std::vector<int> effects;
mAnimation->getLoopingEffects(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() if (mPtr.getClass().getCreatureStats(mPtr).isDeathAnimationFinished()
|| mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(*it)).getMagnitude() <= 0) || mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(effectId)).getMagnitude() <= 0)
mAnimation->removeEffect(*it); mAnimation->removeEffect(effectId);
} }
} }

View file

@ -40,7 +40,7 @@ namespace MWMechanics
continue; continue;
float resist = 0.f; float resist = 0.f;
if (spells.hasCorprusEffect(spell)) if (Spells::hasCorprusEffect(spell))
resist = 1.f - 0.01f * (actorEffects.get(ESM::MagicEffect::ResistCorprusDisease).getMagnitude() resist = 1.f - 0.01f * (actorEffects.get(ESM::MagicEffect::ResistCorprusDisease).getMagnitude()
- actorEffects.get(ESM::MagicEffect::WeaknessToCorprusDisease).getMagnitude()); - actorEffects.get(ESM::MagicEffect::WeaknessToCorprusDisease).getMagnitude());
else if (spell->mData.mType == ESM::Spell::ST_Disease) else if (spell->mData.mType == ESM::Spell::ST_Disease)

View file

@ -31,10 +31,10 @@ namespace MWMechanics
std::vector<std::string> candidates; std::vector<std::string> candidates;
int highestLevel = 0; 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) if (levelledItem.mLevel > highestLevel && levelledItem.mLevel <= playerLevel)
highestLevel = it->mLevel; highestLevel = levelledItem.mLevel;
} }
// For levelled creatures, the flags are swapped. This file format just makes so much sense. // 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; allLevels = levItem->mFlags & ESM::CreatureLevList::AllLevels;
std::pair<int, std::string> highest = std::make_pair(-1, ""); 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 if (playerLevel >= levelledItem.mLevel
&& (allLevels || it->mLevel == highestLevel)) && (allLevels || levelledItem.mLevel == highestLevel))
{ {
candidates.push_back(it->mId); candidates.push_back(levelledItem.mId);
if (it->mLevel >= highest.first) if (levelledItem.mLevel >= highest.first)
highest = std::make_pair(it->mLevel, it->mId); highest = std::make_pair(levelledItem.mLevel, levelledItem.mId);
} }
} }
if (candidates.empty()) if (candidates.empty())

View file

@ -83,7 +83,7 @@ namespace MWMechanics
// reset // reset
creatureStats.setLevel(player->mNpdt.mLevel); creatureStats.setLevel(player->mNpdt.mLevel);
creatureStats.getSpells().clear(); creatureStats.getSpells().clear(true);
creatureStats.modifyMagicEffects(MagicEffects()); creatureStats.modifyMagicEffects(MagicEffects());
for (int i=0; i<27; ++i) for (int i=0; i<27; ++i)

View file

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

View file

@ -36,17 +36,13 @@ namespace MWMechanics
// Check all the doors in this cell // Check all the doors in this cell
const MWWorld::CellRefList<ESM::Door>& doors = cell->getReadOnlyDoors(); 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()); osg::Vec3f pos(actor.getRefData().getPosition().asVec3());
pos.z() = 0; pos.z() = 0;
osg::Vec3f actorDir = (actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,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()); osg::Vec3f doorPos(ref.mData.getPosition().asVec3());
// FIXME: cast // FIXME: cast

View file

@ -0,0 +1,175 @@
#include "spelllist.hpp"
#include <algorithm>
#include <components/esm/loadspel.hpp>
#include <components/misc/rng.hpp>
#include "spells.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/esmstore.hpp"
namespace
{
template<class T>
const std::vector<std::string> getSpellList(const std::string& id)
{
return MWBase::Environment::get().getWorld()->getStore().get<T>().find(id)->mSpells.mList;
}
template<class T>
bool withBaseRecord(const std::string& id, const std::function<bool(std::vector<std::string>&)>& function)
{
T copy = *MWBase::Environment::get().getWorld()->getStore().get<T>().find(id);
bool changed = function(copy.mSpells.mList);
if(changed)
MWBase::Environment::get().getWorld()->createOverrideRecord(copy);
return changed;
}
}
namespace MWMechanics
{
SpellList::SpellList(const std::string& id, int type) : mId(id), mType(type) {}
bool SpellList::withBaseRecord(const std::function<bool(std::vector<std::string>&)>& function)
{
switch(mType)
{
case ESM::REC_CREA:
return ::withBaseRecord<ESM::Creature>(mId, function);
case ESM::REC_NPC_:
return ::withBaseRecord<ESM::NPC>(mId, function);
default:
throw std::logic_error("failed to update base record for " + mId);
}
}
const std::vector<std::string> SpellList::getSpells() const
{
switch(mType)
{
case ESM::REC_CREA:
return getSpellList<ESM::Creature>(mId);
case ESM::REC_NPC_:
return getSpellList<ESM::NPC>(mId);
default:
throw std::logic_error("failed to get spell list for " + mId);
}
}
const ESM::Spell* SpellList::getSpell(const std::string& id)
{
return MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(id);
}
void SpellList::add (const ESM::Spell* spell)
{
auto& id = spell->mId;
bool changed = withBaseRecord([&] (auto& spells)
{
for(auto it : spells)
{
if(Misc::StringUtils::ciEqual(id, it))
return false;
}
spells.push_back(id);
return true;
});
if(changed)
{
for(auto listener : mListeners)
listener->addSpell(spell);
}
}
void SpellList::remove (const ESM::Spell* spell)
{
auto& id = spell->mId;
bool changed = withBaseRecord([&] (auto& spells)
{
for(auto it = spells.begin(); it != spells.end(); it++)
{
if(Misc::StringUtils::ciEqual(id, *it))
{
spells.erase(it);
return true;
}
}
return false;
});
if(changed)
{
for(auto listener : mListeners)
listener->removeSpell(spell);
}
}
void SpellList::removeAll (const std::vector<std::string>& ids)
{
bool changed = withBaseRecord([&] (auto& spells)
{
const auto it = std::remove_if(spells.begin(), spells.end(), [&] (const auto& spell)
{
const auto isSpell = [&] (const auto& id) { return Misc::StringUtils::ciEqual(spell, id); };
return ids.end() != std::find_if(ids.begin(), ids.end(), isSpell);
});
if (it == spells.end())
return false;
spells.erase(it, spells.end());
return true;
});
if(changed)
{
for(auto listener : mListeners)
{
for(auto& id : ids)
{
const auto spell = getSpell(id);
listener->removeSpell(spell);
}
}
}
}
void SpellList::clear()
{
bool changed = withBaseRecord([] (auto& spells)
{
if(spells.empty())
return false;
spells.clear();
return true;
});
if(changed)
{
for(auto listener : mListeners)
listener->removeAllSpells();
}
}
void SpellList::addListener(Spells* spells)
{
for(const auto ptr : mListeners)
{
if(ptr == spells)
return;
}
mListeners.push_back(spells);
}
void SpellList::removeListener(Spells* spells)
{
for(auto it = mListeners.begin(); it != mListeners.end(); it++)
{
if(*it == spells)
{
mListeners.erase(it);
break;
}
}
}
}

View file

@ -0,0 +1,68 @@
#ifndef GAME_MWMECHANICS_SPELLLIST_H
#define GAME_MWMECHANICS_SPELLLIST_H
#include <functional>
#include <map>
#include <string>
#include <set>
#include <vector>
#include <components/esm/loadspel.hpp>
#include "magiceffects.hpp"
namespace ESM
{
struct SpellState;
}
namespace MWMechanics
{
struct SpellParams
{
std::map<int, float> mEffectRands; // <effect index, normalised random magnitude>
std::set<int> mPurgedEffects; // indices of purged effects
};
class Spells;
/// Multiple instances of the same actor share the same spell list in Morrowind.
/// The most obvious result of this is that adding a spell or ability to one instance adds it to all instances.
/// @note The original game will only update visual effects associated with any added abilities for the originally targeted actor,
/// changing cells applies the update to all actors.
/// Aside from sharing the same active spell list, changes made to this list are also written to the actor's base record.
/// Interestingly, it is not just scripted changes that are persisted to the base record. Curing one instance's disease will cure all instances.
/// @note The original game is inconsistent in persisting this example;
/// saving and loading the game might reapply the cured disease depending on which instance was cured.
class SpellList
{
const std::string mId;
const int mType;
std::vector<Spells*> mListeners;
bool withBaseRecord(const std::function<bool(std::vector<std::string>&)>& function);
public:
SpellList(const std::string& id, int type);
/// Get spell from ID, throws exception if not found
static const ESM::Spell* getSpell(const std::string& id);
void add (const ESM::Spell* spell);
///< Adding a spell that is already listed in *this is a no-op.
void remove (const ESM::Spell* spell);
void removeAll(const std::vector<std::string>& spells);
void clear();
///< Remove all spells of all types.
void addListener(Spells* spells);
void removeListener(Spells* spells);
const std::vector<std::string> getSpells() const;
};
}
#endif

View file

@ -1,8 +1,10 @@
#include "spells.hpp" #include "spells.hpp"
#include <components/debug/debuglog.hpp>
#include <components/esm/loadspel.hpp> #include <components/esm/loadspel.hpp>
#include <components/esm/spellstate.hpp> #include <components/esm/spellstate.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/misc/stringops.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -22,46 +24,41 @@ namespace MWMechanics
{ {
} }
Spells::TIterator Spells::begin() const std::map<const ESM::Spell*, SpellParams>::const_iterator Spells::begin() const
{ {
return mSpells.begin(); return mSpells.begin();
} }
Spells::TIterator Spells::end() const std::map<const ESM::Spell*, SpellParams>::const_iterator Spells::end() const
{ {
return mSpells.end(); return mSpells.end();
} }
const ESM::Spell* Spells::getSpell(const std::string& id) const
{
return MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(id);
}
void Spells::rebuildEffects() const void Spells::rebuildEffects() const
{ {
mEffects = MagicEffects(); mEffects = MagicEffects();
mSourcedEffects.clear(); mSourcedEffects.clear();
for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) for (const auto& iter : mSpells)
{ {
const ESM::Spell *spell = iter->first; const ESM::Spell *spell = iter.first;
if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight ||
spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse) spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse)
{ {
int i=0; int i=0;
for (std::vector<ESM::ENAMstruct>::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) for (const auto& effect : spell->mEffects.mList)
{ {
if (iter->second.mPurgedEffects.find(i) != iter->second.mPurgedEffects.end()) if (iter.second.mPurgedEffects.find(i) != iter.second.mPurgedEffects.end())
continue; // effect was purged continue; // effect was purged
float random = 1.f; float random = 1.f;
if (iter->second.mEffectRands.find(i) != iter->second.mEffectRands.end()) if (iter.second.mEffectRands.find(i) != iter.second.mEffectRands.end())
random = iter->second.mEffectRands.at(i); random = iter.second.mEffectRands.at(i);
float magnitude = it->mMagnMin + (it->mMagnMax - it->mMagnMin) * random; float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * random;
mEffects.add (*it, magnitude); mEffects.add (effect, magnitude);
mSourcedEffects[spell].add(MWMechanics::EffectKey(*it), magnitude); mSourcedEffects[spell].add(MWMechanics::EffectKey(effect), magnitude);
++i; ++i;
} }
@ -71,7 +68,7 @@ namespace MWMechanics
bool Spells::hasSpell(const std::string &spell) const bool Spells::hasSpell(const std::string &spell) const
{ {
return hasSpell(getSpell(spell)); return hasSpell(SpellList::getSpell(spell));
} }
bool Spells::hasSpell(const ESM::Spell *spell) const bool Spells::hasSpell(const ESM::Spell *spell) const
@ -80,6 +77,16 @@ namespace MWMechanics
} }
void Spells::add (const ESM::Spell* spell) void Spells::add (const ESM::Spell* spell)
{
mSpellList->add(spell);
}
void Spells::add (const std::string& spellId)
{
add(SpellList::getSpell(spellId));
}
void Spells::addSpell(const ESM::Spell* spell)
{ {
if (mSpells.find (spell)==mSpells.end()) if (mSpells.find (spell)==mSpells.end())
{ {
@ -106,26 +113,26 @@ namespace MWMechanics
} }
} }
void Spells::add (const std::string& spellId)
{
add(getSpell(spellId));
}
void Spells::remove (const std::string& spellId) void Spells::remove (const std::string& spellId)
{ {
const ESM::Spell* spell = getSpell(spellId); const auto spell = SpellList::getSpell(spellId);
TContainer::iterator iter = mSpells.find (spell); removeSpell(spell);
mSpellList->remove(spell);
if (iter!=mSpells.end())
{
mSpells.erase (iter);
mSpellsChanged = true;
}
if (spellId==mSelectedSpell) if (spellId==mSelectedSpell)
mSelectedSpell.clear(); mSelectedSpell.clear();
} }
void Spells::removeSpell(const ESM::Spell* spell)
{
const auto it = mSpells.find(spell);
if(it != mSpells.end())
{
mSpells.erase(it);
mSpellsChanged = true;
}
}
MagicEffects Spells::getMagicEffects() const MagicEffects Spells::getMagicEffects() const
{ {
if (mSpellsChanged) { if (mSpellsChanged) {
@ -135,12 +142,19 @@ namespace MWMechanics
return mEffects; return mEffects;
} }
void Spells::clear() void Spells::removeAllSpells()
{ {
mSpells.clear(); mSpells.clear();
mSpellsChanged = true; mSpellsChanged = true;
} }
void Spells::clear(bool modifyBase)
{
removeAllSpells();
if(modifyBase)
mSpellList->clear();
}
void Spells::setSelectedSpell (const std::string& spellId) void Spells::setSelectedSpell (const std::string& spellId)
{ {
mSelectedSpell = spellId; mSelectedSpell = spellId;
@ -166,101 +180,78 @@ namespace MWMechanics
return false; return false;
} }
bool Spells::hasCommonDisease() const bool Spells::hasDisease(const ESM::Spell::SpellType type) const
{ {
for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) for (const auto& iter : mSpells)
{ {
const ESM::Spell *spell = iter->first; const ESM::Spell *spell = iter.first;
if (spell->mData.mType == ESM::Spell::ST_Disease) if (spell->mData.mType == type)
return true; return true;
} }
return false; return false;
} }
bool Spells::hasCommonDisease() const
{
return hasDisease(ESM::Spell::ST_Disease);
}
bool Spells::hasBlightDisease() const bool Spells::hasBlightDisease() const
{ {
for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) return hasDisease(ESM::Spell::ST_Blight);
{
const ESM::Spell *spell = iter->first;
if (spell->mData.mType == ESM::Spell::ST_Blight)
return true;
} }
return false; void Spells::purge(const SpellFilter& filter)
{
std::vector<std::string> purged;
for (auto iter = mSpells.begin(); iter!=mSpells.end();)
{
const ESM::Spell *spell = iter->first;
if (filter(spell))
{
mSpells.erase(iter++);
purged.push_back(spell->mId);
mSpellsChanged = true;
}
else
++iter;
}
if(!purged.empty())
mSpellList->removeAll(purged);
} }
void Spells::purgeCommonDisease() void Spells::purgeCommonDisease()
{ {
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Disease; });
{
const ESM::Spell *spell = iter->first;
if (spell->mData.mType == ESM::Spell::ST_Disease)
{
mSpells.erase(iter++);
mSpellsChanged = true;
}
else
++iter;
}
} }
void Spells::purgeBlightDisease() void Spells::purgeBlightDisease()
{ {
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Blight && !hasCorprusEffect(spell); });
{
const ESM::Spell *spell = iter->first;
if (spell->mData.mType == ESM::Spell::ST_Blight && !hasCorprusEffect(spell))
{
mSpells.erase(iter++);
mSpellsChanged = true;
}
else
++iter;
}
} }
void Spells::purgeCorprusDisease() void Spells::purgeCorprusDisease()
{ {
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) purge(&hasCorprusEffect);
{
const ESM::Spell *spell = iter->first;
if (hasCorprusEffect(spell))
{
mSpells.erase(iter++);
mSpellsChanged = true;
}
else
++iter;
}
} }
void Spells::purgeCurses() void Spells::purgeCurses()
{ {
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Curse; });
{
const ESM::Spell *spell = iter->first;
if (spell->mData.mType == ESM::Spell::ST_Curse)
{
mSpells.erase(iter++);
mSpellsChanged = true;
}
else
++iter;
}
} }
void Spells::removeEffects(const std::string &id) void Spells::removeEffects(const std::string &id)
{ {
if (isSpellActive(id)) if (isSpellActive(id))
{ {
for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell) for (auto& spell : mSpells)
{ {
if (spell->first == getSpell(id)) if (spell.first == SpellList::getSpell(id))
{ {
for (long unsigned int i = 0; i != spell->first->mEffects.mList.size(); i++) for (long unsigned int i = 0; i != spell.first->mEffects.mList.size(); i++)
{ {
spell->second.mPurgedEffects.insert(i); spell.second.mPurgedEffects.insert(i);
} }
} }
} }
@ -276,23 +267,21 @@ namespace MWMechanics
mSpellsChanged = false; mSpellsChanged = false;
} }
for (std::map<SpellKey, MagicEffects>::const_iterator it = mSourcedEffects.begin(); for (const auto& it : mSourcedEffects)
it != mSourcedEffects.end(); ++it)
{ {
const ESM::Spell * spell = it->first; const ESM::Spell * spell = it.first;
for (MagicEffects::Collection::const_iterator effectIt = it->second.begin(); for (const auto& effectIt : it.second)
effectIt != it->second.end(); ++effectIt)
{ {
visitor.visit(effectIt->first, spell->mName, spell->mId, -1, effectIt->second.getMagnitude()); visitor.visit(effectIt.first, spell->mName, spell->mId, -1, effectIt.second.getMagnitude());
} }
} }
} }
bool Spells::hasCorprusEffect(const ESM::Spell *spell) bool Spells::hasCorprusEffect(const ESM::Spell *spell)
{ {
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) for (const auto& effectIt : spell->mEffects.mList)
{ {
if (effectIt->mEffectID == ESM::MagicEffect::Corprus) if (effectIt.mEffectID == ESM::MagicEffect::Corprus)
{ {
return true; return true;
} }
@ -302,14 +291,14 @@ namespace MWMechanics
void Spells::purgeEffect(int effectId) void Spells::purgeEffect(int effectId)
{ {
for (TContainer::iterator spellIt = mSpells.begin(); spellIt != mSpells.end(); ++spellIt) for (auto& spellIt : mSpells)
{ {
int i = 0; int i = 0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = spellIt->first->mEffects.mList.begin(); effectIt != spellIt->first->mEffects.mList.end(); ++effectIt) for (auto& effectIt : spellIt.first->mEffects.mList)
{ {
if (effectIt->mEffectID == effectId) if (effectIt.mEffectID == effectId)
{ {
spellIt->second.mPurgedEffects.insert(i); spellIt.second.mPurgedEffects.insert(i);
mSpellsChanged = true; mSpellsChanged = true;
} }
++i; ++i;
@ -319,15 +308,15 @@ namespace MWMechanics
void Spells::purgeEffect(int effectId, const std::string & sourceId) void Spells::purgeEffect(int effectId, const std::string & sourceId)
{ {
const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(sourceId); const ESM::Spell * spell = SpellList::getSpell(sourceId);
TContainer::iterator spellIt = mSpells.find(spell); auto spellIt = mSpells.find(spell);
if (spellIt == mSpells.end()) if (spellIt == mSpells.end())
return; return;
int i = 0; int i = 0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = spellIt->first->mEffects.mList.begin(); effectIt != spellIt->first->mEffects.mList.end(); ++effectIt) for (auto& effectIt : spellIt->first->mEffects.mList)
{ {
if (effectIt->mEffectID == effectId) if (effectIt.mEffectID == effectId)
{ {
spellIt->second.mPurgedEffects.insert(i); spellIt->second.mPurgedEffects.insert(i);
mSpellsChanged = true; mSpellsChanged = true;
@ -338,11 +327,8 @@ namespace MWMechanics
bool Spells::canUsePower(const ESM::Spell* spell) const bool Spells::canUsePower(const ESM::Spell* spell) const
{ {
std::map<SpellKey, MWWorld::TimeStamp>::const_iterator it = mUsedPowers.find(spell); const auto it = mUsedPowers.find(spell);
if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp()) return it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp();
return true;
else
return false;
} }
void Spells::usePower(const ESM::Spell* spell) void Spells::usePower(const ESM::Spell* spell)
@ -352,6 +338,8 @@ namespace MWMechanics
void Spells::readState(const ESM::SpellState &state, CreatureStats* creatureStats) void Spells::readState(const ESM::SpellState &state, CreatureStats* creatureStats)
{ {
const auto& baseSpells = mSpellList->getSpells();
for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it)
{ {
// Discard spells that are no longer available due to changed content files // Discard spells that are no longer available due to changed content files
@ -365,6 +353,13 @@ namespace MWMechanics
mSelectedSpell = it->first; mSelectedSpell = it->first;
} }
} }
// Add spells from the base record
for(const std::string& id : baseSpells)
{
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(id);
if(spell)
addSpell(spell);
}
for (std::map<std::string, ESM::TimeStamp>::const_iterator it = state.mUsedPowers.begin(); it != state.mUsedPowers.end(); ++it) for (std::map<std::string, ESM::TimeStamp>::const_iterator it = state.mUsedPowers.begin(); it != state.mUsedPowers.end(); ++it)
{ {
@ -436,17 +431,49 @@ namespace MWMechanics
void Spells::writeState(ESM::SpellState &state) const void Spells::writeState(ESM::SpellState &state) const
{ {
for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) const auto& baseSpells = mSpellList->getSpells();
for (const auto& it : mSpells)
{
//Don't save spells stored in the base record
if(std::find(baseSpells.begin(), baseSpells.end(), it.first->mId) == baseSpells.end())
{ {
ESM::SpellState::SpellParams params; ESM::SpellState::SpellParams params;
params.mEffectRands = it->second.mEffectRands; params.mEffectRands = it.second.mEffectRands;
params.mPurgedEffects = it->second.mPurgedEffects; params.mPurgedEffects = it.second.mPurgedEffects;
state.mSpells.insert(std::make_pair(it->first->mId, params)); state.mSpells.insert(std::make_pair(it.first->mId, params));
}
} }
state.mSelectedSpell = mSelectedSpell; state.mSelectedSpell = mSelectedSpell;
for (std::map<SpellKey, MWWorld::TimeStamp>::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) for (const auto& it : mUsedPowers)
state.mUsedPowers[it->first->mId] = it->second.toEsm(); state.mUsedPowers[it.first->mId] = it.second.toEsm();
}
bool Spells::setSpells(const std::string& actorId)
{
bool result;
std::tie(mSpellList, result) = MWBase::Environment::get().getWorld()->getStore().getSpellList(actorId);
mSpellList->addListener(this);
addAllToInstance(mSpellList->getSpells());
return result;
}
void Spells::addAllToInstance(const std::vector<std::string>& spells)
{
for(const std::string& id : spells)
{
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(id);
if(spell)
addSpell(spell);
else
Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << id << "'";
}
}
Spells::~Spells()
{
if(mSpellList)
mSpellList->removeListener(this);
} }
} }

View file

@ -1,22 +1,19 @@
#ifndef GAME_MWMECHANICS_SPELLS_H #ifndef GAME_MWMECHANICS_SPELLS_H
#define GAME_MWMECHANICS_SPELLS_H #define GAME_MWMECHANICS_SPELLS_H
#include <memory>
#include <map> #include <map>
#include <string> #include <string>
#include <set> #include <set>
#include <vector>
#include <components/misc/stringops.hpp>
#include "../mwworld/ptr.hpp"
#include "../mwworld/timestamp.hpp" #include "../mwworld/timestamp.hpp"
#include "magiceffects.hpp" #include "magiceffects.hpp"
#include "spelllist.hpp"
namespace ESM namespace ESM
{ {
struct Spell;
struct SpellState; struct SpellState;
} }
@ -32,37 +29,36 @@ namespace MWMechanics
/// diseases. It also keeps track of used powers (which can only be used every 24h). /// diseases. It also keeps track of used powers (which can only be used every 24h).
class Spells class Spells
{ {
public: std::shared_ptr<SpellList> mSpellList;
std::map<const ESM::Spell*, SpellParams> mSpells;
typedef const ESM::Spell* SpellKey;
struct SpellParams
{
std::map<int, float> mEffectRands; // <effect index, normalised random magnitude>
std::set<int> mPurgedEffects; // indices of purged effects
};
typedef std::map<SpellKey, SpellParams> TContainer;
typedef TContainer::const_iterator TIterator;
private:
TContainer mSpells;
// Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) // Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different)
std::string mSelectedSpell; std::string mSelectedSpell;
std::map<SpellKey, MWWorld::TimeStamp> mUsedPowers; std::map<const ESM::Spell*, MWWorld::TimeStamp> mUsedPowers;
mutable bool mSpellsChanged; mutable bool mSpellsChanged;
mutable MagicEffects mEffects; mutable MagicEffects mEffects;
mutable std::map<SpellKey, MagicEffects> mSourcedEffects; mutable std::map<const ESM::Spell*, MagicEffects> mSourcedEffects;
void rebuildEffects() const; void rebuildEffects() const;
/// Get spell from ID, throws exception if not found bool hasDisease(const ESM::Spell::SpellType type) const;
const ESM::Spell* getSpell(const std::string& id) const;
using SpellFilter = bool (*)(const ESM::Spell*);
void purge(const SpellFilter& filter);
void addSpell(const ESM::Spell* spell);
void removeSpell(const ESM::Spell* spell);
void removeAllSpells();
friend class SpellList;
public: public:
using TIterator = std::map<const ESM::Spell*, SpellParams>::const_iterator;
Spells(); Spells();
~Spells();
static bool hasCorprusEffect(const ESM::Spell *spell); static bool hasCorprusEffect(const ESM::Spell *spell);
void purgeEffect(int effectId); void purgeEffect(int effectId);
@ -96,7 +92,7 @@ namespace MWMechanics
MagicEffects getMagicEffects() const; MagicEffects getMagicEffects() const;
///< Return sum of magic effects resulting from abilities, blights, deseases and curses. ///< Return sum of magic effects resulting from abilities, blights, deseases and curses.
void clear(); void clear(bool modifyBase = false);
///< Remove all spells of al types. ///< Remove all spells of al types.
void setSelectedSpell (const std::string& spellId); void setSelectedSpell (const std::string& spellId);
@ -118,6 +114,10 @@ namespace MWMechanics
void readState (const ESM::SpellState& state, CreatureStats* creatureStats); void readState (const ESM::SpellState& state, CreatureStats* creatureStats);
void writeState (ESM::SpellState& state) const; void writeState (ESM::SpellState& state) const;
bool setSpells(const std::string& id);
void addAllToInstance(const std::vector<std::string>& spells);
}; };
} }

View file

@ -13,6 +13,7 @@
#include "../mwworld/refdata.hpp" #include "../mwworld/refdata.hpp"
#include "../mwmechanics/drawstate.hpp" #include "../mwmechanics/drawstate.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/npcstats.hpp"
#include "npcanimation.hpp" #include "npcanimation.hpp"
@ -52,7 +53,10 @@ namespace MWRender
mCamera(camera), mCamera(camera),
mAnimation(nullptr), mAnimation(nullptr),
mFirstPersonView(true), 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), mNearest(30.f),
mFurthest(800.f), mFurthest(800.f),
mIsNearest(false), mIsNearest(false),
@ -62,25 +66,19 @@ namespace MWRender
mVanityToggleQueuedValue(false), mVanityToggleQueuedValue(false),
mViewModeToggleQueued(false), mViewModeToggleQueued(false),
mCameraDistance(0.f), mCameraDistance(0.f),
mMaxNextCameraDistance(800.f),
mFocalPointCurrentOffset(osg::Vec2d()), mFocalPointCurrentOffset(osg::Vec2d()),
mFocalPointTargetOffset(osg::Vec2d()), mFocalPointTargetOffset(osg::Vec2d()),
mFocalPointTransitionSpeedCoef(1.f), mFocalPointTransitionSpeedCoef(1.f),
mSkipFocalPointTransition(true),
mPreviousTransitionInfluence(0.f), mPreviousTransitionInfluence(0.f),
mSmoothedSpeed(0.f), mSmoothedSpeed(0.f),
mZoomOutWhenMoveCoef(Settings::Manager::getFloat("zoom out when move coef", "Camera")), mZoomOutWhenMoveCoef(Settings::Manager::getFloat("zoom out when move coef", "Camera")),
mDynamicCameraDistanceEnabled(false), mDynamicCameraDistanceEnabled(false),
mShowCrosshairInThirdPersonMode(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; mCameraDistance = mBaseCameraDistance;
mUpdateCallback = new UpdateRenderCameraCallback(this); mUpdateCallback = new UpdateRenderCameraCallback(this);
@ -92,17 +90,11 @@ namespace MWRender
mCamera->removeUpdateCallback(mUpdateCallback); mCamera->removeUpdateCallback(mUpdateCallback);
} }
MWWorld::Ptr Camera::getTrackingPtr() const
{
return mTrackingPtr;
}
osg::Vec3d Camera::getFocalPoint() const osg::Vec3d Camera::getFocalPoint() const
{ {
const osg::Node* trackNode = mTrackingNode; if (!mTrackingNode)
if (!trackNode)
return osg::Vec3d(); return osg::Vec3d();
osg::NodePathList nodepaths = trackNode->getParentalNodePaths(); osg::NodePathList nodepaths = mTrackingNode->getParentalNodePaths();
if (nodepaths.empty()) if (nodepaths.empty())
return osg::Vec3d(); return osg::Vec3d();
osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]);
@ -124,12 +116,9 @@ namespace MWRender
osg::Vec3d Camera::getFocalPointOffset() const osg::Vec3d Camera::getFocalPointOffset() const
{ {
osg::Vec3d offset(0, 0, 10.f); osg::Vec3d offset(0, 0, 10.f);
if (!mPreviewMode && !mVanity.enabled)
{
offset.x() += mFocalPointCurrentOffset.x() * cos(getYaw()); offset.x() += mFocalPointCurrentOffset.x() * cos(getYaw());
offset.y() += mFocalPointCurrentOffset.x() * sin(getYaw()); offset.y() += mFocalPointCurrentOffset.x() * sin(getYaw());
offset.z() += mFocalPointCurrentOffset.y(); offset.z() += mFocalPointCurrentOffset.y();
}
return offset; return offset;
} }
@ -147,9 +136,6 @@ namespace MWRender
void Camera::updateCamera(osg::Camera *cam) void Camera::updateCamera(osg::Camera *cam)
{ {
if (mTrackingPtr.isEmpty())
return;
osg::Vec3d focal, position; osg::Vec3d focal, position;
getPosition(focal, position); getPosition(focal, position);
@ -179,11 +165,6 @@ namespace MWRender
setPitch(pitch); setPitch(pitch);
} }
void Camera::attachTo(const MWWorld::Ptr &ptr)
{
mTrackingPtr = ptr;
}
void Camera::update(float duration, bool paused) void Camera::update(float duration, bool paused)
{ {
if (mAnimation->upperBodyReady()) if (mAnimation->upperBodyReady())
@ -196,7 +177,6 @@ namespace MWRender
} }
if (mViewModeToggleQueued) if (mViewModeToggleQueued)
{ {
togglePreviewMode(false); togglePreviewMode(false);
toggleViewMode(); toggleViewMode();
mViewModeToggleQueued = false; mViewModeToggleQueued = false;
@ -208,19 +188,38 @@ namespace MWRender
// only show the crosshair in game mode // only show the crosshair in game mode
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
wm->showCrosshair(!wm->isGuiMode() && !mVanity.enabled && !mPreviewMode wm->showCrosshair(!wm->isGuiMode() && mMode != Mode::Preview && mMode != Mode::Vanity
&& (mFirstPersonView || mShowCrosshairInThirdPersonMode)); && (mFirstPersonView || mShowCrosshairInThirdPersonMode));
if(mVanity.enabled) if(mMode == Mode::Vanity)
{
rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true); rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true);
}
updateFocalPointOffset(duration); updateFocalPointOffset(duration);
float speed = mTrackingPtr.getClass().getSpeed(mTrackingPtr); float speed = mTrackingPtr.getClass().getSpeed(mTrackingPtr);
speed /= (1.f + speed / 500.f);
float maxDelta = 300.f * duration; float maxDelta = 300.f * duration;
mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta); mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta);
mMaxNextCameraDistance = mCameraDistance + duration * (100.f + mBaseCameraDistance);
updateStandingPreviewMode();
}
void Camera::updateStandingPreviewMode()
{
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::setFocalPointTargetOffset(osg::Vec2d v) void Camera::setFocalPointTargetOffset(osg::Vec2d v)
@ -235,6 +234,14 @@ namespace MWRender
if (duration <= 0) if (duration <= 0)
return; return;
if (mSkipFocalPointTransition)
{
mSkipFocalPointTransition = false;
mPreviousExtraOffset = osg::Vec2d();
mPreviousTransitionInfluence = 0.f;
mFocalPointCurrentOffset = mFocalPointTargetOffset;
}
osg::Vec2d oldOffset = mFocalPointCurrentOffset; osg::Vec2d oldOffset = mFocalPointCurrentOffset;
if (mPreviousTransitionInfluence > 0) if (mPreviousTransitionInfluence > 0)
@ -279,14 +286,19 @@ namespace MWRender
mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).setSideMovementAngle(0); mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).setSideMovementAngle(0);
mFirstPersonView = !mFirstPersonView; mFirstPersonView = !mFirstPersonView;
updateStandingPreviewMode();
instantTransition();
processViewChange(); processViewChange();
} }
void Camera::allowVanityMode(bool allow) void Camera::allowVanityMode(bool allow)
{ {
if (!allow && mVanity.enabled) if (!allow && mMode == Mode::Vanity)
{
disableDeferredPreviewRotation();
toggleVanityMode(false); toggleVanityMode(false);
mVanity.allowed = allow; }
mVanityAllowed = allow;
} }
bool Camera::toggleVanityMode(bool enable) bool Camera::toggleVanityMode(bool enable)
@ -300,26 +312,18 @@ namespace MWRender
return false; return false;
} }
if(!mVanity.allowed && enable) if (!mVanityAllowed && enable)
return false; return false;
if(mVanity.enabled == enable) if ((mMode == Mode::Vanity) == enable)
return true; return true;
mVanity.enabled = enable; mMode = enable ? Mode::Vanity : Mode::Normal;
if (!mDeferredRotationAllowed)
disableDeferredPreviewRotation();
if (!enable)
calculateDeferredRotation();
processViewChange(); processViewChange();
float offset = mPreviewCam.offset;
if (mVanity.enabled) {
setPitch(osg::DegreesToRadians(-30.f));
mMainCam.offset = mCameraDistance;
} else {
offset = mMainCam.offset;
}
mCameraDistance = offset;
return true; return true;
} }
@ -328,22 +332,21 @@ namespace MWRender
if (mFirstPersonView && !mAnimation->upperBodyReady()) if (mFirstPersonView && !mAnimation->upperBodyReady())
return; return;
if(mPreviewMode == enable) if((mMode == Mode::Preview) == enable)
return; return;
mPreviewMode = enable; mMode = enable ? Mode::Preview : Mode::Normal;
processViewChange(); if (mMode == Mode::Normal)
updateStandingPreviewMode();
float offset = mCameraDistance; else if (mFirstPersonView)
if (mPreviewMode) { instantTransition();
mMainCam.offset = offset; if (mMode == Mode::Normal)
offset = mPreviewCam.offset; {
} else { if (!mDeferredRotationAllowed)
mPreviewCam.offset = offset; disableDeferredPreviewRotation();
offset = mMainCam.offset; calculateDeferredRotation();
} }
processViewChange();
mCameraDistance = offset;
} }
void Camera::setSneakOffset(float offset) void Camera::setSneakOffset(float offset)
@ -351,13 +354,6 @@ namespace MWRender
mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset)); 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) void Camera::setYaw(float angle)
{ {
if (angle > osg::PI) { if (angle > osg::PI) {
@ -365,38 +361,14 @@ namespace MWRender
} else if (angle < -osg::PI) { } else if (angle < -osg::PI) {
angle += osg::PI*2; angle += osg::PI*2;
} }
if (mVanity.enabled || mPreviewMode) { mYaw = angle;
mPreviewCam.yaw = angle;
} else {
mMainCam.yaw = angle;
}
}
float Camera::getPitch() const
{
if (mVanity.enabled || mPreviewMode) {
return mPreviewCam.pitch;
}
return mMainCam.pitch;
} }
void Camera::setPitch(float angle) void Camera::setPitch(float angle)
{ {
const float epsilon = 0.000001f; const float epsilon = 0.000001f;
float limit = osg::PI_2 - epsilon; float limit = osg::PI_2 - epsilon;
if(mPreviewMode) mPitch = osg::clampBetween(angle, -limit, limit);
limit /= 2;
if(angle > limit)
angle = limit;
else if(angle < -limit)
angle = -limit;
if (mVanity.enabled || mPreviewMode) {
mPreviewCam.pitch = angle;
} else {
mMainCam.pitch = angle;
}
} }
float Camera::getCameraDistance() const float Camera::getCameraDistance() const
@ -408,50 +380,25 @@ namespace MWRender
void Camera::updateBaseCameraDistance(float dist, bool adjust) void Camera::updateBaseCameraDistance(float dist, bool adjust)
{ {
if(mFirstPersonView && !mPreviewMode && !mVanity.enabled) if (isFirstPerson())
return; return;
mIsNearest = false;
if (adjust) if (adjust)
{
if (mVanity.enabled || mPreviewMode)
dist += mCameraDistance;
else
dist += std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance); dist += std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance);
}
mIsNearest = dist <= mNearest;
if (dist >= mFurthest) mBaseCameraDistance = osg::clampBetween(dist, mNearest, mFurthest);
dist = mFurthest; Settings::Manager::setFloat("third person camera distance", "Camera", mBaseCameraDistance);
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);
}
setCameraDistance(); setCameraDistance();
} }
void Camera::setCameraDistance(float dist, bool adjust) void Camera::setCameraDistance(float dist, bool adjust)
{ {
if(mFirstPersonView && !mPreviewMode && !mVanity.enabled) if (isFirstPerson())
return; return;
if (adjust)
if (adjust) dist += mCameraDistance; dist += mCameraDistance;
mCameraDistance = osg::clampBetween(dist, 10.f, mFurthest);
if (dist >= mFurthest)
dist = mFurthest;
else if (dist < 10.f)
dist = 10.f;
mCameraDistance = dist;
} }
float Camera::getCameraDistanceCorrection() const float Camera::getCameraDistanceCorrection() const
@ -469,17 +416,17 @@ namespace MWRender
void Camera::setCameraDistance() void Camera::setCameraDistance()
{ {
if (mVanity.enabled || mPreviewMode)
mCameraDistance = mPreviewCam.offset;
else if (!mFirstPersonView)
mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection();
mFocalPointAdjustment = osg::Vec3d(); mFocalPointAdjustment = osg::Vec3d();
if (isFirstPerson())
return;
mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection();
if (mDynamicCameraDistanceEnabled)
mCameraDistance = std::min(mCameraDistance, mMaxNextCameraDistance);
} }
void Camera::setAnimation(NpcAnimation *anim) void Camera::setAnimation(NpcAnimation *anim)
{ {
mAnimation = anim; mAnimation = anim;
processViewChange(); processViewChange();
} }
@ -506,13 +453,74 @@ namespace MWRender
rotateCamera(getPitch(), getYaw(), false); 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;
} }
bool Camera::isNearest() const 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)
{ {
return mIsNearest; 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;
} }
} }
void Camera::rotateCameraToTrackingPtr()
{
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;
}
}

View file

@ -23,11 +23,10 @@ namespace MWRender
/// \brief Camera control /// \brief Camera control
class Camera class Camera
{ {
private: public:
struct CamData { enum class Mode { Normal, Vanity, Preview, StandingPreview };
float pitch, yaw, offset;
};
private:
MWWorld::Ptr mTrackingPtr; MWWorld::Ptr mTrackingPtr;
osg::ref_ptr<const osg::Node> mTrackingNode; osg::ref_ptr<const osg::Node> mTrackingNode;
float mHeightScale; float mHeightScale;
@ -37,28 +36,30 @@ namespace MWRender
NpcAnimation *mAnimation; NpcAnimation *mAnimation;
bool mFirstPersonView; bool mFirstPersonView;
bool mPreviewMode; Mode mMode;
bool mVanityAllowed;
bool mStandingPreviewAllowed;
bool mDeferredRotationAllowed;
float mNearest; float mNearest;
float mFurthest; float mFurthest;
bool mIsNearest; bool mIsNearest;
struct {
bool enabled, allowed;
} mVanity;
float mHeight, mBaseCameraDistance; float mHeight, mBaseCameraDistance;
CamData mMainCam, mPreviewCam; float mPitch, mYaw;
bool mVanityToggleQueued; bool mVanityToggleQueued;
bool mVanityToggleQueuedValue; bool mVanityToggleQueuedValue;
bool mViewModeToggleQueued; bool mViewModeToggleQueued;
float mCameraDistance; float mCameraDistance;
float mMaxNextCameraDistance;
osg::Vec3d mFocalPointAdjustment; osg::Vec3d mFocalPointAdjustment;
osg::Vec2d mFocalPointCurrentOffset; osg::Vec2d mFocalPointCurrentOffset;
osg::Vec2d mFocalPointTargetOffset; osg::Vec2d mFocalPointTargetOffset;
float mFocalPointTransitionSpeedCoef; float mFocalPointTransitionSpeedCoef;
bool mSkipFocalPointTransition;
// This fields are used to make focal point transition smooth if previous transition was not finished. // This fields are used to make focal point transition smooth if previous transition was not finished.
float mPreviousTransitionInfluence; float mPreviousTransitionInfluence;
@ -76,14 +77,23 @@ namespace MWRender
osg::ref_ptr<osg::NodeCallback> mUpdateCallback; 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: public:
Camera(osg::Camera* camera); Camera(osg::Camera* 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 setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; } void setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; }
void setFocalPointTargetOffset(osg::Vec2d v); void setFocalPointTargetOffset(osg::Vec2d v);
void instantTransition();
void enableDynamicCameraDistance(bool v) { mDynamicCameraDistanceEnabled = v; } void enableDynamicCameraDistance(bool v) { mDynamicCameraDistanceEnabled = v; }
void enableCrosshairInThirdPersonMode(bool v) { mShowCrosshairInThirdPersonMode = v; } void enableCrosshairInThirdPersonMode(bool v) { mShowCrosshairInThirdPersonMode = v; }
@ -96,16 +106,14 @@ namespace MWRender
/// Set where the camera is looking at. Uses Morrowind (euler) angles /// Set where the camera is looking at. Uses Morrowind (euler) angles
/// \param rot Rotation angles in radians /// \param rot Rotation angles in radians
void rotateCamera(float pitch, float yaw, bool adjust); void rotateCamera(float pitch, float yaw, bool adjust);
void rotateCameraToTrackingPtr();
float getYaw() const; float getYaw() const { return mYaw; }
void setYaw(float angle); void setYaw(float angle);
float getPitch() const; float getPitch() const { return mPitch; }
void setPitch(float angle); 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. /// @param Force view mode switch, even if currently not allowed by the animation.
void toggleViewMode(bool force=false); void toggleViewMode(bool force=false);
@ -115,11 +123,13 @@ namespace MWRender
/// @note this may be ignored if an important animation is currently playing /// @note this may be ignored if an important animation is currently playing
void togglePreviewMode(bool enable); void togglePreviewMode(bool enable);
void applyDeferredPreviewRotationToPlayer(float dt);
void disableDeferredPreviewRotation() { mDeferredRotationDisabled = true; }
/// \brief Lowers the camera for sneak. /// \brief Lowers the camera for sneak.
void setSneakOffset(float offset); void setSneakOffset(float offset);
bool isFirstPerson() const bool isFirstPerson() const { return mFirstPersonView && mMode == Mode::Normal; }
{ return !(mVanity.enabled || mPreviewMode || !mFirstPersonView); }
void processViewChange(); void processViewChange();
@ -148,9 +158,10 @@ namespace MWRender
/// Stores focal and camera world positions in passed arguments /// Stores focal and camera world positions in passed arguments
void getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const; 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; }
}; };
} }

View file

@ -372,6 +372,7 @@ namespace MWRender
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip));
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance));
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false));
mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near");
mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far");
@ -653,7 +654,7 @@ namespace MWRender
if(ptr == mCamera->getTrackingPtr() && if(ptr == mCamera->getTrackingPtr() &&
!mCamera->isVanityOrPreviewModeEnabled()) !mCamera->isVanityOrPreviewModeEnabled())
{ {
mCamera->rotateCamera(-ptr.getRefData().getPosition().rot[0], -ptr.getRefData().getPosition().rot[2], false); mCamera->rotateCameraToTrackingPtr();
} }
ptr.getRefData().getBaseNode()->setAttitude(rot); ptr.getRefData().getBaseNode()->setAttitude(rot);

View file

@ -21,24 +21,25 @@ namespace MWRender
mAutoSwitchShoulder(Settings::Manager::getBool("auto switch shoulder", "Camera")), mAutoSwitchShoulder(Settings::Manager::getBool("auto switch shoulder", "Camera")),
mOverShoulderHorizontalOffset(30.f), mOverShoulderVerticalOffset(-10.f) mOverShoulderHorizontalOffset(30.f), mOverShoulderVerticalOffset(-10.f)
{ {
std::stringstream offset(Settings::Manager::getString("view over shoulder offset", "Camera")); osg::Vec2f offset = Settings::Manager::getVector2("view over shoulder offset", "Camera");
offset >> mOverShoulderHorizontalOffset >> mOverShoulderVerticalOffset; mOverShoulderHorizontalOffset = std::abs(offset.x());
mDefaultShoulderIsRight = mOverShoulderHorizontalOffset >= 0; mOverShoulderVerticalOffset = offset.y();
mOverShoulderHorizontalOffset = std::abs(mOverShoulderHorizontalOffset); mDefaultShoulderIsRight = offset.x() >= 0;
mCamera->enableDynamicCameraDistance(true); mCamera->enableDynamicCameraDistance(true);
mCamera->enableCrosshairInThirdPersonMode(true); mCamera->enableCrosshairInThirdPersonMode(true);
mCamera->setFocalPointTargetOffset({mOverShoulderHorizontalOffset, mOverShoulderVerticalOffset}); mCamera->setFocalPointTargetOffset(offset);
} }
void ViewOverShoulderController::update() void ViewOverShoulderController::update()
{ {
if (mCamera->isVanityOrPreviewModeEnabled() || mCamera->isFirstPerson()) if (mCamera->isFirstPerson())
return; return;
Mode oldMode = mMode; Mode oldMode = mMode;
auto ptr = mCamera->getTrackingPtr(); auto ptr = mCamera->getTrackingPtr();
if (ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getDrawState() != MWMechanics::DrawState_Nothing) bool combat = ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getDrawState() != MWMechanics::DrawState_Nothing;
if (combat && !mCamera->isVanityOrPreviewModeEnabled())
mMode = Mode::Combat; mMode = Mode::Combat;
else if (MWBase::Environment::get().getWorld()->isSwimming(ptr)) else if (MWBase::Environment::get().getWorld()->isSwimming(ptr))
mMode = Mode::Swimming; mMode = Mode::Swimming;
@ -46,12 +47,18 @@ namespace MWRender
mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder; mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder;
if (mAutoSwitchShoulder && (mMode == Mode::LeftShoulder || mMode == Mode::RightShoulder)) if (mAutoSwitchShoulder && (mMode == Mode::LeftShoulder || mMode == Mode::RightShoulder))
trySwitchShoulder(); trySwitchShoulder();
if (oldMode == mMode) return;
if (oldMode == Mode::Combat || mMode == Mode::Combat) 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); mCamera->setFocalPointTransitionSpeed(5.f);
else else
mCamera->setFocalPointTransitionSpeed(1.f); mCamera->setFocalPointTransitionSpeed(1.f); // Default transition speed.
switch (mMode) switch (mMode)
{ {
@ -70,6 +77,9 @@ namespace MWRender
void ViewOverShoulderController::trySwitchShoulder() 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 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 const float limitToSwitchBack = 300; // switch back to default shoulder if there is no walls at this distance
@ -79,17 +89,21 @@ namespace MWRender
MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::World* world = MWBase::Environment::get().getWorld();
osg::Vec3d sideOffset = orient * osg::Vec3d(world->getHalfExtents(mCamera->getTrackingPtr()).x() - 1, 0, 0); osg::Vec3d sideOffset = orient * osg::Vec3d(world->getHalfExtents(mCamera->getTrackingPtr()).x() - 1, 0, 0);
float rayRight = world->getDistToNearestRayHit( float rayRight = world->getDistToNearestRayHit(
playerPos + sideOffset, orient * osg::Vec3d(1, 1, 0), limitToSwitchBack + 1); playerPos + sideOffset, orient * osg::Vec3d(1, 0, 0), limitToSwitchBack + 1);
float rayLeft = world->getDistToNearestRayHit( float rayLeft = world->getDistToNearestRayHit(
playerPos - sideOffset, orient * osg::Vec3d(-1, 1, 0), limitToSwitchBack + 1); playerPos - sideOffset, orient * osg::Vec3d(-1, 0, 0), limitToSwitchBack + 1);
float rayForward = world->getDistToNearestRayHit( float rayRightForward = world->getDistToNearestRayHit(
playerPos, orient * osg::Vec3d(0, 1, 0), limitToSwitchBack + 1); playerPos + sideOffset, orient * osg::Vec3d(1, 3, 0), limitToSwitchBack + 1);
float rayLeftForward = world->getDistToNearestRayHit(
playerPos - sideOffset, orient * osg::Vec3d(-1, 3, 0), limitToSwitchBack + 1);
float distRight = std::min(rayRight, rayRightForward);
float distLeft = std::min(rayLeft, rayLeftForward);
if (rayLeft < limitToSwitch && rayRight > limitToSwitchBack) if (distLeft < limitToSwitch && distRight > limitToSwitchBack)
mMode = Mode::RightShoulder; mMode = Mode::RightShoulder;
else if (rayRight < limitToSwitch && rayLeft > limitToSwitchBack) else if (distRight < limitToSwitch && distLeft > limitToSwitchBack)
mMode = Mode::LeftShoulder; mMode = Mode::LeftShoulder;
else if (rayLeft > limitToSwitchBack && rayRight > limitToSwitchBack && rayForward > limitToSwitchBack) else if (distRight > limitToSwitchBack && distLeft > limitToSwitchBack)
mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder; mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder;
} }

View file

@ -60,6 +60,13 @@ namespace MWScript
|| ::Misc::StringUtils::ciEqual(item, "gold_100")) || ::Misc::StringUtils::ciEqual(item, "gold_100"))
item = "gold_001"; item = "gold_001";
// Explicit calls to non-unique actors affect the base record
if(!R::implicit && ptr.getClass().isActor() && MWBase::Environment::get().getWorld()->getStore().getRefCount(ptr.getCellRef().getRefId()) > 1)
{
ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, count);
return;
}
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr);
// Create a Ptr for the first added item to recover the item name later // Create a Ptr for the first added item to recover the item name later
MWWorld::Ptr itemPtr = *store.add (item, 1, ptr); MWWorld::Ptr itemPtr = *store.add (item, 1, ptr);
@ -147,6 +154,13 @@ namespace MWScript
|| ::Misc::StringUtils::ciEqual(item, "gold_100")) || ::Misc::StringUtils::ciEqual(item, "gold_100"))
item = "gold_001"; item = "gold_001";
// Explicit calls to non-unique actors affect the base record
if(!R::implicit && ptr.getClass().isActor() && MWBase::Environment::get().getWorld()->getStore().getRefCount(ptr.getCellRef().getRefId()) > 1)
{
ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, -count);
return;
}
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr);
std::string itemName; std::string itemName;

View file

@ -299,6 +299,15 @@ namespace MWScript
return iter->second->mLocals; return iter->second->mLocals;
} }
const Locals* GlobalScripts::getLocalsIfPresent (const std::string& name) const
{
std::string name2 = ::Misc::StringUtils::lowerCase (name);
auto iter = mScripts.find (name2);
if (iter==mScripts.end())
return nullptr;
return &iter->second->mLocals;
}
void GlobalScripts::updatePtrs(const MWWorld::Ptr& base, const MWWorld::Ptr& updated) void GlobalScripts::updatePtrs(const MWWorld::Ptr& base, const MWWorld::Ptr& updated)
{ {
MatchPtrVisitor visitor(base); MatchPtrVisitor visitor(base);

View file

@ -82,6 +82,8 @@ namespace MWScript
///< If the script \a name has not been added as a global script yet, it is added ///< If the script \a name has not been added as a global script yet, it is added
/// automatically, but is not set to running state. /// automatically, but is not set to running state.
const Locals* getLocalsIfPresent (const std::string& name) const;
void updatePtrs(const MWWorld::Ptr& base, const MWWorld::Ptr& updated); void updatePtrs(const MWWorld::Ptr& base, const MWWorld::Ptr& updated);
///< Update the Ptrs stored in mTarget. Should be called after the reference has been moved to a new cell. ///< Update the Ptrs stored in mTarget. Should be called after the reference has been moved to a new cell.
}; };

View file

@ -225,15 +225,13 @@ namespace MWScript
std::vector<std::string> InterpreterContext::getGlobals() const std::vector<std::string> InterpreterContext::getGlobals() const
{ {
std::vector<std::string> ids;
const MWWorld::Store<ESM::Global>& globals = const MWWorld::Store<ESM::Global>& globals =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Global>(); MWBase::Environment::get().getWorld()->getStore().get<ESM::Global>();
for (MWWorld::Store<ESM::Global>::iterator iter = globals.begin(); iter!=globals.end(); std::vector<std::string> ids;
++iter) for (auto& globalVariable : globals)
{ {
ids.push_back (iter->mId); ids.emplace_back(globalVariable.mId);
} }
return ids; return ids;
@ -245,22 +243,22 @@ namespace MWScript
return world->getGlobalVariableType(name); return world->getGlobalVariableType(name);
} }
std::string InterpreterContext::getActionBinding(const std::string& action) const std::string InterpreterContext::getActionBinding(const std::string& targetAction) const
{ {
MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); MWBase::InputManager* input = MWBase::Environment::get().getInputManager();
std::vector<int> actions = input->getActionKeySorting (); std::vector<int> actions = input->getActionKeySorting ();
for (std::vector<int>::const_iterator it = actions.begin(); it != actions.end(); ++it) for (const int action : actions)
{ {
std::string desc = input->getActionDescription (*it); std::string desc = input->getActionDescription (action);
if(desc == "") if(desc == "")
continue; continue;
if(desc == action) if(desc == targetAction)
{ {
if(input->joystickLastUsed()) if(input->joystickLastUsed())
return input->getActionControllerBindingName(*it); return input->getActionControllerBindingName(action);
else else
return input->getActionKeyBindingName (*it); return input->getActionKeyBindingName(action);
} }
} }

View file

@ -1,4 +1,5 @@
#include "locals.hpp" #include "locals.hpp"
#include "globalscripts.hpp"
#include <components/esm/loadscpt.hpp> #include <components/esm/loadscpt.hpp>
#include <components/esm/variant.hpp> #include <components/esm/variant.hpp>
@ -33,6 +34,15 @@ namespace MWScript
if (mInitialised) if (mInitialised)
return false; return false;
const Locals* global = MWBase::Environment::get().getScriptManager()->getGlobalScripts().getLocalsIfPresent(script.mId);
if(global)
{
mShorts = global->mShorts;
mLongs = global->mLongs;
mFloats = global->mFloats;
}
else
{
const Compiler::Locals& locals = const Compiler::Locals& locals =
MWBase::Environment::get().getScriptManager()->getLocals (script.mId); MWBase::Environment::get().getScriptManager()->getLocals (script.mId);
@ -42,6 +52,7 @@ namespace MWScript
mLongs.resize (locals.get ('l').size(), 0); mLongs.resize (locals.get ('l').size(), 0);
mFloats.clear(); mFloats.clear();
mFloats.resize (locals.get ('f').size(), 0); mFloats.resize (locals.get ('f').size(), 0);
}
mInitialised = true; mInitialised = true;
return true; return true;

View file

@ -45,9 +45,9 @@ namespace
void addToLevList(ESM::LevelledListBase* list, const std::string& itemId, int level) void addToLevList(ESM::LevelledListBase* list, const std::string& itemId, int level)
{ {
for (std::vector<ESM::LevelledListBase::LevelItem>::iterator it = list->mList.begin(); it != list->mList.end(); ++it) for (auto& levelItem : list->mList)
{ {
if (it->mLevel == level && itemId == it->mId) if (levelItem.mLevel == level && itemId == levelItem.mId)
return; return;
} }
@ -563,9 +563,9 @@ namespace MWScript
effects += store.getMagicEffects(); effects += store.getMagicEffects();
} }
for (MWMechanics::MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) for (const auto& effect : effects)
{ {
if (it->first.mId == key && it->second.getModifier() > 0) if (effect.first.mId == key && effect.second.getModifier() > 0)
{ {
runtime.push(1); runtime.push(1);
return; return;

View file

@ -151,16 +151,17 @@ namespace MWScript
const MWWorld::Store<ESM::Script>& scripts = mStore.get<ESM::Script>(); const MWWorld::Store<ESM::Script>& scripts = mStore.get<ESM::Script>();
for (MWWorld::Store<ESM::Script>::iterator iter = scripts.begin(); for (auto& script : mStore.get<ESM::Script>())
iter != scripts.end(); ++iter) {
if (!std::binary_search (mScriptBlacklist.begin(), mScriptBlacklist.end(), if (!std::binary_search (mScriptBlacklist.begin(), mScriptBlacklist.end(),
Misc::StringUtils::lowerCase (iter->mId))) Misc::StringUtils::lowerCase(script.mId)))
{ {
++count; ++count;
if (compile (iter->mId)) if (compile(script.mId))
++success; ++success;
} }
}
return std::make_pair (count, success); return std::make_pair (count, success);
} }
@ -195,7 +196,7 @@ namespace MWScript
scanner.scan (parser); scanner.scan (parser);
std::map<std::string, Compiler::Locals>::iterator iter = std::map<std::string, Compiler::Locals>::iterator iter =
mOtherLocals.insert (std::make_pair (name2, locals)).first; mOtherLocals.emplace(name2, locals).first;
return iter->second; return iter->second;
} }

View file

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

View file

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

View file

@ -522,6 +522,11 @@ namespace MWWorld
throw std::runtime_error ("class does not have creature stats"); throw std::runtime_error ("class does not have creature stats");
} }
void Class::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const
{
throw std::runtime_error ("class does not have an inventory store");
}
float Class::getWalkSpeed(const Ptr& /*ptr*/) const float Class::getWalkSpeed(const Ptr& /*ptr*/) const
{ {
return 0; return 0;

View file

@ -363,6 +363,8 @@ namespace MWWorld
virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const; virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const;
virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const;
virtual float getWalkSpeed(const Ptr& ptr) const; virtual float getWalkSpeed(const Ptr& ptr) const;
virtual float getRunSpeed(const Ptr& ptr) const; virtual float getRunSpeed(const Ptr& ptr) const;

View file

@ -9,6 +9,47 @@
#include <components/esm/esmreader.hpp> #include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp> #include <components/esm/esmwriter.hpp>
#include "../mwmechanics/spelllist.hpp"
namespace
{
void readRefs(const ESM::Cell& cell, std::map<ESM::RefNum, std::string>& refs, std::vector<ESM::ESMReader>& readers)
{
for (size_t i = 0; i < cell.mContextList.size(); i++)
{
size_t index = cell.mContextList[i].index;
if (readers.size() <= index)
readers.resize(index + 1);
cell.restore(readers[index], i);
ESM::CellRef ref;
ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile;
bool deleted = false;
while(cell.getNextRef(readers[index], ref, deleted))
{
if(deleted)
refs.erase(ref.mRefNum);
else if (std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum) == cell.mMovedRefs.end())
{
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
refs[ref.mRefNum] = ref.mRefID;
}
}
}
for(const auto& it : cell.mLeasedRefs)
{
bool deleted = it.second;
if(deleted)
refs.erase(it.first.mRefNum);
else
{
ESM::CellRef ref = it.first;
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
refs[ref.mRefNum] = ref.mRefID;
}
}
}
}
namespace MWWorld namespace MWWorld
{ {
@ -146,7 +187,33 @@ void ESMStore::setUp(bool validateRecords)
mDialogs.setUp(); mDialogs.setUp();
if (validateRecords) if (validateRecords)
{
validate(); validate();
countRecords();
}
}
void ESMStore::countRecords()
{
if(!mRefCount.empty())
return;
std::map<ESM::RefNum, std::string> refs;
std::vector<ESM::ESMReader> readers;
for(auto it = mCells.intBegin(); it != mCells.intEnd(); it++)
readRefs(*it, refs, readers);
for(auto it = mCells.extBegin(); it != mCells.extEnd(); it++)
readRefs(*it, refs, readers);
for(const auto& pair : refs)
mRefCount[pair.second]++;
}
int ESMStore::getRefCount(const std::string& id) const
{
const std::string lowerId = Misc::StringUtils::lowerCase(id);
auto it = mRefCount.find(lowerId);
if(it == mRefCount.end())
return 0;
return it->second;
} }
void ESMStore::validate() void ESMStore::validate()
@ -344,4 +411,23 @@ void ESMStore::validate()
throw std::runtime_error ("Invalid player record (race or class unavailable"); throw std::runtime_error ("Invalid player record (race or class unavailable");
} }
std::pair<std::shared_ptr<MWMechanics::SpellList>, bool> ESMStore::getSpellList(const std::string& originalId) const
{
const std::string id = Misc::StringUtils::lowerCase(originalId);
auto result = mSpellListCache.find(id);
std::shared_ptr<MWMechanics::SpellList> ptr;
if (result != mSpellListCache.end())
ptr = result->second.lock();
if (!ptr)
{
int type = find(id);
ptr = std::make_shared<MWMechanics::SpellList>(id, type);
if (result != mSpellListCache.end())
result->second = ptr;
else
mSpellListCache.insert({id, ptr});
return {ptr, false};
}
return {ptr, true};
}
} // end namespace } // end namespace

View file

@ -1,6 +1,7 @@
#ifndef OPENMW_MWWORLD_ESMSTORE_H #ifndef OPENMW_MWWORLD_ESMSTORE_H
#define OPENMW_MWWORLD_ESMSTORE_H #define OPENMW_MWWORLD_ESMSTORE_H
#include <memory>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
@ -12,6 +13,11 @@ namespace Loading
class Listener; class Listener;
} }
namespace MWMechanics
{
class SpellList;
}
namespace MWWorld namespace MWWorld
{ {
class ESMStore class ESMStore
@ -70,15 +76,20 @@ namespace MWWorld
std::map<std::string, int> mIds; std::map<std::string, int> mIds;
std::map<std::string, int> mStaticIds; std::map<std::string, int> mStaticIds;
std::map<std::string, int> mRefCount;
std::map<int, StoreBase *> mStores; std::map<int, StoreBase *> mStores;
ESM::NPC mPlayerTemplate; ESM::NPC mPlayerTemplate;
unsigned int mDynamicCount; unsigned int mDynamicCount;
mutable std::map<std::string, std::weak_ptr<MWMechanics::SpellList> > mSpellListCache;
/// Validate entries in store after setup /// Validate entries in store after setup
void validate(); void validate();
void countRecords();
public: public:
/// \todo replace with SharedIterator<StoreBase> /// \todo replace with SharedIterator<StoreBase>
typedef std::map<int, StoreBase *>::const_iterator iterator; typedef std::map<int, StoreBase *>::const_iterator iterator;
@ -252,6 +263,13 @@ namespace MWWorld
// To be called when we are done with dynamic record loading // To be called when we are done with dynamic record loading
void checkPlayer(); void checkPlayer();
/// @return The number of instances defined in the base files. Excludes changes from the save file.
int getRefCount(const std::string& id) const;
/// Actors with the same ID share spells, abilities, etc.
/// @return The shared spell list to use for this actor and whether or not it has already been initialized.
std::pair<std::shared_ptr<MWMechanics::SpellList>, bool> getSpellList(const std::string& id) const;
}; };
template <> template <>

View file

@ -945,6 +945,7 @@ namespace MWWorld
removeContainerScripts(getPlayerPtr()); removeContainerScripts(getPlayerPtr());
mWorldScene->changeToInteriorCell(cellName, position, adjustPlayerPos, changeEvent); mWorldScene->changeToInteriorCell(cellName, position, adjustPlayerPos, changeEvent);
addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
mRendering->getCamera()->instantTransition();
} }
void World::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) void World::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
@ -960,6 +961,7 @@ namespace MWWorld
removeContainerScripts(getPlayerPtr()); removeContainerScripts(getPlayerPtr());
mWorldScene->changeToExteriorCell(position, adjustPlayerPos, changeEvent); mWorldScene->changeToExteriorCell(position, adjustPlayerPos, changeEvent);
addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
mRendering->getCamera()->instantTransition();
} }
void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
@ -2393,6 +2395,11 @@ namespace MWWorld
return mRendering->getCamera()->isFirstPerson(); return mRendering->getCamera()->isFirstPerson();
} }
bool World::isPreviewModeEnabled() const
{
return mRendering->getCamera()->getMode() == MWRender::Camera::Mode::Preview;
}
void World::togglePreviewMode(bool enable) void World::togglePreviewMode(bool enable)
{ {
mRendering->togglePreviewMode(enable); mRendering->togglePreviewMode(enable);
@ -2403,6 +2410,16 @@ namespace MWWorld
return mRendering->toggleVanityMode(enable); return mRendering->toggleVanityMode(enable);
} }
void World::disableDeferredPreviewRotation()
{
mRendering->getCamera()->disableDeferredPreviewRotation();
}
void World::applyDeferredPreviewRotationToPlayer(float dt)
{
mRendering->getCamera()->applyDeferredPreviewRotationToPlayer(dt);
}
void World::allowVanityMode(bool allow) void World::allowVanityMode(bool allow)
{ {
mRendering->allowVanityMode(allow); mRendering->allowVanityMode(allow);

View file

@ -524,6 +524,7 @@ namespace MWWorld
void togglePOV(bool force = false) override; void togglePOV(bool force = false) override;
bool isFirstPerson() const override; bool isFirstPerson() const override;
bool isPreviewModeEnabled() const override;
void togglePreviewMode(bool enable) override; void togglePreviewMode(bool enable) override;
@ -536,6 +537,9 @@ namespace MWWorld
bool vanityRotateCamera(float * rot) override; bool vanityRotateCamera(float * rot) override;
void setCameraDistance(float dist, bool adjust = false, bool override = true) override; void setCameraDistance(float dist, bool adjust = false, bool override = true) override;
void applyDeferredPreviewRotationToPlayer(float dt) override;
void disableDeferredPreviewRotation() override;
void setupPlayer() override; void setupPlayer() override;
void renderPlayer() override; void renderPlayer() override;

View file

@ -8,6 +8,12 @@
#include <components/loadinglistener/loadinglistener.hpp> #include <components/loadinglistener/loadinglistener.hpp>
#include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwworld/esmstore.hpp"
#include "apps/openmw/mwmechanics/spelllist.hpp"
namespace MWMechanics
{
SpellList::SpellList(const std::string& id, int type) : mId(id), mType(type) {}
}
static Loading::Listener dummyListener; static Loading::Listener dummyListener;

View file

@ -59,7 +59,7 @@ add_component_dir (nif
) )
add_component_dir (nifosg add_component_dir (nifosg
nifloader controller particle userdata nifloader controller particle matrixtransform
) )
add_component_dir (nifbullet add_component_dir (nifbullet

View file

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

View file

@ -4,7 +4,7 @@
#include "esmwriter.hpp" #include "esmwriter.hpp"
unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE;
int ESM::SavedGame::sCurrentFormat = 12; int ESM::SavedGame::sCurrentFormat = 13;
void ESM::SavedGame::load (ESMReader &esm) void ESM::SavedGame::load (ESMReader &esm)
{ {

View file

@ -4,14 +4,13 @@
#include <osg/TexMat> #include <osg/TexMat>
#include <osg/Material> #include <osg/Material>
#include <osg/Texture2D> #include <osg/Texture2D>
#include <osg/UserDataContainer>
#include <osgParticle/Emitter> #include <osgParticle/Emitter>
#include <components/nif/data.hpp> #include <components/nif/data.hpp>
#include <components/sceneutil/morphgeometry.hpp> #include <components/sceneutil/morphgeometry.hpp>
#include "userdata.hpp" #include "matrixtransform.hpp"
namespace NifOsg namespace NifOsg
{ {
@ -119,13 +118,12 @@ void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv)
{ {
if (hasInput()) if (hasInput())
{ {
osg::MatrixTransform* trans = static_cast<osg::MatrixTransform*>(node); NifOsg::MatrixTransform* trans = static_cast<NifOsg::MatrixTransform*>(node);
osg::Matrix mat = trans->getMatrix(); osg::Matrix mat = trans->getMatrix();
float time = getInputValue(nv); float time = getInputValue(nv);
NodeUserData* userdata = static_cast<NodeUserData*>(trans->getUserDataContainer()->getUserObject(0)); Nif::Matrix3& rot = trans->mRotationScale;
Nif::Matrix3& rot = userdata->mRotationScale;
bool setRot = false; bool setRot = false;
if(!mRotations.empty()) if(!mRotations.empty())
@ -140,18 +138,18 @@ void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv)
} }
else else
{ {
// no rotation specified, use the previous value from the UserData // no rotation specified, use the previous value
for (int i=0;i<3;++i) for (int i=0;i<3;++i)
for (int j=0;j<3;++j) for (int j=0;j<3;++j)
mat(j,i) = rot.mValues[i][j]; // NB column/row major difference mat(j,i) = rot.mValues[i][j]; // NB column/row major difference
} }
if (setRot) // copy the new values back to the UserData if (setRot) // copy the new values back
for (int i=0;i<3;++i) for (int i=0;i<3;++i)
for (int j=0;j<3;++j) for (int j=0;j<3;++j)
rot.mValues[i][j] = mat(j,i); // NB column/row major difference rot.mValues[i][j] = mat(j,i); // NB column/row major difference
float& scale = userdata->mScale; float& scale = trans->mScale;
if(!mScales.empty()) if(!mScales.empty())
scale = mScales.interpKey(time); scale = mScales.interpKey(time);

View file

@ -9,11 +9,9 @@
#include <components/sceneutil/controller.hpp> #include <components/sceneutil/controller.hpp>
#include <components/sceneutil/statesetupdater.hpp> #include <components/sceneutil/statesetupdater.hpp>
#include <set> //UVController #include <set>
// FlipController
#include <osg/Texture2D> #include <osg/Texture2D>
#include <osg/ref_ptr>
#include <osg/StateSet> #include <osg/StateSet>
#include <osg/NodeCallback> #include <osg/NodeCallback>
@ -22,8 +20,6 @@
namespace osg namespace osg
{ {
class Node;
class StateSet;
class Material; class Material;
} }

View file

@ -0,0 +1,23 @@
#include "matrixtransform.hpp"
namespace NifOsg
{
MatrixTransform::MatrixTransform()
: osg::MatrixTransform()
{
}
MatrixTransform::MatrixTransform(const Nif::Transformation &trafo)
: osg::MatrixTransform(trafo.toMatrix())
, mScale(trafo.scale)
, mRotationScale(trafo.rotation)
{
}
MatrixTransform::MatrixTransform(const MatrixTransform &copy, const osg::CopyOp &copyop)
: osg::MatrixTransform(copy, copyop)
, mScale(copy.mScale)
, mRotationScale(copy.mRotationScale)
{
}
}

View file

@ -0,0 +1,31 @@
#ifndef OPENMW_COMPONENTS_NIFOSG_MATRIXTRANSFORM_H
#define OPENMW_COMPONENTS_NIFOSG_MATRIXTRANSFORM_H
#include <components/nif/niftypes.hpp>
#include <osg/MatrixTransform>
namespace NifOsg
{
class MatrixTransform : public osg::MatrixTransform
{
public:
MatrixTransform();
MatrixTransform(const Nif::Transformation &trafo);
MatrixTransform(const MatrixTransform &copy, const osg::CopyOp &copyop);
META_Node(NifOsg, MatrixTransform)
// Hack: account for Transform differences between OSG and NIFs.
// OSG uses a 4x4 matrix, NIF's use a 3x3 rotationScale, float scale, and vec3 position.
// Decomposing the original components from the 4x4 matrix isn't possible, which causes
// problems when a KeyframeController wants to change only one of these components. So
// we store the scale and rotation components separately here.
float mScale{0.f};
Nif::Matrix3 mRotationScale;
};
}
#endif

View file

@ -3,7 +3,6 @@
#include <mutex> #include <mutex>
#include <osg/Matrixf> #include <osg/Matrixf>
#include <osg/MatrixTransform>
#include <osg/Geometry> #include <osg/Geometry>
#include <osg/Array> #include <osg/Array>
#include <osg/LOD> #include <osg/LOD>
@ -43,8 +42,9 @@
#include <components/sceneutil/riggeometry.hpp> #include <components/sceneutil/riggeometry.hpp>
#include <components/sceneutil/morphgeometry.hpp> #include <components/sceneutil/morphgeometry.hpp>
#include "matrixtransform.hpp"
#include "nodeindexholder.hpp"
#include "particle.hpp" #include "particle.hpp"
#include "userdata.hpp"
namespace namespace
{ {
@ -170,31 +170,6 @@ namespace
namespace NifOsg namespace NifOsg
{ {
class CollisionSwitch : public osg::MatrixTransform
{
public:
CollisionSwitch() : osg::MatrixTransform()
{
}
CollisionSwitch(const CollisionSwitch& copy, const osg::CopyOp& copyop)
: osg::MatrixTransform(copy, copyop)
{
}
META_Node(NifOsg, CollisionSwitch)
CollisionSwitch(const osg::Matrixf& transformations, bool enabled) : osg::MatrixTransform(transformations)
{
setEnabled(enabled);
}
void setEnabled(bool enabled)
{
setNodeMask(enabled ? ~0 : Loader::getIntersectionDisabledNodeMask());
}
};
bool Loader::sShowMarkers = false; bool Loader::sShowMarkers = false;
void Loader::setShowMarkers(bool show) void Loader::setShowMarkers(bool show)
@ -501,14 +476,6 @@ namespace NifOsg
case Nif::RC_NiBillboardNode: case Nif::RC_NiBillboardNode:
dataVariance = osg::Object::DYNAMIC; dataVariance = osg::Object::DYNAMIC;
break; break;
case Nif::RC_NiCollisionSwitch:
{
bool enabled = nifNode->flags & Nif::NiNode::Flag_ActiveCollision;
node = new CollisionSwitch(nifNode->trafo.toMatrix(), enabled);
// This matrix transform must not be combined with another matrix transform.
dataVariance = osg::Object::DYNAMIC;
break;
}
default: default:
// The Root node can be created as a Group if no transformation is required. // The Root node can be created as a Group if no transformation is required.
// This takes advantage of the fact root nodes can't have additional controllers // This takes advantage of the fact root nodes can't have additional controllers
@ -521,7 +488,14 @@ namespace NifOsg
break; break;
} }
if (!node) if (!node)
node = new osg::MatrixTransform(nifNode->trafo.toMatrix()); node = new NifOsg::MatrixTransform(nifNode->trafo);
if (nifNode->recType == Nif::RC_NiCollisionSwitch && !(nifNode->flags & Nif::NiNode::Flag_ActiveCollision))
{
node->setNodeMask(Loader::getIntersectionDisabledNodeMask());
// This node must not be combined with another node.
dataVariance = osg::Object::DYNAMIC;
}
node->setDataVariance(dataVariance); node->setDataVariance(dataVariance);
@ -549,14 +523,11 @@ namespace NifOsg
if (!rootNode) if (!rootNode)
rootNode = node; rootNode = node;
// UserData used for a variety of features: // The original NIF record index is used for a variety of features:
// - finding the correct emitter node for a particle system // - finding the correct emitter node for a particle system
// - establishing connections to the animated collision shapes, which are handled in a separate loader // - establishing connections to the animated collision shapes, which are handled in a separate loader
// - finding a random child NiNode in NiBspArrayController // - finding a random child NiNode in NiBspArrayController
// - storing the previous 3x3 rotation and scale values for when a KeyframeController wants to node->getOrCreateUserDataContainer()->addUserObject(new NodeIndexHolder(nifNode->recIndex));
// change only certain elements of the 4x4 transform
node->getOrCreateUserDataContainer()->addUserObject(
new NodeUserData(nifNode->recIndex, nifNode->trafo.scale, nifNode->trafo.rotation));
for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->next) for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->next)
{ {

View file

@ -0,0 +1,35 @@
#ifndef OPENMW_COMPONENTS_NIFOSG_NODEINDEXHOLDER_H
#define OPENMW_COMPONENTS_NIFOSG_NODEINDEXHOLDER_H
#include <osg/Object>
namespace NifOsg
{
class NodeIndexHolder : public osg::Object
{
public:
NodeIndexHolder() = default;
NodeIndexHolder(int index)
: mIndex(index)
{
}
NodeIndexHolder(const NodeIndexHolder& copy, const osg::CopyOp& copyop)
: Object(copy, copyop)
, mIndex(copy.mIndex)
{
}
META_Object(NifOsg, NodeIndexHolder)
int getIndex() const { return mIndex; }
private:
// NIF record index
int mIndex{0};
};
}
#endif

View file

@ -11,7 +11,7 @@
#include <components/nif/controlled.hpp> #include <components/nif/controlled.hpp>
#include <components/nif/data.hpp> #include <components/nif/data.hpp>
#include "userdata.hpp" #include "nodeindexholder.hpp"
namespace NifOsg namespace NifOsg
{ {
@ -383,8 +383,8 @@ void FindGroupByRecIndex::applyNode(osg::Node &searchNode)
{ {
if (searchNode.getUserDataContainer() && searchNode.getUserDataContainer()->getNumUserObjects()) if (searchNode.getUserDataContainer() && searchNode.getUserDataContainer()->getNumUserObjects())
{ {
NodeUserData* holder = dynamic_cast<NodeUserData*>(searchNode.getUserDataContainer()->getUserObject(0)); NodeIndexHolder* holder = dynamic_cast<NodeIndexHolder*>(searchNode.getUserDataContainer()->getUserObject(0));
if (holder && holder->mIndex == mRecIndex) if (holder && holder->getIndex() == mRecIndex)
{ {
osg::Group* group = searchNode.asGroup(); osg::Group* group = searchNode.asGroup();
if (!group) if (!group)

View file

@ -1,48 +0,0 @@
#ifndef OPENMW_COMPONENTS_NIFOSG_USERDATA_H
#define OPENMW_COMPONENTS_NIFOSG_USERDATA_H
#include <components/nif/niftypes.hpp>
#include <osg/Object>
namespace NifOsg
{
// Note if you are copying a scene graph with this user data you should use the DEEP_COPY_USERDATA copyop.
class NodeUserData : public osg::Object
{
public:
NodeUserData(int index, float scale, const Nif::Matrix3& rotationScale)
: mIndex(index), mScale(scale), mRotationScale(rotationScale)
{
}
NodeUserData()
: mIndex(0), mScale(0)
{
}
NodeUserData(const NodeUserData& copy, const osg::CopyOp& copyop)
: Object(copy, copyop)
, mIndex(copy.mIndex)
, mScale(copy.mScale)
, mRotationScale(copy.mRotationScale)
{
}
META_Object(NifOsg, NodeUserData)
// NIF record index
int mIndex;
// Hack: account for Transform differences between OSG and NIFs.
// OSG uses a 4x4 matrix, NIF's use a 3x3 rotationScale, float scale, and vec3 position.
// Decomposing the original components from the 4x4 matrix isn't possible, which causes
// problems when a KeyframeController wants to change only one of these components. So
// we store the scale and rotation components separately here.
// Note for a cleaner solution it would be possible to write a custom Transform node
float mScale;
Nif::Matrix3 mRotationScale;
};
}
#endif

View file

@ -64,7 +64,7 @@ namespace SceneUtil
for (const osg::ref_ptr<osg::Node>& node : mToCopy) for (const osg::ref_ptr<osg::Node>& node : mToCopy)
{ {
if (node->getNumParents() > 1) if (node->getNumParents() > 1)
Log(Debug::Error) << "Error CopyRigVisitor: node has multiple parents"; Log(Debug::Error) << "Error CopyRigVisitor: node has " << node->getNumParents() << " parents";
while (node->getNumParents()) while (node->getNumParents())
node->getParent(0)->removeChild(node); node->getParent(0)->removeChild(node);

View file

@ -6,8 +6,6 @@
#include <osgParticle/ParticleSystemUpdater> #include <osgParticle/ParticleSystemUpdater>
#include <osgParticle/Emitter> #include <osgParticle/Emitter>
#include <components/nifosg/userdata.hpp>
#include <components/sceneutil/morphgeometry.hpp> #include <components/sceneutil/morphgeometry.hpp>
#include <components/sceneutil/riggeometry.hpp> #include <components/sceneutil/riggeometry.hpp>
@ -22,15 +20,6 @@ namespace SceneUtil
| osg::CopyOp::DEEP_COPY_USERDATA); | osg::CopyOp::DEEP_COPY_USERDATA);
} }
osg::Object* CopyOp::operator ()(const osg::Object* node) const
{
// We should copy node transformations when we copy node
if (dynamic_cast<const NifOsg::NodeUserData*>(node))
return static_cast<NifOsg::NodeUserData*>(node->clone(*this));
return osg::CopyOp::operator()(node);
}
osg::Node* CopyOp::operator ()(const osg::Node* node) const osg::Node* CopyOp::operator ()(const osg::Node* node) const
{ {
if (const osgParticle::ParticleProcessor* processor = dynamic_cast<const osgParticle::ParticleProcessor*>(node)) if (const osgParticle::ParticleProcessor* processor = dynamic_cast<const osgParticle::ParticleProcessor*>(node))

View file

@ -30,8 +30,6 @@ namespace SceneUtil
virtual osg::Node* operator() (const osg::Node* node) const; virtual osg::Node* operator() (const osg::Node* node) const;
virtual osg::Drawable* operator() (const osg::Drawable* drawable) const; virtual osg::Drawable* operator() (const osg::Drawable* drawable) const;
virtual osg::Object* operator ()(const osg::Object* node) const;
private: private:
// maps new pointers to their old pointers // maps new pointers to their old pointers
// a little messy, but I think this should be the most efficient way // a little messy, but I think this should be the most efficient way

View file

@ -3,6 +3,8 @@
#include <osgDB/ObjectWrapper> #include <osgDB/ObjectWrapper>
#include <osgDB/Registry> #include <osgDB/Registry>
#include <components/nifosg/matrixtransform.hpp>
#include <components/sceneutil/positionattitudetransform.hpp> #include <components/sceneutil/positionattitudetransform.hpp>
#include <components/sceneutil/skeleton.hpp> #include <components/sceneutil/skeleton.hpp>
#include <components/sceneutil/riggeometry.hpp> #include <components/sceneutil/riggeometry.hpp>
@ -74,6 +76,15 @@ public:
} }
}; };
class MatrixTransformSerializer : public osgDB::ObjectWrapper
{
public:
MatrixTransformSerializer()
: osgDB::ObjectWrapper(createInstanceFunc<NifOsg::MatrixTransform>, "NifOsg::MatrixTransform", "osg::Object osg::Node osg::Transform osg::MatrixTransform NifOsg::MatrixTransform")
{
}
};
osgDB::ObjectWrapper* makeDummySerializer(const std::string& classname) osgDB::ObjectWrapper* makeDummySerializer(const std::string& classname)
{ {
return new osgDB::ObjectWrapper(createInstanceFunc<osg::DummyObject>, classname, "osg::Object"); return new osgDB::ObjectWrapper(createInstanceFunc<osg::DummyObject>, classname, "osg::Object");
@ -100,6 +111,7 @@ void registerSerializers()
mgr->addWrapper(new MorphGeometrySerializer); mgr->addWrapper(new MorphGeometrySerializer);
mgr->addWrapper(new LightManagerSerializer); mgr->addWrapper(new LightManagerSerializer);
mgr->addWrapper(new CameraRelativeTransformSerializer); mgr->addWrapper(new CameraRelativeTransformSerializer);
mgr->addWrapper(new MatrixTransformSerializer);
// Don't serialize Geometry data as we are more interested in the overall structure rather than tons of vertex data that would make the file large and hard to read. // Don't serialize Geometry data as we are more interested in the overall structure rather than tons of vertex data that would make the file large and hard to read.
mgr->removeWrapper(mgr->findWrapper("osg::Geometry")); mgr->removeWrapper(mgr->findWrapper("osg::Geometry"));
@ -118,7 +130,6 @@ void registerSerializers()
"SceneUtil::StateSetUpdater", "SceneUtil::StateSetUpdater",
"SceneUtil::DisableLight", "SceneUtil::DisableLight",
"SceneUtil::MWShadowTechnique", "SceneUtil::MWShadowTechnique",
"NifOsg::NodeUserData",
"NifOsg::FlipController", "NifOsg::FlipController",
"NifOsg::KeyframeController", "NifOsg::KeyframeController",
"NifOsg::TextKeyMapHolder", "NifOsg::TextKeyMapHolder",
@ -131,7 +142,8 @@ void registerSerializers()
"NifOsg::StaticBoundingBoxCallback", "NifOsg::StaticBoundingBoxCallback",
"NifOsg::GeomMorpherController", "NifOsg::GeomMorpherController",
"NifOsg::UpdateMorphGeometry", "NifOsg::UpdateMorphGeometry",
"NifOsg::CollisionSwitch", "NifOsg::UVController",
"NifOsg::NodeIndexHolder",
"osgMyGUI::Drawable", "osgMyGUI::Drawable",
"osg::DrawCallback", "osg::DrawCallback",
"osgOQ::ClearQueriesCallback", "osgOQ::ClearQueriesCallback",

View file

@ -76,6 +76,28 @@ bool Manager::getBool (const std::string& setting, const std::string& category)
return Misc::StringUtils::ciEqual(string, "true"); 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) void Manager::setString(const std::string &setting, const std::string &category, const std::string &value)
{ {
CategorySettingValueMap::key_type key = std::make_pair(category, setting); 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"); 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) void Manager::resetPendingChange(const std::string &setting, const std::string &category)
{ {
CategorySettingValueMap::key_type key = std::make_pair(category, setting); CategorySettingValueMap::key_type key = std::make_pair(category, setting);

View file

@ -6,6 +6,8 @@
#include <set> #include <set>
#include <map> #include <map>
#include <string> #include <string>
#include <osg/Vec2f>
#include <osg/Vec3f>
namespace Settings namespace Settings
{ {
@ -44,11 +46,15 @@ namespace Settings
static float getFloat (const std::string& setting, const std::string& category); static float getFloat (const std::string& setting, const std::string& category);
static std::string getString (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 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 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 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 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 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);
}; };
} }

View file

@ -34,6 +34,9 @@ namespace Shader
foundPos = source.find_first_of("\n\r", foundPos); foundPos = source.find_first_of("\n\r", foundPos);
foundPos = source.find_first_not_of("\n\r", foundPos); foundPos = source.find_first_not_of("\n\r", foundPos);
if (foundPos == std::string::npos)
break;
size_t lineDirectivePosition = source.rfind("#line", foundPos); size_t lineDirectivePosition = source.rfind("#line", foundPos);
int lineNumber; int lineNumber;
if (lineDirectivePosition != std::string::npos) if (lineDirectivePosition != std::string::npos)

View file

@ -148,7 +148,8 @@ color topic enable
:Range: True/False :Range: True/False
:Default: False :Default: False
Control wether additionnal formatting will be applied to dialogs topic. See 'color topic specific' and 'color topic exhausted' for details. This setting controls whether the topics available in the dialogue topic list are coloured according to their state.
See 'color topic specific' and 'color topic exhausted' for details.
color topic specific color topic specific
-------------------- --------------------
@ -157,11 +158,11 @@ color topic specific
:Range: 0.0 to 1.0 :Range: 0.0 to 1.0
:Default: empty :Default: empty
This setting overrides the color of keywords in the dialogue topic window. This setting overrides the colour of dialogue topics that have a response unique to the actors speaking.
The value is composed of four floating point values representing the red, green, blue and alpha channels. The value is composed of four floating point values representing the red, green, blue and alpha channels.
The alpha value is currently ignored. The alpha value is currently ignored.
The color is overriden if the actor is about to give an answer that is unique to him (that is, dialogue with their object ID in the Actor field) that wasn't seen yet. A topic response is considered unique if its Actor filter field contains the speaking actor's object ID and hasn't yet been read.
color topic exhausted color topic exhausted
--------------------- ---------------------
@ -170,8 +171,8 @@ color topic exhausted
:Range: 0.0 to 1.0 :Range: 0.0 to 1.0
:Default: empty :Default: empty
This setting overrides the color of keywords in the dialogue topic window. This setting overrides the colour of dialogue topics which have been "exhausted" by the player.
The value is composed of four floating point values representing the red, green, blue and alpha channels. The value is composed of four floating point values representing the red, green, blue and alpha channels.
The alpha value is currently ignored. The alpha value is currently ignored.
The color is overridden if the next actor responses to the topic keyword has already been seen by the player. A topic is considered "exhausted" if the response the player is about to see has already been seen.

View file

@ -136,7 +136,7 @@ This setting controls third person view mode.
False: View is centered on the character's head. Crosshair is hidden. False: View is centered on the character's head. Crosshair is hidden.
True: In non-combat mode camera is positioned behind the character's shoulder. Crosshair is visible in third person mode as well. True: In non-combat mode camera is positioned behind the character's shoulder. Crosshair is visible in third person mode as well.
This setting can only be configured by editing the settings configuration file. This setting can be controlled in Advanced tab of the launcher.
view over shoulder offset view over shoulder offset
------------------------- -------------------------
@ -174,3 +174,27 @@ Slightly pulls camera away (or closer in case of negative value) when the charac
This setting can only be configured by editing the settings configuration file. 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.

View file

@ -0,0 +1,115 @@
Fog Settings
############
use distant fog
---------------
:Type: boolean
:Range: True/False
:Default: False
This setting overhauls the behavior of fog calculations.
Normally the fog start and end distance are proportional to the viewing distance
and use the fog depth set in the fallback settings.
Enabling this setting separates the fog distance from the viewing distance and fallback settings and makes fog distance
and apparent density dependent on the weather and the current location according to the settings below.
Unfortunately specific weather-dependent fog factor and offset parameters are currently hard-coded.
They are based off the default settings of MGE XE.
+--------------+------------+--------+
| Weather Type | Fog Factor | Offset |
+==============+============+========+
| Clear | 1.0 | 0.0 |
+--------------+------------+--------+
| Cloudy | 0.9 | 0.0 |
+--------------+------------+--------+
| Foggy | 0.2 | 0.3 |
+--------------+------------+--------+
| Overcast | 0.7 | 0.0 |
+--------------+------------+--------+
| Rain | 0.5 | 0.1 |
+--------------+------------+--------+
| Thunderstorm | 0.5 | 0.2 |
+--------------+------------+--------+
| Ashstorm | 0.2 | 0.5 |
+--------------+------------+--------+
| Blight | 0.2 | 0.6 |
+--------------+------------+--------+
| Snow | 0.5 | 0.4 |
+--------------+------------+--------+
| Blizzard | 0.16 | 0.7 |
+--------------+------------+--------+
Non-underwater fog start and end distance are calculated like this according to these parameters::
fog start distance = fog factor * (base fog start distance - fog offset * base fog end distance)
fog end distance = fog factor * (1.0 - fog offset) * base fog end distance
Underwater fog distance is used as-is.
A negative fog start distance means that the fog starts behind the camera
so the entirety of the scene will be at least partially fogged.
A negative fog end distance means that the fog ends behind the camera
so the entirety of the scene will be completely submerged in the fog.
Fog end distance should be larger than the fog start distance.
This setting and all further settings can only be configured by editing the settings configuration file.
distant land fog start
----------------------
:Type: floating point
:Range: The whole range of 32-bit floating point
:Default: 16384 (2 cells)
This is the base fog start distance used for distant fog calculations in exterior locations.
distant land fog end
--------------------
:Type: floating point
:Range: The whole range of 32-bit floating point
:Default: 40960 (5 cells)
This is the base fog end distance used for distant fog calculations in exterior locations.
distant underwater fog start
----------------------------
:Type: floating point
:Range: The whole range of 32-bit floating point
:Default: -4096
This is the base fog start distance used for distant fog calculations in underwater locations.
distant underwater fog end
--------------------------
:Type: floating point
:Range: The whole range of 32-bit floating point
:Default: 2457.6
This is the base fog end distance used for distant fog calculations in underwater locations.
distant interior fog start
--------------------------
:Type: floating point
:Range: The whole range of 32-bit floating point
:Default: 0
This is the base fog start distance used for distant fog calculations in interior locations.
distant interior fog end
------------------------
:Type: floating point
:Range: The whole range of 32-bit floating point
:Default: 16384 (2 cells)
This is the base fog end distance used for distant fog calculations in interior locations.

View file

@ -329,7 +329,7 @@ If disabled then the whole character's body is pointed to the direction of view.
If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it also changes straight right and straight left movement. If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it also changes straight right and straight left movement.
This setting can only be configured by editing the settings configuration file. This setting can be controlled in Advanced tab of the launcher.
swim upward coef swim upward coef
---------------- ----------------
@ -356,3 +356,17 @@ If disabled then the 3 best skills of trainers and the training limits take into
If enabled then the 3 best skills of trainers and the training limits are based on the trainer base skills. If enabled then the 3 best skills of trainers and the training limits are based on the trainer base skills.
This setting can be controlled in Advanced tab of the launcher. This setting can be controlled in Advanced tab of the launcher.
always allow stealing from knocked out actors
---------------------------------------------
:Type: boolean
:Range: True/False
:Default: False
By Bethesda's design, in the latest released version of Morrowind pickpocketing is impossible during combat,
even if the fighting NPC is knocked out.
This setting allows the player to steal items from fighting NPCs that were knocked out if enabled.
This setting can be controlled in Advanced tab of the launcher.

View file

@ -29,7 +29,7 @@ Specify the format for screen shots taken by pressing the screen shot key (bound
This setting should be the file extension commonly associated with the desired format. This setting should be the file extension commonly associated with the desired format.
The formats supported will be determined at compilation, but "jpg", "png", and "tga" should be allowed. The formats supported will be determined at compilation, but "jpg", "png", and "tga" should be allowed.
This setting can only be configured by editing the settings configuration file. This setting can be configured in Advanced tab of the launcher.
texture mag filter texture mag filter
------------------ ------------------

View file

@ -41,6 +41,7 @@ The ranges included with each setting are the physically possible ranges, not re
camera camera
cells cells
fog
map map
GUI GUI
HUD HUD

View file

@ -5,8 +5,8 @@ Copyright 2020 Bret Curtis <psi29a@gmail.com>
--> -->
<component type="desktop"> <component type="desktop">
<id>org.openmw.launcher.desktop</id> <id>org.openmw.launcher.desktop</id>
<metadata_license>CC0-1.0</metadata_license> <metadata_license>GPL-3+</metadata_license>
<project_license>GPL-3.0 and MIT</project_license> <project_license>GPL-3+</project_license>
<name>OpenMW</name> <name>OpenMW</name>
<summary>Unofficial open source engine re-implementation of the game Morrowind</summary> <summary>Unofficial open source engine re-implementation of the game Morrowind</summary>
<description> <description>

View file

@ -48,6 +48,12 @@ auto switch shoulder = true
# Slightly pulls camera away when the character moves. Works only in 'view over shoulder' mode. Set to 0 to disable. # 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 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] [Cells]
# Preload cells in a background thread. All settings starting with 'preload' have no effect unless this is enabled. # Preload cells in a background thread. All settings starting with 'preload' have no effect unless this is enabled.
@ -325,6 +331,9 @@ swim upward coef = 0.0
# Make the training skills proposed by a trainer based on its base attribute instead of its modified ones # Make the training skills proposed by a trainer based on its base attribute instead of its modified ones
trainers training skills based on base skill = false trainers training skills based on base skill = false
# Make stealing items from NPCs that were knocked down possible during combat.
always allow stealing from knocked out actors = false
[General] [General]
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).

View file

@ -4,18 +4,28 @@
<widget class="QWidget" name="AdvancedPage"> <widget class="QWidget" name="AdvancedPage">
<layout class="QVBoxLayout" name="pageVerticalLayout"> <layout class="QVBoxLayout" name="pageVerticalLayout">
<item> <item>
<widget class="QScrollArea" name="scrollArea"> <widget class="QTabWidget" name="AdvancedTabWidget">
<property name="widgetResizable"> <property name="tabPosition">
<bool>true</bool> <enum>QTabWidget::West</enum>
</property> </property>
<widget class="QWidget" name="scrollAreaWidgetContents"> <property name="currentIndex">
<layout class="QVBoxLayout" name="scrollAreaVerticalLayout"> <number>0</number>
</property>
<widget class="QWidget" name="GameMechanics">
<attribute name="title">
<string>Game mechanics</string>
</attribute>
<layout class="QVBoxLayout">
<item> <item>
<widget class="QGroupBox" name="gameGroup"> <widget class="QCheckBox" name="toggleSneakCheckBox">
<property name="title"> <property name="toolTip">
<string>Game Mechanics</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting causes the behavior of the sneak key (bound to Ctrl by default) to toggle sneaking on and off rather than requiring the key to be held down while sneaking. Players that spend significant time sneaking may find the character easier to control with this option enabled. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<layout class="QVBoxLayout" name="gameGroupVerticalLayout"> <property name="text">
<string>Toggle sneak</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QCheckBox" name="canLootDuringDeathAnimationCheckBox"> <widget class="QCheckBox" name="canLootDuringDeathAnimationCheckBox">
<property name="toolTip"> <property name="toolTip">
@ -32,17 +42,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Followers attack on sight</string> <string>Followers defend immediately</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="preventMerchantEquippingCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Prevents merchants from equipping items that are sold to them.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Prevent merchant equipping</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -52,7 +52,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make the value of filled soul gems dependent only on soul magnitude.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make the value of filled soul gems dependent only on soul magnitude.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Rebalance soul gem values</string> <string>Soulgem values rebalance</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -72,7 +72,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make disposition change of merchants caused by trading permanent.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make disposition change of merchants caused by trading permanent.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Barter disposition change is permanent</string> <string>Permanent barter disposition changes</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -96,27 +96,32 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="uncappedDamageFatigueCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.&lt;/p&gt;&lt;p&gt;This means that unlike Morrowind you will be able to knock down actors using this effect.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Uncapped Damage Fatigue</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="normaliseRaceSpeedCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Don't use race weight in NPC movement speed calculations.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Racial variation in speed fix</string>
</property>
</widget>
</item>
<item alignment="Qt::AlignLeft"> <item alignment="Qt::AlignLeft">
<widget class="QWidget" name="unarmedFactorsStrengthGroup" native="true"> <widget class="QWidget" name="unarmedFactorsStrengthGroup">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Factor strength into hand-to-hand damage calculations, as the MCP formula: damage * (strength / 40).&lt;/p&gt;&lt;p&gt;The default value is Off.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Factor strength into hand-to-hand damage calculations, as the MCP formula: damage * (strength / 40).&lt;/p&gt;&lt;p&gt;The default value is Off.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<layout class="QHBoxLayout" name="horizontalUnarmedStrengthLayout"> <layout class="QHBoxLayout" name="horizontalUnarmedStrengthLayout">
<property name="spacing">
<number>-1</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignRight"> <item alignment="Qt::AlignRight">
<widget class="QLabel" name="unarmedFactorsStrengthLabel"> <widget class="QLabel" name="unarmedFactorsStrengthLabel">
<property name="text"> <property name="text">
@ -149,6 +154,43 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="stealingFromKnockedOutCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make stealing items from NPCs that were knocked down possible during combat.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Always allow stealing from knocked out actors</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="Visuals">
<attribute name="title">
<string>Visuals</string>
</attribute>
<layout class="QVBoxLayout">
<item>
<widget class="QCheckBox" name="bumpMapLocalLightingCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark.
Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option.
Affected objects will use shaders.
&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Bump/reflect map local lighting</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QCheckBox" name="magicItemAnimationsCheckBox"> <widget class="QCheckBox" name="magicItemAnimationsCheckBox">
<property name="toolTip"> <property name="toolTip">
@ -159,16 +201,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="normaliseRaceSpeedCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Don't use race weight in NPC movement speed calculations.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Normalise race speed</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QCheckBox" name="animSourcesCheckBox"> <widget class="QCheckBox" name="animSourcesCheckBox">
<property name="toolTip"> <property name="toolTip">
@ -180,23 +212,11 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QWidget" name="sheathingGroup" native="true"> <widget class="QWidget" name="sheathingGroup">
<layout class="QVBoxLayout" name="sheathingLayout"> <layout class="QVBoxLayout" name="sheathingLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin"> <property name="leftMargin">
<number>20</number> <number>20</number>
</property> </property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item> <item>
<widget class="QCheckBox" name="weaponSheathingCheckBox"> <widget class="QCheckBox" name="weaponSheathingCheckBox">
<property name="enabled"> <property name="enabled">
@ -227,208 +247,92 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="uncappedDamageFatigueCheckBox"> <widget class="QCheckBox" name="viewOverShoulderCheckBox">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.&lt;/p&gt;&lt;p&gt;This means that unlike Morrowind you will be able to knock down actors using this effect.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting controls third person view mode.&lt;/p&gt;&lt;p&gt;False: View is centered on the character's head. Crosshair is hidden.
True: In non-combat mode camera is positioned behind the character's shoulder. Crosshair is visible in third person mode as well.
&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Uncapped Damage Fatigue</string> <string>View over the shoulder</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="trainersTrainingSkillsBasedOnBaseSkillCheckBox"> <widget class="QCheckBox" name="turnToMovementDirectionCheckBox">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Affects side and diagonal movement. Enabling this setting makes movement more realistic.&lt;/p&gt;&lt;p&gt;If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.&lt;/p&gt;&lt;p&gt;If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it also changes straight right and straight left movement.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Trainers choose their training skills based on their base skill points</string> <string>Turn to movement direction</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="inputGroup"> <widget class="QCheckBox" name="distantLandCheckBox">
<property name="title">
<string>Input</string>
</property>
<layout class="QVBoxLayout" name="inputGroupVerticalLayout">
<item>
<widget class="QCheckBox" name="grabCursorCheckBox">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;OpenMW will capture control of the cursor if this setting is true.&lt;/p&gt;&lt;p&gt;In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.&lt;/p&gt;&lt;p&gt;This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.&lt;/p&gt;&lt;p&gt;Note for developers: its desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Grab cursor</string> <string>Distant land</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="toggleSneakCheckBox"> <widget class="QCheckBox" name="activeGridObjectPagingCheckBox">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting causes the behavior of the sneak key (bound to Ctrl by default) to toggle sneaking on and off rather than requiring the key to be held down while sneaking. Players that spend significant time sneaking may find the character easier to control with this option enabled. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use object paging for active cells grid.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Toggle sneak</string> <string>Active grid object paging</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="savesGroup">
<property name="title">
<string>Saves</string>
</property>
<layout class="QVBoxLayout" name="savesGroupVerticalLayout">
<item>
<widget class="QCheckBox" name="timePlayedCheckbox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Add &quot;Time Played&quot; to saves</string>
</property> </property>
</widget> </widget>
</item> </item>
<item alignment="Qt::AlignLeft"> <item alignment="Qt::AlignLeft">
<widget class="QWidget" name="maximumQuicksavesGroup" native="true"> <widget class="QWidget" name="viewingDistanceGroup">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting determines how many quicksave and autosave slots you can have at a time. If greater than 1, quicksaves will be sequentially created each time you quicksave. Once the maximum number of quicksaves has been reached, the oldest quicksave will be recycled the next time you perform a quicksave.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This value controls the maximum visible distance (in cell units).
</property> Larger values significantly improve rendering in exterior spaces,
<layout class="QHBoxLayout" name="maximumQuicksavesLayout"> but also increase the amount of rendered geometry and significantly reduce the frame rate.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property> </property>
<layout class="QHBoxLayout" name="viewingDistanceLayout">
<item> <item>
<widget class="QLabel" name="maximumQuicksavesLabel"> <widget class="QLabel" name="viewingDistanceLabel">
<property name="text"> <property name="text">
<string>Maximum Quicksaves</string> <string>Viewing distance</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QSpinBox" name="maximumQuicksavesComboBox"> <widget class="QDoubleSpinBox" name="viewingDistanceComboBox">
<property name="minimum"> <property name="minimum">
<number>1</number> <double>0.0</double>
</property>
<property name="singleStep">
<double>0.5</double>
</property>
<property name="suffix">
<string> Cells</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="testingGroup"> <spacer>
<property name="title">
<string>Testing</string>
</property>
<layout class="QVBoxLayout" name="testingGroupVerticalLayout">
<item>
<widget class="QLabel" name="testingLabel">
<property name="text">
<string>These settings are intended for testing mods and will cause issues if used for normal gameplay.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="testingLine">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="skipMenuCheckBox">
<property name="text">
<string>Skip menu and generate default character</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="startDefaultCharacterAtHorizontalLayout">
<item>
<spacer name="startDefaultCharacterAtHorizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QLabel" name="startDefaultCharacterAtLabel">
<property name="text">
<string>Start default character at</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="startDefaultCharacterAtField">
<property name="placeholderText">
<string>default cell</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Run script after startup:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="runScriptAfterStartupHorizontalLayout">
<item>
<widget class="QLineEdit" name="runScriptAfterStartupField"/>
</item>
<item>
<widget class="QPushButton" name="runScriptAfterStartupBrowseButton">
<property name="text">
<string>Browse…</string>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
</item> <widget class="QWidget" name="InterfaceChanges">
<item> <attribute name="title">
<widget class="QGroupBox" name="userInterfaceGroup"> <string>Interface changes</string>
<property name="title"> </attribute>
<string>User Interface</string> <layout class="QVBoxLayout">
</property>
<layout class="QVBoxLayout" name="userInterfaceVerticalLayout">
<item> <item>
<widget class="QCheckBox" name="showEffectDurationCheckBox"> <widget class="QCheckBox" name="showEffectDurationCheckBox">
<property name="toolTip"> <property name="toolTip">
@ -480,26 +384,11 @@
</widget> </widget>
</item> </item>
<item alignment="Qt::AlignLeft"> <item alignment="Qt::AlignLeft">
<widget class="QWidget" name="showOwnedGroup" native="true"> <widget class="QWidget" name="showOwnedGroup">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable visual clues for items owned by NPCs when the crosshair is on the object.&lt;/p&gt;&lt;p&gt;The default value is Off.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable visual clues for items owned by NPCs when the crosshair is on the object.&lt;/p&gt;&lt;p&gt;The default value is Off.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignRight"> <item alignment="Qt::AlignRight">
<widget class="QLabel" name="showOwnedLabel"> <widget class="QLabel" name="showOwnedLabel">
<property name="text"> <property name="text">
@ -537,6 +426,93 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="BugFixes">
<attribute name="title">
<string>Bug fixes</string>
</attribute>
<layout class="QVBoxLayout">
<item>
<widget class="QCheckBox" name="preventMerchantEquippingCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Prevents merchants from equipping items that are sold to them.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Merchant equipping fix</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="trainersTrainingSkillsBasedOnBaseSkillCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Trainers choose their training skills based on their base skill points</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="Miscellaneous">
<attribute name="title">
<string>Miscellaneous</string>
</attribute>
<layout class="QVBoxLayout" name="scrollAreaVerticalLayout">
<item>
<widget class="QGroupBox" name="savesGroup">
<property name="title">
<string>Saves</string>
</property>
<layout class="QVBoxLayout" name="savesGroupVerticalLayout">
<item>
<widget class="QCheckBox" name="timePlayedCheckbox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Add &quot;Time Played&quot; to saves</string>
</property>
</widget>
</item>
<item alignment="Qt::AlignLeft">
<widget class="QWidget" name="maximumQuicksavesGroup">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting determines how many quicksave and autosave slots you can have at a time. If greater than 1, quicksaves will be sequentially created each time you quicksave. Once the maximum number of quicksaves has been reached, the oldest quicksave will be recycled the next time you perform a quicksave.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<layout class="QHBoxLayout" name="maximumQuicksavesLayout">
<item>
<widget class="QLabel" name="maximumQuicksavesLabel">
<property name="text">
<string>Maximum Quicksaves</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="maximumQuicksavesComboBox">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -547,26 +523,11 @@
</property> </property>
<layout class="QVBoxLayout" name="otherGroupVerticalLayout"> <layout class="QVBoxLayout" name="otherGroupVerticalLayout">
<item alignment="Qt::AlignLeft"> <item alignment="Qt::AlignLeft">
<widget class="QWidget" name="screenshotFormatGroup" native="true"> <widget class="QWidget" name="screenshotFormatGroup">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Specify the format for screen shots taken by pressing the screen shot key (bound to F12 by default). This setting should be the file extension commonly associated with the desired format. The formats supported will be determined at compilation, but “jpg”, “png”, and “tga” should be allowed.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Specify the format for screen shots taken by pressing the screen shot key (bound to F12 by default). This setting should be the file extension commonly associated with the desired format. The formats supported will be determined at compilation, but “jpg”, “png”, and “tga” should be allowed.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<layout class="QHBoxLayout" name="screenshotFormatLayout"> <layout class="QHBoxLayout" name="screenshotFormatLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignRight"> <item alignment="Qt::AlignRight">
<widget class="QLabel" name="screenshotFormatLabel"> <widget class="QLabel" name="screenshotFormatLabel">
<property name="text"> <property name="text">
@ -599,6 +560,110 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="Testing">
<attribute name="title">
<string>Testing</string>
</attribute>
<layout class="QVBoxLayout">
<item>
<widget class="QLabel" name="testingLabel">
<property name="text">
<string>These settings are intended for testing mods and will cause issues if used for normal gameplay.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="testingLine">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="grabCursorCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;OpenMW will capture control of the cursor if this setting is true.&lt;/p&gt;&lt;p&gt;In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.&lt;/p&gt;&lt;p&gt;This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.&lt;/p&gt;&lt;p&gt;Note for developers: its desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Grab cursor</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="skipMenuCheckBox">
<property name="text">
<string>Skip menu and generate default character</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="startDefaultCharacterAtHorizontalLayout">
<item>
<spacer name="startDefaultCharacterAtHorizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="startDefaultCharacterAtLabel">
<property name="text">
<string>Start default character at</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="startDefaultCharacterAtField">
<property name="placeholderText">
<string>default cell</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Run script after startup:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="runScriptAfterStartupHorizontalLayout">
<item>
<widget class="QLineEdit" name="runScriptAfterStartupField"/>
</item>
<item>
<widget class="QPushButton" name="runScriptAfterStartupBrowseButton">
<property name="text">
<string>Browse…</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>

View file

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item alignment="Qt::AlignTop">
<widget class="QTabWidget" name="DisplayTabWidget"> <widget class="QTabWidget" name="DisplayTabWidget">
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>0</number>