Add OpenMW commits up to 13 Apr 2021

# Conflicts:
#   .travis.yml
#   apps/openmw/mwmechanics/actors.cpp
#   apps/openmw/mwmechanics/summoning.cpp
#   apps/openmw/mwphysics/mtphysics.hpp
pull/593/head
David Cernat 4 years ago
commit cedf70f367

@ -30,6 +30,29 @@ stages:
paths: paths:
- build/install/ - build/install/
Coverity:
extends: .Debian
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic coverity
- curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN
- tar xfz /tmp/cov-analysis-linux64.tgz
script:
- CI/before_script.linux.sh
# Add more than just `openmw` once we can build everything under 3h
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw
after_script:
- tar cfz cov-int.tar.gz cov-int
- curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME \
--form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL \
--form file=@cov-int.tar.gz --form version="`git describe --tags`" \
--form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID
variables:
CC: gcc
CXX: g++
timeout: 8h
Debian_GCC: Debian_GCC:
extends: .Debian extends: .Debian
cache: cache:
@ -93,24 +116,49 @@ Debian_Clang_tests:
CCACHE_SIZE: 1G CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1 BUILD_TESTS_ONLY: 1
MacOS: .MacOS:
image: macos-11-xcode-12
tags: tags:
- macos - macos
stage: build stage: build
only: only:
variables: variables:
- $CI_PROJECT_ID == "7107382" - $CI_PROJECT_ID == "7107382"
cache:
paths:
- ccache/
script: script:
- rm -fr build/* # remove anything in the build directory - rm -fr build/* # remove anything in the build directory
- export CCACHE_BASEDIR="$(pwd)"
- export CCACHE_DIR="$(pwd)/ccache"
- mkdir -pv "${CCACHE_DIR}"
- ccache -z -M "${CCACHE_SIZE}"
- CI/before_install.osx.sh - CI/before_install.osx.sh
- CI/before_script.osx.sh - CI/before_script.osx.sh
- cd build; make -j2 package - cd build; make -j $(sysctl -n hw.logicalcpu) package
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done
- ccache -s
artifacts: artifacts:
paths: paths:
- build/OpenMW-*.dmg - build/OpenMW-*.dmg
- "build/**/*.log" - "build/**/*.log"
macOS11_Xcode12:
extends: .MacOS
image: macos-11-xcode-12
cache:
key: macOS11_Xcode12.v1
variables:
CCACHE_SIZE: 3G
macOS10.15_Xcode11:
extends: .MacOS
image: macos-10.15-xcode-11
cache:
key: macOS10.15_Xcode11.v1
variables:
CCACHE_SIZE: 3G
variables: &engine-targets variables: &engine-targets
targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard"
package: "Engine" package: "Engine"

@ -18,7 +18,7 @@ addons:
- ubuntu-toolchain-r-test - ubuntu-toolchain-r-test
packages: [ packages: [
# Dev # Dev
cmake, clang-tools, gcc-8, g++-8, ccache, cmake, clang-tools-7, gcc-8, g++-8, ccache,
# Boost # Boost
libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev, libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev,
# FFmpeg # FFmpeg

@ -4,7 +4,7 @@ Contributors
The OpenMW project was started in 2008 by Nicolay Korslund. The OpenMW project was started in 2008 by Nicolay Korslund.
In the course of years many people have contributed to the project. In the course of years many people have contributed to the project.
If you feel your name is missing from this list, please notify a developer. If you feel your name is missing from this list, please add it to `AUTHORS.md`.
Programmers Programmers
@ -24,7 +24,7 @@ Programmers
Alex McKibben Alex McKibben
alexanderkjall alexanderkjall
Alexander Nadeau (wareya) Alexander Nadeau (wareya)
Alexander Olofsson (Ace) Alexander Olofsson (Ananace)
Alex Rice Alex Rice
Alex S (docwest) Alex S (docwest)
Allofich Allofich
@ -131,6 +131,7 @@ Programmers
Martin Otto (MAtahualpa) Martin Otto (MAtahualpa)
Mateusz Kołaczek (PL_kolek) Mateusz Kołaczek (PL_kolek)
Mateusz Malisz (malice) Mateusz Malisz (malice)
Max Henzerling (SaintMercury)
megaton megaton
Michael Hogan (Xethik) Michael Hogan (Xethik)
Michael Mc Donnell Michael Mc Donnell
@ -183,6 +184,7 @@ Programmers
sergoz sergoz
ShadowRadiance ShadowRadiance
Siimacore Siimacore
Simon Meulenbeek (simonmb)
sir_herrbatka sir_herrbatka
smbas smbas
Sophie Kirschner (pineapplemachine) Sophie Kirschner (pineapplemachine)
@ -196,6 +198,7 @@ Programmers
Sylvain Thesnieres (Garvek) Sylvain Thesnieres (Garvek)
t6 t6
terrorfisch terrorfisch
Tess (tescoShoppah)
thegriglat thegriglat
Thomas Luppi (Digmaster) Thomas Luppi (Digmaster)
tlmullis tlmullis
@ -234,7 +237,8 @@ Documentation
Packagers Packagers
--------- ---------
Alexander Olofsson (Ace) - Windows Alexander Olofsson (Ananace) - Windows and Flatpak
Alexey Sokolov (DarthGandalf) - Gentoo Linux
Bret Curtis (psi29a) - Debian and Ubuntu Linux Bret Curtis (psi29a) - Debian and Ubuntu Linux
Edmondo Tommasina (edmondo) - Gentoo Linux Edmondo Tommasina (edmondo) - Gentoo Linux
Julian Ospald (hasufell) - Gentoo Linux Julian Ospald (hasufell) - Gentoo Linux

@ -57,6 +57,7 @@
Bug #5424: Creatures do not headtrack player Bug #5424: Creatures do not headtrack player
Bug #5425: Poison effect only appears for one frame Bug #5425: Poison effect only appears for one frame
Bug #5427: GetDistance unknown ID error is misleading Bug #5427: GetDistance unknown ID error is misleading
Bug #5431: Physics performance degradation after a specific number of actors on a scene
Bug #5435: Enemies can't hurt the player when collision is off Bug #5435: Enemies can't hurt the player when collision is off
Bug #5441: Enemies can't push a player character when in critical strike stance Bug #5441: Enemies can't push a player character when in critical strike stance
Bug #5451: Magic projectiles don't disappear with the caster Bug #5451: Magic projectiles don't disappear with the caster
@ -87,6 +88,7 @@
Bug #5656: Sneaking characters block hits while standing Bug #5656: Sneaking characters block hits while standing
Bug #5661: Region sounds don't play at the right interval Bug #5661: Region sounds don't play at the right interval
Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx
Bug #5680: Bull Netches incorrectly aim over the player character's head and always miss
Bug #5681: Player character can clip or pass through bridges instead of colliding against them Bug #5681: Player character can clip or pass through bridges instead of colliding against them
Bug #5687: Bound items covering the same inventory slot expiring at the same time freezes the game Bug #5687: Bound items covering the same inventory slot expiring at the same time freezes the game
Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5688: Water shader broken indoors with enable indoor shadows = false
@ -111,6 +113,10 @@
Bug #5899: Visible modal windows and dropdowns crashing game on exit Bug #5899: Visible modal windows and dropdowns crashing game on exit
Bug #5902: NiZBufferProperty is unable to disable the depth test Bug #5902: NiZBufferProperty is unable to disable the depth test
Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs
Bug #5912: ImprovedBound mod doesn't work
Bug #5914: BM: The Swimmer can't reach destination
Bug #5923: Clicking on empty spaces between journal entries might show random topics
Bug #5934: AddItem command doesn't accept negative values
Feature #390: 3rd person look "over the shoulder" Feature #390: 3rd person look "over the shoulder"
Feature #832: OpenMW-CS: Handle deleted references Feature #832: OpenMW-CS: Handle deleted references
Feature #1536: Show more information about level on menu Feature #1536: Show more information about level on menu
@ -118,6 +124,7 @@
Feature #2404: Levelled List can not be placed into a container Feature #2404: Levelled List can not be placed into a container
Feature #2686: Timestamps in openmw.log Feature #2686: Timestamps in openmw.log
Feature #3171: OpenMW-CS: Instance drag selection Feature #3171: OpenMW-CS: Instance drag selection
Feature #3983: Wizard: Add link to buy Morrowind
Feature #4894: Consider actors as obstacles for pathfinding Feature #4894: Consider actors as obstacles for pathfinding
Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing
Feature #4977: Use the "default icon.tga" when an item's icon is not found Feature #4977: Use the "default icon.tga" when an item's icon is not found
@ -129,6 +136,7 @@
Feature #5456: Basic collada animation support Feature #5456: Basic collada animation support
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 #5511: Add in game option to toggle HRTF support in OpenMW
Feature #5519: Code Patch tab in launcher Feature #5519: Code Patch tab in launcher
Feature #5524: Resume failed script execution after reload Feature #5524: Resume failed script execution after reload
Feature #5545: Option to allow stealing from an unconscious NPC during combat Feature #5545: Option to allow stealing from an unconscious NPC during combat
@ -143,6 +151,8 @@
Feature #5730: Add graphic herbalism option to the launcher and documents Feature #5730: Add graphic herbalism option to the launcher and documents
Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used.
Feature #5813: Instanced groundcover support Feature #5813: Instanced groundcover support
Feature #5814: Bsatool should be able to create BSA archives, not only to extract it
Feature #5910: Fall back to delta time when physics can't keep up
Task #5480: Drop Qt4 support Task #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation Task #5520: Improve cell name autocompleter implementation
Task #5844: Update 'toggle sneak' documentation Task #5844: Update 'toggle sneak' documentation

@ -848,9 +848,11 @@ fi
wrappedExit 1 wrappedExit 1
fi fi
if ! [ -e "aqt-venv/${VENV_BIN_DIR}/aqt" ]; then # check version
aqt-venv/${VENV_BIN_DIR}/pip list | grep 'aqtinstall\s*1.1.3' || [ $? -ne 0 ]
if [ $? -eq 0 ]; then
echo " Installing aqt wheel into virtualenv..." echo " Installing aqt wheel into virtualenv..."
run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==0.9.2 run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3
fi fi
popd > /dev/null popd > /dev/null

@ -16,7 +16,7 @@ cmake \
-D CMAKE_CXX_FLAGS="-stdlib=libc++" \ -D CMAKE_CXX_FLAGS="-stdlib=libc++" \
-D CMAKE_C_FLAGS_RELEASE="-g -O0" \ -D CMAKE_C_FLAGS_RELEASE="-g -O0" \
-D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \ -D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.12" \ -D CMAKE_OSX_DEPLOYMENT_TARGET="10.14" \
-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 \

@ -28,6 +28,8 @@ declare -rA GROUPED_DEPS=(
# These dependencies can alternatively be built and linked statically. # These dependencies can alternatively be built and linked statically.
[openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev" [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev"
[coverity]="curl"
# Pre-requisites for building MyGUI and OSG for static linking. # Pre-requisites for building MyGUI and OSG for static linking.
# #
# * MyGUI and OSG: libsdl2-dev liblz4-dev libfreetype6-dev # * MyGUI and OSG: libsdl2-dev liblz4-dev libfreetype6-dev

@ -20,6 +20,7 @@ struct Arguments
std::string mode; std::string mode;
std::string filename; std::string filename;
std::string extractfile; std::string extractfile;
std::string addfile;
std::string outdir; std::string outdir;
bool longformat; bool longformat;
@ -36,6 +37,10 @@ bool parseOptions (int argc, char** argv, Arguments &info)
" Extract a file from the input archive.\n\n" " Extract a file from the input archive.\n\n"
" bsatool extractall archivefile [output_directory]\n" " bsatool extractall archivefile [output_directory]\n"
" Extract all files from the input archive.\n\n" " Extract all files from the input archive.\n\n"
" bsatool add [-a] archivefile file_to_add\n"
" Add a file to the input archive.\n\n"
" bsatool create [-c] archivefile\n"
" Create an archive.\n\n"
"Allowed options"); "Allowed options");
desc.add_options() desc.add_options()
@ -95,7 +100,7 @@ bool parseOptions (int argc, char** argv, Arguments &info)
} }
info.mode = variables["mode"].as<std::string>(); info.mode = variables["mode"].as<std::string>();
if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall")) if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall" || info.mode == "add" || info.mode == "create"))
{ {
std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n" std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n"
<< desc << std::endl; << desc << std::endl;
@ -126,6 +131,17 @@ bool parseOptions (int argc, char** argv, Arguments &info)
if (variables["input-file"].as< std::vector<std::string> >().size() > 2) if (variables["input-file"].as< std::vector<std::string> >().size() > 2)
info.outdir = variables["input-file"].as< std::vector<std::string> >()[2]; info.outdir = variables["input-file"].as< std::vector<std::string> >()[2];
} }
else if (info.mode == "add")
{
if (variables["input-file"].as< std::vector<std::string> >().size() < 1)
{
std::cout << "\nERROR: file to add unspecified\n\n"
<< desc << std::endl;
return false;
}
if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
info.addfile = variables["input-file"].as< std::vector<std::string> >()[1];
}
else if (variables["input-file"].as< std::vector<std::string> >().size() > 1) else if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
info.outdir = variables["input-file"].as< std::vector<std::string> >()[1]; info.outdir = variables["input-file"].as< std::vector<std::string> >()[1];
@ -138,6 +154,7 @@ bool parseOptions (int argc, char** argv, Arguments &info)
int list(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info); int list(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
int extract(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info); int extract(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info); int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
int add(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
@ -157,6 +174,12 @@ int main(int argc, char** argv)
else else
bsa = std::make_unique<Bsa::BSAFile>(Bsa::BSAFile()); bsa = std::make_unique<Bsa::BSAFile>(Bsa::BSAFile());
if (info.mode == "create")
{
bsa->open(info.filename);
return 0;
}
bsa->open(info.filename); bsa->open(info.filename);
if (info.mode == "list") if (info.mode == "list")
@ -165,6 +188,8 @@ int main(int argc, char** argv)
return extract(bsa, info); return extract(bsa, info);
else if (info.mode == "extractall") else if (info.mode == "extractall")
return extractAll(bsa, info); return extractAll(bsa, info);
else if (info.mode == "add")
return add(bsa, info);
else else
{ {
std::cout << "Unsupported mode. That is not supposed to happen." << std::endl; std::cout << "Unsupported mode. That is not supposed to happen." << std::endl;
@ -188,13 +213,13 @@ int list(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
{ {
// Long format // Long format
std::ios::fmtflags f(std::cout.flags()); std::ios::fmtflags f(std::cout.flags());
std::cout << std::setw(50) << std::left << file.name; std::cout << std::setw(50) << std::left << file.name();
std::cout << std::setw(8) << std::left << std::dec << file.fileSize; std::cout << std::setw(8) << std::left << std::dec << file.fileSize;
std::cout << "@ 0x" << std::hex << file.offset << std::endl; std::cout << "@ 0x" << std::hex << file.offset << std::endl;
std::cout.flags(f); std::cout.flags(f);
} }
else else
std::cout << file.name << std::endl; std::cout << file.name() << std::endl;
} }
return 0; return 0;
@ -253,7 +278,7 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
{ {
for (const auto &file : bsa->getList()) for (const auto &file : bsa->getList())
{ {
std::string extractPath(file.name); std::string extractPath(file.name());
Misc::StringUtils::replaceAll(extractPath, "\\", "/"); Misc::StringUtils::replaceAll(extractPath, "\\", "/");
// Get the target path (the path the file will be extracted to) // Get the target path (the path the file will be extracted to)
@ -272,7 +297,7 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
// Get a stream for the file to extract // Get a stream for the file to extract
// (inefficient because getFile iter on the list again) // (inefficient because getFile iter on the list again)
Files::IStreamPtr data = bsa->getFile(file.name); Files::IStreamPtr data = bsa->getFile(file.name());
bfs::ofstream out(target, std::ios::binary); bfs::ofstream out(target, std::ios::binary);
// Write the file to disk // Write the file to disk
@ -283,3 +308,11 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
return 0; return 0;
} }
int add(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
{
boost::filesystem::fstream stream(info.addfile, std::ios_base::binary | std::ios_base::out | std::ios_base::in);
bsa->addFile(info.addfile, stream);
return 0;
}

@ -13,6 +13,7 @@ set(LAUNCHER
utils/profilescombobox.cpp utils/profilescombobox.cpp
utils/textinputdialog.cpp utils/textinputdialog.cpp
utils/lineedit.cpp utils/lineedit.cpp
utils/openalutil.cpp
${CMAKE_SOURCE_DIR}/files/windows/launcher.rc ${CMAKE_SOURCE_DIR}/files/windows/launcher.rc
) )
@ -31,6 +32,7 @@ set(LAUNCHER_HEADER
utils/profilescombobox.hpp utils/profilescombobox.hpp
utils/textinputdialog.hpp utils/textinputdialog.hpp
utils/lineedit.hpp utils/lineedit.hpp
utils/openalutil.hpp
) )
# Headers that must be pre-processed # Headers that must be pre-processed
@ -47,6 +49,7 @@ set(LAUNCHER_HEADER_MOC
utils/textinputdialog.hpp utils/textinputdialog.hpp
utils/profilescombobox.hpp utils/profilescombobox.hpp
utils/lineedit.hpp utils/lineedit.hpp
utils/openalutil.hpp
) )
@ -95,6 +98,7 @@ endif (WIN32)
target_link_libraries(openmw-launcher target_link_libraries(openmw-launcher
${SDL2_LIBRARY_ONLY} ${SDL2_LIBRARY_ONLY}
${OPENAL_LIBRARY}
components components
) )

@ -5,11 +5,14 @@
#include <QFileDialog> #include <QFileDialog>
#include <QCompleter> #include <QCompleter>
#include <QProxyStyle> #include <QProxyStyle>
#include <QString>
#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> #include <cmath>
#include "utils/openalutil.hpp"
Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
Settings::Manager &engineSettings, QWidget *parent) Settings::Manager &engineSettings, QWidget *parent)
: QWidget(parent) : QWidget(parent)
@ -19,7 +22,17 @@ Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
setObjectName ("AdvancedPage"); setObjectName ("AdvancedPage");
setupUi(this); setupUi(this);
for(const char * name : Launcher::enumerateOpenALDevices())
{
audioDeviceSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name));
}
for(const char * name : Launcher::enumerateOpenALDevicesHrtf())
{
hrtfProfileSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name));
}
loadSettings(); loadSettings();
mCellNameCompleter.setModel(&mCellNameCompleterModel); mCellNameCompleter.setModel(&mCellNameCompleterModel);
startDefaultCharacterAtField->setCompleter(&mCellNameCompleter); startDefaultCharacterAtField->setCompleter(&mCellNameCompleter);
} }
@ -95,6 +108,7 @@ bool Launcher::AdvancedPage::loadSettings()
int numPhysicsThreads = mEngineSettings.getInt("async num threads", "Physics"); int numPhysicsThreads = mEngineSettings.getInt("async num threads", "Physics");
if (numPhysicsThreads >= 0) if (numPhysicsThreads >= 0)
physicsThreadsSpinBox->setValue(numPhysicsThreads); physicsThreadsSpinBox->setValue(numPhysicsThreads);
loadSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game");
} }
// Visuals // Visuals
@ -126,6 +140,34 @@ bool Launcher::AdvancedPage::loadSettings()
viewingDistanceComboBox->setValue(convertToCells(mEngineSettings.getInt("viewing distance", "Camera"))); viewingDistanceComboBox->setValue(convertToCells(mEngineSettings.getInt("viewing distance", "Camera")));
} }
// Audio
{
std::string selectedAudioDevice = mEngineSettings.getString("device", "Sound");
if (selectedAudioDevice.empty() == false)
{
int audioDeviceIndex = audioDeviceSelectorComboBox->findData(QString::fromStdString(selectedAudioDevice));
if (audioDeviceIndex != -1)
{
audioDeviceSelectorComboBox->setCurrentIndex(audioDeviceIndex);
}
}
int hrtfEnabledIndex = mEngineSettings.getInt("hrtf enable", "Sound");
if (hrtfEnabledIndex >= -1 && hrtfEnabledIndex <= 1)
{
enableHRTFComboBox->setCurrentIndex(hrtfEnabledIndex + 1);
}
std::string selectedHRTFProfile = mEngineSettings.getString("hrtf", "Sound");
if (selectedHRTFProfile.empty() == false)
{
int hrtfProfileIndex = hrtfProfileSelectorComboBox->findData(QString::fromStdString(selectedHRTFProfile));
if (hrtfProfileIndex != -1)
{
hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex);
}
}
}
// Camera // Camera
{ {
loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera"); loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
@ -248,6 +290,33 @@ void Launcher::AdvancedPage::saveSettings()
} }
} }
// Audio
{
int audioDeviceIndex = audioDeviceSelectorComboBox->currentIndex();
if (audioDeviceIndex != 0)
{
mEngineSettings.setString("device", "Sound", audioDeviceSelectorComboBox->currentText().toUtf8().constData());
}
else
{
mEngineSettings.setString("device", "Sound", "");
}
int hrtfEnabledIndex = enableHRTFComboBox->currentIndex() - 1;
if (hrtfEnabledIndex != mEngineSettings.getInt("hrtf enable", "Sound"))
{
mEngineSettings.setInt("hrtf enable", "Sound", hrtfEnabledIndex);
}
int selectedHRTFProfileIndex = hrtfProfileSelectorComboBox->currentIndex();
if (selectedHRTFProfileIndex != 0)
{
mEngineSettings.setString("hrtf", "Sound", hrtfProfileSelectorComboBox->currentText().toUtf8().constData());
}
else
{
mEngineSettings.setString("hrtf", "Sound", "");
}
}
// Camera // Camera
{ {
saveSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera"); saveSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");

@ -20,6 +20,9 @@
QString getAspect(int x, int y) QString getAspect(int x, int y)
{ {
int gcd = std::gcd (x, y); int gcd = std::gcd (x, y);
if (gcd == 0)
return QString();
int xaspect = x / gcd; int xaspect = x / gcd;
int yaspect = y / gcd; int yaspect = y / gcd;
// special case: 8 : 5 is usually referred to as 16:10 // special case: 8 : 5 is usually referred to as 16:10
@ -298,9 +301,9 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
return result; return result;
} }
QString aspect = getAspect(mode.w, mode.h);
QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(mode.h); QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(mode.h);
QString aspect = getAspect(mode.w, mode.h);
if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) { if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) {
resolution.append(tr("\t(Wide ") + aspect + ")"); resolution.append(tr("\t(Wide ") + aspect + ")");

@ -0,0 +1,55 @@
#include <cstring>
#include <vector>
#include <memory>
#include <apps/openmw/mwsound/alext.h>
#include "openalutil.hpp"
#ifndef ALC_ALL_DEVICES_SPECIFIER
#define ALC_ALL_DEVICES_SPECIFIER 0x1013
#endif
std::vector<const char *> Launcher::enumerateOpenALDevices()
{
std::vector<const char *> devlist;
const ALCchar *devnames;
if(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT"))
{
devnames = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
}
else
{
devnames = alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
}
while(devnames && *devnames)
{
devlist.emplace_back(devnames);
devnames += strlen(devnames)+1;
}
return devlist;
}
std::vector<const char *> Launcher::enumerateOpenALDevicesHrtf()
{
std::vector<const char *> ret;
ALCdevice *device = alcOpenDevice(nullptr);
if(device && alcIsExtensionPresent(device, "ALC_SOFT_HRTF"))
{
LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr;
void* funcPtr = alcGetProcAddress(device, "alcGetStringiSOFT");
memcpy(&alcGetStringiSOFT, &funcPtr, sizeof(funcPtr));
ALCint num_hrtf;
alcGetIntegerv(device, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf);
ret.reserve(num_hrtf);
for(ALCint i = 0;i < num_hrtf && i < 20;++i)
{
const ALCchar *entry = alcGetStringiSOFT(device, ALC_HRTF_SPECIFIER_SOFT, i);
ret.emplace_back(entry);
}
}
return ret;
}

@ -0,0 +1,7 @@
#include <vector>
namespace Launcher
{
std::vector<const char *> enumerateOpenALDevices();
std::vector<const char *> enumerateOpenALDevicesHrtf();
}

@ -34,7 +34,7 @@ namespace CSMPrefs
void storeValue(const QKeySequence& sequence); void storeValue(const QKeySequence& sequence);
void resetState(); void resetState();
static const int MaxKeys = 4; static constexpr int MaxKeys = 4;
QPushButton* mButton; QPushButton* mButton;

@ -56,7 +56,9 @@ void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData
CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns) CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns)
: InventoryColumns (columns) {} : InventoryColumns (columns)
, mEffects(nullptr)
{}
CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns) CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns)
: InventoryRefIdAdapter<ESM::Ingredient> (UniversalId::Type_Ingredient, columns), : InventoryRefIdAdapter<ESM::Ingredient> (UniversalId::Type_Ingredient, columns),
@ -585,7 +587,13 @@ void CSMWorld::DoorRefIdAdapter::setData (const RefIdColumn *column, RefIdData&
} }
CSMWorld::LightColumns::LightColumns (const InventoryColumns& columns) CSMWorld::LightColumns::LightColumns (const InventoryColumns& columns)
: InventoryColumns (columns) {} : InventoryColumns (columns)
, mTime(nullptr)
, mRadius(nullptr)
, mColor(nullptr)
, mSound(nullptr)
, mEmitterType(nullptr)
{}
CSMWorld::LightRefIdAdapter::LightRefIdAdapter (const LightColumns& columns) CSMWorld::LightRefIdAdapter::LightRefIdAdapter (const LightColumns& columns)
: InventoryRefIdAdapter<ESM::Light> (UniversalId::Type_Light, columns), mColumns (columns) : InventoryRefIdAdapter<ESM::Light> (UniversalId::Type_Light, columns), mColumns (columns)
@ -1454,7 +1462,15 @@ int CSMWorld::CreatureMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *co
} }
CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns) CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns)
: EnchantableColumns (columns) {} : EnchantableColumns (columns)
, mType(nullptr)
, mHealth(nullptr)
, mSpeed(nullptr)
, mReach(nullptr)
, mChop{nullptr}
, mSlash{nullptr}
, mThrust{nullptr}
{}
CSMWorld::WeaponRefIdAdapter::WeaponRefIdAdapter (const WeaponColumns& columns) CSMWorld::WeaponRefIdAdapter::WeaponRefIdAdapter (const WeaponColumns& columns)
: EnchantableRefIdAdapter<ESM::Weapon> (UniversalId::Type_Weapon, columns), mColumns (columns) : EnchantableRefIdAdapter<ESM::Weapon> (UniversalId::Type_Weapon, columns), mColumns (columns)

@ -178,7 +178,11 @@ namespace CSMWorld
const RefIdColumn *mName; const RefIdColumn *mName;
const RefIdColumn *mScript; const RefIdColumn *mScript;
NameColumns (const ModelColumns& base) : ModelColumns (base) {} NameColumns (const ModelColumns& base)
: ModelColumns (base)
, mName(nullptr)
, mScript(nullptr)
{}
}; };
/// \brief Adapter for IDs with names (all but levelled lists and statics) /// \brief Adapter for IDs with names (all but levelled lists and statics)
@ -247,7 +251,12 @@ namespace CSMWorld
const RefIdColumn *mWeight; const RefIdColumn *mWeight;
const RefIdColumn *mValue; const RefIdColumn *mValue;
InventoryColumns (const NameColumns& base) : NameColumns (base) {} InventoryColumns (const NameColumns& base)
: NameColumns (base)
, mIcon(nullptr)
, mWeight(nullptr)
, mValue(nullptr)
{}
}; };
/// \brief Adapter for IDs that can go into an inventory /// \brief Adapter for IDs that can go into an inventory
@ -405,7 +414,11 @@ namespace CSMWorld
const RefIdColumn *mEnchantment; const RefIdColumn *mEnchantment;
const RefIdColumn *mEnchantmentPoints; const RefIdColumn *mEnchantmentPoints;
EnchantableColumns (const InventoryColumns& base) : InventoryColumns (base) {} EnchantableColumns (const InventoryColumns& base)
: InventoryColumns (base)
, mEnchantment(nullptr)
, mEnchantmentPoints(nullptr)
{}
}; };
/// \brief Adapter for enchantable IDs /// \brief Adapter for enchantable IDs
@ -474,7 +487,11 @@ namespace CSMWorld
const RefIdColumn *mQuality; const RefIdColumn *mQuality;
const RefIdColumn *mUses; const RefIdColumn *mUses;
ToolColumns (const InventoryColumns& base) : InventoryColumns (base) {} ToolColumns (const InventoryColumns& base)
: InventoryColumns (base)
, mQuality(nullptr)
, mUses(nullptr)
{}
}; };
/// \brief Adapter for tools with limited uses IDs (lockpick, repair, probes) /// \brief Adapter for tools with limited uses IDs (lockpick, repair, probes)
@ -549,7 +566,17 @@ namespace CSMWorld
const RefIdColumn *mAiPackages; const RefIdColumn *mAiPackages;
std::map<const RefIdColumn *, unsigned int> mServices; std::map<const RefIdColumn *, unsigned int> mServices;
ActorColumns (const NameColumns& base) : NameColumns (base) {} ActorColumns (const NameColumns& base)
: NameColumns (base)
, mHello(nullptr)
, mFlee(nullptr)
, mFight(nullptr)
, mAlarm(nullptr)
, mInventory(nullptr)
, mSpells(nullptr)
, mDestinations(nullptr)
, mAiPackages(nullptr)
{}
}; };
/// \brief Adapter for actor IDs (handles common AI functionality) /// \brief Adapter for actor IDs (handles common AI functionality)
@ -2054,7 +2081,11 @@ namespace CSMWorld
const RefIdColumn *mLevList; const RefIdColumn *mLevList;
const RefIdColumn *mNestedListLevList; const RefIdColumn *mNestedListLevList;
LevListColumns (const BaseColumns& base) : BaseColumns (base) {} LevListColumns (const BaseColumns& base)
: BaseColumns (base)
, mLevList(nullptr)
, mNestedListLevList(nullptr)
{}
}; };
template<typename RecordT> template<typename RecordT>

@ -33,26 +33,6 @@ namespace Compiler
class Context; class Context;
} }
namespace MWScript
{
class ScriptManager;
}
namespace MWSound
{
class SoundManager;
}
namespace MWWorld
{
class World;
}
namespace MWGui
{
class WindowManager;
}
namespace Files namespace Files
{ {
struct ConfigurationManager; struct ConfigurationManager;

@ -32,7 +32,6 @@ namespace MyGUI
namespace ESM namespace ESM
{ {
struct Class;
class ESMReader; class ESMReader;
class ESMWriter; class ESMWriter;
struct CellId; struct CellId;

@ -401,13 +401,13 @@ namespace MWBase
virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0; virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0;
virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool moveToActive=false) = 0; virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false) = 0;
///< @return an updated Ptr in case the Ptr's cell changes ///< @return an updated Ptr in case the Ptr's cell changes
virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0;
///< @return an updated Ptr ///< @return an updated Ptr
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec) = 0; virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive) = 0;
///< @return an updated Ptr ///< @return an updated Ptr
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
@ -587,6 +587,8 @@ namespace MWBase
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0; virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
virtual void disableDeferredPreviewRotation() = 0; virtual void disableDeferredPreviewRotation() = 0;
virtual void saveLoaded() = 0;
virtual void setupPlayer() = 0; virtual void setupPlayer() = 0;
virtual void renderPlayer() = 0; virtual void renderPlayer() = 0;
@ -799,7 +801,7 @@ namespace MWBase
/// Return a vector aiming the actor's weapon towards a target. /// Return a vector aiming the actor's weapon towards a target.
/// @note The length of the vector is the distance between actor and target. /// @note The length of the vector is the distance between actor and target.
virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target, bool isRangedCombat) = 0;
/// Return the distance between actor's weapon and target's collision box. /// Return the distance between actor's weapon and target's collision box.
virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0;

@ -58,11 +58,6 @@ namespace MWClass
mStore.readState(inventory); mStore.readState(inventory);
} }
MWWorld::CustomData *ContainerCustomData::clone() const
{
return new ContainerCustomData (*this);
}
ContainerCustomData& ContainerCustomData::asContainerCustomData() ContainerCustomData& ContainerCustomData::asContainerCustomData()
{ {
return *this; return *this;
@ -84,7 +79,7 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>(); MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
// store // store
ptr.getRefData().setCustomData (std::make_unique<ContainerCustomData>(*ref->mBase, ptr.getCell()).release()); ptr.getRefData().setCustomData (std::make_unique<ContainerCustomData>(*ref->mBase, ptr.getCell()));
MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell()); MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell());
} }
@ -381,7 +376,7 @@ namespace MWClass
return; return;
const ESM::ContainerState& containerState = state.asContainerState(); const ESM::ContainerState& containerState = state.asContainerState();
ptr.getRefData().setCustomData(std::make_unique<ContainerCustomData>(containerState.mInventory).release()); ptr.getRefData().setCustomData(std::make_unique<ContainerCustomData>(containerState.mInventory));
} }
void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const

@ -13,15 +13,13 @@ namespace ESM
namespace MWClass namespace MWClass
{ {
class ContainerCustomData : public MWWorld::CustomData class ContainerCustomData : public MWWorld::TypedCustomData<ContainerCustomData>
{ {
MWWorld::ContainerStore mStore; MWWorld::ContainerStore mStore;
public: public:
ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell); ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell);
ContainerCustomData(const ESM::InventoryState& inventory); ContainerCustomData(const ESM::InventoryState& inventory);
MWWorld::CustomData *clone() const override;
ContainerCustomData& asContainerCustomData() override; ContainerCustomData& asContainerCustomData() override;
const ContainerCustomData& asContainerCustomData() const override; const ContainerCustomData& asContainerCustomData() const override;

@ -68,14 +68,16 @@ namespace
namespace MWClass namespace MWClass
{ {
class CreatureCustomData : public MWWorld::CustomData class CreatureCustomData : public MWWorld::TypedCustomData<CreatureCustomData>
{ {
public: public:
MWMechanics::CreatureStats mCreatureStats; MWMechanics::CreatureStats mCreatureStats;
MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures std::unique_ptr<MWWorld::ContainerStore> mContainerStore; // may be InventoryStore for some creatures
MWMechanics::Movement mMovement; MWMechanics::Movement mMovement;
MWWorld::CustomData *clone() const override; CreatureCustomData() = default;
CreatureCustomData(const CreatureCustomData& other);
CreatureCustomData(CreatureCustomData&& other) noexcept = default;
CreatureCustomData& asCreatureCustomData() override CreatureCustomData& asCreatureCustomData() override
{ {
@ -85,16 +87,13 @@ namespace MWClass
{ {
return *this; return *this;
} }
CreatureCustomData() : mContainerStore(nullptr) {}
virtual ~CreatureCustomData() { delete mContainerStore; }
}; };
MWWorld::CustomData *CreatureCustomData::clone() const CreatureCustomData::CreatureCustomData(const CreatureCustomData& other)
: mCreatureStats(other.mCreatureStats),
mContainerStore(other.mContainerStore->clone()),
mMovement(other.mMovement)
{ {
CreatureCustomData* cloned = new CreatureCustomData (*this);
cloned->mContainerStore = mContainerStore->clone();
return cloned;
} }
const Creature::GMST& Creature::getGmst() const Creature::GMST& Creature::getGmst()
@ -165,16 +164,16 @@ namespace MWClass
// inventory // inventory
bool hasInventory = hasInventoryStore(ptr); bool hasInventory = hasInventoryStore(ptr);
if (hasInventory) if (hasInventory)
data->mContainerStore = new MWWorld::InventoryStore(); data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
else else
data->mContainerStore = new MWWorld::ContainerStore(); data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold); data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold);
data->mCreatureStats.setNeedRecalcDynamicStats(false); data->mCreatureStats.setNeedRecalcDynamicStats(false);
// store // store
ptr.getRefData().setCustomData(data.release()); ptr.getRefData().setCustomData(std::move(data));
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
@ -946,11 +945,11 @@ namespace MWClass
std::unique_ptr<CreatureCustomData> data (new CreatureCustomData); std::unique_ptr<CreatureCustomData> data (new CreatureCustomData);
if (hasInventoryStore(ptr)) if (hasInventoryStore(ptr))
data->mContainerStore = new MWWorld::InventoryStore(); data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
else else
data->mContainerStore = new MWWorld::ContainerStore(); data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
ptr.getRefData().setCustomData (data.release()); ptr.getRefData().setCustomData (std::move(data));
} }
} }
else else

@ -10,15 +10,13 @@
namespace MWClass namespace MWClass
{ {
class CreatureLevListCustomData : public MWWorld::CustomData class CreatureLevListCustomData : public MWWorld::TypedCustomData<CreatureLevListCustomData>
{ {
public: public:
// actorId of the creature we spawned // actorId of the creature we spawned
int mSpawnActorId; int mSpawnActorId;
bool mSpawn; // Should a new creature be spawned? bool mSpawn; // Should a new creature be spawned?
MWWorld::CustomData *clone() const override;
CreatureLevListCustomData& asCreatureLevListCustomData() override CreatureLevListCustomData& asCreatureLevListCustomData() override
{ {
return *this; return *this;
@ -29,11 +27,6 @@ namespace MWClass
} }
}; };
MWWorld::CustomData *CreatureLevListCustomData::clone() const
{
return new CreatureLevListCustomData (*this);
}
std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const
{ {
return ""; return "";
@ -149,11 +142,11 @@ namespace MWClass
{ {
if (!ptr.getRefData().getCustomData()) if (!ptr.getRefData().getCustomData())
{ {
std::unique_ptr<CreatureLevListCustomData> data (new CreatureLevListCustomData); std::unique_ptr<CreatureLevListCustomData> data = std::make_unique<CreatureLevListCustomData>();
data->mSpawnActorId = -1; data->mSpawnActorId = -1;
data->mSpawn = true; data->mSpawn = true;
ptr.getRefData().setCustomData(data.release()); ptr.getRefData().setCustomData(std::move(data));
} }
} }

@ -43,13 +43,11 @@
namespace MWClass namespace MWClass
{ {
class DoorCustomData : public MWWorld::CustomData class DoorCustomData : public MWWorld::TypedCustomData<DoorCustomData>
{ {
public: public:
MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle; MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle;
MWWorld::CustomData *clone() const override;
DoorCustomData& asDoorCustomData() override DoorCustomData& asDoorCustomData() override
{ {
return *this; return *this;
@ -60,11 +58,6 @@ namespace MWClass
} }
}; };
MWWorld::CustomData *DoorCustomData::clone() const
{
return new DoorCustomData (*this);
}
void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
{ {
if (!model.empty()) if (!model.empty())
@ -419,8 +412,7 @@ namespace MWClass
{ {
if (!ptr.getRefData().getCustomData()) if (!ptr.getRefData().getCustomData())
{ {
std::unique_ptr<DoorCustomData> data(new DoorCustomData); ptr.getRefData().setCustomData(std::make_unique<DoorCustomData>());
ptr.getRefData().setCustomData(data.release());
} }
} }

@ -263,15 +263,13 @@ namespace
namespace MWClass namespace MWClass
{ {
class NpcCustomData : public MWWorld::CustomData class NpcCustomData : public MWWorld::TypedCustomData<NpcCustomData>
{ {
public: public:
MWMechanics::NpcStats mNpcStats; MWMechanics::NpcStats mNpcStats;
MWMechanics::Movement mMovement; MWMechanics::Movement mMovement;
MWWorld::InventoryStore mInventoryStore; MWWorld::InventoryStore mInventoryStore;
MWWorld::CustomData *clone() const override;
NpcCustomData& asNpcCustomData() override NpcCustomData& asNpcCustomData() override
{ {
return *this; return *this;
@ -282,11 +280,6 @@ namespace MWClass
} }
}; };
MWWorld::CustomData *NpcCustomData::clone() const
{
return new NpcCustomData (*this);
}
const Npc::GMST& Npc::getGmst() const Npc::GMST& Npc::getGmst()
{ {
static GMST gmst; static GMST gmst;
@ -414,7 +407,7 @@ namespace MWClass
data->mNpcStats.setGoldPool(gold); data->mNpcStats.setGoldPool(gold);
// store // store
ptr.getRefData().setCustomData (data.release()); ptr.getRefData().setCustomData(std::move(data));
getInventoryStore(ptr).autoEquip(ptr); getInventoryStore(ptr).autoEquip(ptr);
} }
@ -1525,8 +1518,7 @@ namespace MWClass
if (!ptr.getRefData().getCustomData()) if (!ptr.getRefData().getCustomData())
{ {
// Create a CustomData, but don't fill it from ESM records (not needed) // Create a CustomData, but don't fill it from ESM records (not needed)
std::unique_ptr<NpcCustomData> data (new NpcCustomData); ptr.getRefData().setCustomData(std::make_unique<NpcCustomData>());
ptr.getRefData().setCustomData (data.release());
} }
} }
else else

@ -1,5 +1,7 @@
#include "bookpage.hpp" #include "bookpage.hpp"
#include <optional>
#include "MyGUI_RenderItem.h" #include "MyGUI_RenderItem.h"
#include "MyGUI_RenderManager.h" #include "MyGUI_RenderManager.h"
#include "MyGUI_TextureUtility.h" #include "MyGUI_TextureUtility.h"
@ -894,6 +896,27 @@ protected:
return mIsPageReset || (mPage != page); return mIsPageReset || (mPage != page);
} }
std::optional<MyGUI::IntPoint> getAdjustedPos(int left, int top, bool move = false)
{
if (!mBook)
return {};
if (mPage >= mBook->mPages.size())
return {};
MyGUI::IntPoint pos (left, top);
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
// work around inconsistency in MyGUI where the mouse press coordinates aren't
// transformed by the current Layer (even though mouse *move* events are).
if(!move)
pos = mNode->getLayer()->getPosition(left, top);
#endif
pos.left -= mCroppedParent->getAbsoluteLeft ();
pos.top -= mCroppedParent->getAbsoluteTop ();
pos.top += mViewTop;
return pos;
}
public: public:
typedef TypesetBookImpl::StyleImpl Style; typedef TypesetBookImpl::StyleImpl Style;
@ -952,16 +975,10 @@ public:
void onMouseMove (int left, int top) void onMouseMove (int left, int top)
{ {
if (!mBook) Style * hit = nullptr;
return; if(auto pos = getAdjustedPos(left, top, true))
if(pos->top <= mViewBottom)
if (mPage >= mBook->mPages.size()) hit = mBook->hitTestWithMargin (pos->left, pos->top);
return;
left -= mCroppedParent->getAbsoluteLeft ();
top -= mCroppedParent->getAbsoluteTop ();
Style * hit = mBook->hitTestWithMargin (left, mViewTop + top);
if (mLastDown == MyGUI::MouseButton::None) if (mLastDown == MyGUI::MouseButton::None)
{ {
@ -991,24 +1008,11 @@ public:
void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id)
{ {
if (!mBook) auto pos = getAdjustedPos(left, top);
return;
if (mPage >= mBook->mPages.size()) if (pos && mLastDown == MyGUI::MouseButton::None)
return;
// work around inconsistency in MyGUI where the mouse press coordinates aren't
// transformed by the current Layer (even though mouse *move* events are).
MyGUI::IntPoint pos (left, top);
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
pos = mNode->getLayer()->getPosition(left, top);
#endif
pos.left -= mCroppedParent->getAbsoluteLeft ();
pos.top -= mCroppedParent->getAbsoluteTop ();
if (mLastDown == MyGUI::MouseButton::None)
{ {
mFocusItem = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top); mFocusItem = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr;
mItemActive = true; mItemActive = true;
dirtyFocusItem (); dirtyFocusItem ();
@ -1019,25 +1023,11 @@ public:
void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id)
{ {
if (!mBook) auto pos = getAdjustedPos(left, top);
return;
if (mPage >= mBook->mPages.size())
return;
// work around inconsistency in MyGUI where the mouse release coordinates aren't
// transformed by the current Layer (even though mouse *move* events are).
MyGUI::IntPoint pos (left, top);
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
pos = mNode->getLayer()->getPosition(left, top);
#endif
pos.left -= mCroppedParent->getAbsoluteLeft ();
pos.top -= mCroppedParent->getAbsoluteTop ();
if (mLastDown == id) if (pos && mLastDown == id)
{ {
Style * item = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top); Style * item = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr;
bool clicked = mFocusItem == item; bool clicked = mFocusItem == item;

@ -6,11 +6,6 @@
#include "itemmodel.hpp" #include "itemmodel.hpp"
namespace MWWorld
{
class Environment;
}
namespace MyGUI namespace MyGUI
{ {
class Gui; class Gui;
@ -19,7 +14,6 @@ namespace MyGUI
namespace MWGui namespace MWGui
{ {
class WindowManager;
class ContainerWindow; class ContainerWindow;
class ItemView; class ItemView;
class SortFilterItemModel; class SortFilterItemModel;

@ -15,11 +15,6 @@ namespace Gui
class MWList; class MWList;
} }
namespace MWGui
{
class WindowManager;
}
namespace MWGui namespace MWGui
{ {
class ResponseCallback; class ResponseCallback;

@ -85,7 +85,7 @@ namespace MWGui
, mUpdateTimer(0.f) , mUpdateTimer(0.f)
{ {
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
if (uiScale > 1.0) if (uiScale > 0.f)
mScaleFactor = uiScale; mScaleFactor = uiScale;
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture()));

@ -7,11 +7,6 @@
#include <MyGUI_RenderManager.h> #include <MyGUI_RenderManager.h>
namespace MWGui
{
class WindowManager;
}
namespace MWRender namespace MWRender
{ {
class RaceSelectionPreview; class RaceSelectionPreview;

@ -11,11 +11,6 @@ namespace ESM
struct Spell; struct Spell;
} }
namespace MWGui
{
class WindowManager;
}
namespace MWGui namespace MWGui
{ {
class ReviewDialog : public WindowModal class ReviewDialog : public WindowModal

@ -61,6 +61,9 @@ namespace
std::string getAspect (int x, int y) std::string getAspect (int x, int y)
{ {
int gcd = std::gcd (x, y); int gcd = std::gcd (x, y);
if (gcd == 0)
return std::string();
int xaspect = x / gcd; int xaspect = x / gcd;
int yaspect = y / gcd; int yaspect = y / gcd;
// special case: 8 : 5 is usually referred to as 16:10 // special case: 8 : 5 is usually referred to as 16:10
@ -264,8 +267,10 @@ namespace MWGui
std::sort(resolutions.begin(), resolutions.end(), sortResolutions); std::sort(resolutions.begin(), resolutions.end(), sortResolutions);
for (std::pair<int, int>& resolution : resolutions) for (std::pair<int, int>& resolution : resolutions)
{ {
std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second) std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second);
+ " (" + getAspect(resolution.first, resolution.second) + ")"; std::string aspect = getAspect(resolution.first, resolution.second);
if (!aspect.empty())
str = str + " (" + aspect + ")";
if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE) if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE)
mResolutionList->addItem(str); mResolutionList->addItem(str);

@ -3,11 +3,6 @@
#include "windowbase.hpp" #include "windowbase.hpp"
namespace MWGui
{
class WindowManager;
}
namespace MWGui namespace MWGui
{ {
class SettingsWindow : public WindowBase class SettingsWindow : public WindowBase

@ -35,20 +35,20 @@ namespace MWGui
bool onDropItem(const MWWorld::Ptr &item, int count) override; bool onDropItem(const MWWorld::Ptr &item, int count) override;
bool onTakeItem(const MWWorld::Ptr &item, int count) override; bool onTakeItem(const MWWorld::Ptr &item, int count) override;
static const int Category_Weapon = (1<<1); static constexpr int Category_Weapon = (1<<1);
static const int Category_Apparel = (1<<2); static constexpr int Category_Apparel = (1<<2);
static const int Category_Misc = (1<<3); static constexpr int Category_Misc = (1<<3);
static const int Category_Magic = (1<<4); static constexpr int Category_Magic = (1<<4);
static const int Category_All = 255; static constexpr int Category_All = 255;
static const int Filter_OnlyIngredients = (1<<0); static constexpr int Filter_OnlyIngredients = (1<<0);
static const int Filter_OnlyEnchanted = (1<<1); static constexpr int Filter_OnlyEnchanted = (1<<1);
static const int Filter_OnlyEnchantable = (1<<2); static constexpr int Filter_OnlyEnchantable = (1<<2);
static const int Filter_OnlyChargedSoulstones = (1<<3); static constexpr int Filter_OnlyChargedSoulstones = (1<<3);
static const int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action static constexpr int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action
static const int Filter_OnlyRepairable = (1<<5); static constexpr int Filter_OnlyRepairable = (1<<5);
static const int Filter_OnlyRechargable = (1<<6); static constexpr int Filter_OnlyRechargable = (1<<6);
static const int Filter_OnlyRepairTools = (1<<7); static constexpr int Filter_OnlyRepairTools = (1<<7);
private: private:

@ -15,11 +15,6 @@ namespace MyGUI
class Widget; class Widget;
} }
namespace MWGui
{
class WindowManager;
}
namespace MWGui namespace MWGui
{ {
class SpellBuyingWindow : public ReferenceInterface, public WindowBase class SpellBuyingWindow : public ReferenceInterface, public WindowBase

@ -405,7 +405,8 @@ namespace MWGui
MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::Ptr player = MWMechanics::getPlayer();
int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
if (MyGUI::utility::parseInt(mPriceLabel->getCaption()) > playerGold) int price = MyGUI::utility::parseInt(mPriceLabel->getCaption());
if (price > playerGold)
{ {
MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}"); MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}");
return; return;
@ -413,8 +414,6 @@ namespace MWGui
mSpell.mName = mNameEdit->getCaption(); mSpell.mName = mNameEdit->getCaption();
int price = MyGUI::utility::parseInt(mPriceLabel->getCaption());
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player);
// add gold to NPC trading gold pool // add gold to NPC trading gold pool

@ -6,8 +6,6 @@
namespace MWGui namespace MWGui
{ {
class WindowManager;
class StatsWindow : public WindowPinnableBase, public NoDrop, public StatsListener class StatsWindow : public WindowPinnableBase, public NoDrop, public StatsListener
{ {
public: public:

@ -3,11 +3,6 @@
#include "windowbase.hpp" #include "windowbase.hpp"
namespace MWGui
{
class WindowManager;
}
namespace MWGui namespace MWGui
{ {
class TextInputDialog : public WindowModal class TextInputDialog : public WindowModal

@ -58,12 +58,12 @@ namespace MWGui
} }
} }
int TimeAdvancer::getHours() int TimeAdvancer::getHours() const
{ {
return mHours; return mHours;
} }
bool TimeAdvancer::isRunning() bool TimeAdvancer::isRunning() const
{ {
return mRunning; return mRunning;
} }

@ -14,8 +14,8 @@ namespace MWGui
void stop(); void stop();
void onFrame(float dt); void onFrame(float dt);
int getHours(); int getHours() const;
bool isRunning(); bool isRunning() const;
// signals // signals
typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;

@ -11,12 +11,6 @@ namespace MyGUI
class Widget; class Widget;
} }
namespace MWGui
{
class WindowManager;
}
namespace MWGui namespace MWGui
{ {
class TravelWindow : public ReferenceInterface, public WindowBase class TravelWindow : public ReferenceInterface, public WindowBase

@ -268,7 +268,7 @@ namespace MWGui
void initialiseOverride() override; void initialiseOverride() override;
private: private:
static const int sIconOffset = 24; static constexpr int sIconOffset = 24;
void updateWidgets(); void updateWidgets();

@ -3,11 +3,6 @@
#include "layout.hpp" #include "layout.hpp"
namespace MWBase
{
class WindowManager;
}
namespace MWWorld namespace MWWorld
{ {
class Ptr; class Ptr;
@ -15,7 +10,6 @@ namespace MWWorld
namespace MWGui namespace MWGui
{ {
class WindowManager;
class DragAndDrop; class DragAndDrop;
class WindowBase: public Layout class WindowBase: public Layout

@ -114,7 +114,6 @@ namespace MWGui
class TrainingWindow; class TrainingWindow;
class SpellIcons; class SpellIcons;
class MerchantRepair; class MerchantRepair;
class Repair;
class SoulgemDialog; class SoulgemDialog;
class Recharge; class Recharge;
class CompanionWindow; class CompanionWindow;

@ -5,8 +5,6 @@
namespace MWGui namespace MWGui
{ {
class WindowManager;
class WindowPinnableBase: public WindowBase class WindowPinnableBase: public WindowBase
{ {
public: public:

@ -70,7 +70,7 @@ namespace MWInput
} }
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
if (uiScale != 0.f) if (uiScale > 0.f)
mInvUiScalingFactor = 1.f / uiScale; mInvUiScalingFactor = 1.f / uiScale;
float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input"); float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input");

@ -37,7 +37,7 @@ namespace MWInput
, mGuiCursorEnabled(true) , mGuiCursorEnabled(true)
{ {
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
if (uiScale != 0.f) if (uiScale > 0.f)
mInvUiScalingFactor = 1.f / uiScale; mInvUiScalingFactor = 1.f / uiScale;
int w,h; int w,h;

@ -5,6 +5,8 @@
#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/actorutil.hpp"
#include <components/misc/timer.hpp>
namespace MWRender namespace MWRender
{ {
class Animation; class Animation;
@ -41,12 +43,18 @@ namespace MWMechanics
bool isTurningToPlayer() const; bool isTurningToPlayer() const;
void setTurningToPlayer(bool turning); void setTurningToPlayer(bool turning);
Misc::TimerStatus updateEngageCombatTimer(float duration)
{
return mEngageCombat.update(duration);
}
private: private:
std::unique_ptr<CharacterController> mCharacterController; std::unique_ptr<CharacterController> mCharacterController;
int mGreetingTimer{0}; int mGreetingTimer{0};
float mTargetAngleRadians{0.f}; float mTargetAngleRadians{0.f};
GreetingState mGreetingState{Greet_None}; GreetingState mGreetingState{Greet_None};
bool mIsTurningToPlayer{false}; bool mIsTurningToPlayer{false};
Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f)};
}; };
} }

@ -379,7 +379,11 @@ namespace MWMechanics
if (actor != MWMechanics::getPlayer()) if (actor != MWMechanics::getPlayer())
return; return;
MWWorld::Ptr newItem = *store.getSlot(slot); MWWorld::Ptr newItem;
auto it = store.getSlot(slot);
// Equip can fail because beast races cannot equip boots/helmets
if(it != store.end())
newItem = *it;
if (newItem.isEmpty() || boundPtr != newItem) if (newItem.isEmpty() || boundPtr != newItem)
return; return;
@ -2022,14 +2026,11 @@ namespace MWMechanics
{ {
if(!paused) if(!paused)
{ {
static float timerUpdateAITargets = 0;
static float timerUpdateHeadTrack = 0; static float timerUpdateHeadTrack = 0;
static float timerUpdateEquippedLight = 0; static float timerUpdateEquippedLight = 0;
static float timerUpdateHello = 0; static float timerUpdateHello = 0;
const float updateEquippedLightInterval = 1.0f; const float updateEquippedLightInterval = 1.0f;
// target lists get updated once every 1.0 sec
if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0;
if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0; if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0;
if (timerUpdateHello >= 0.25f) timerUpdateHello = 0; if (timerUpdateHello >= 0.25f) timerUpdateHello = 0;
if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0; if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0;
@ -2124,6 +2125,8 @@ namespace MWMechanics
iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration);
const Misc::TimerStatus engageCombatTimerStatus = iter->second->updateEngageCombatTimer(duration);
// For dead actors we need to update looping spell particles // For dead actors we need to update looping spell particles
if (iter->first.getClass().getCreatureStats(iter->first).isDead()) if (iter->first.getClass().getCreatureStats(iter->first).isDead())
{ {
@ -2161,7 +2164,7 @@ namespace MWMechanics
if (inProcessingRange && (aiActive || isLocalActor || isDedicatedActor)) if (inProcessingRange && (aiActive || isLocalActor || isDedicatedActor))
{ {
if (timerUpdateAITargets == 0 && (isLocalActor || aiActive)) if (engageCombatTimerStatus == Misc::TimerStatus::Elapsed && (isLocalActor || aiActive))
{ {
if (!isPlayer) if (!isPlayer)
adjustCommandedActor(iter->first); adjustCommandedActor(iter->first);
@ -2251,7 +2254,6 @@ namespace MWMechanics
if (avoidCollisions) if (avoidCollisions)
predictAndAvoidCollisions(); predictAndAvoidCollisions();
timerUpdateAITargets += duration;
timerUpdateHeadTrack += duration; timerUpdateHeadTrack += duration;
timerUpdateEquippedLight += duration; timerUpdateEquippedLight += duration;
timerUpdateHello += duration; timerUpdateHello += duration;

@ -23,4 +23,10 @@ namespace MWMechanics
MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::World* world = MWBase::Environment::get().getWorld();
return (actor.getClass().canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor); return (actor.getClass().canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor);
} }
bool hasWaterWalking(const MWWorld::Ptr& actor)
{
const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects();
return effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0;
}
} }

@ -31,6 +31,7 @@ namespace MWMechanics
MWWorld::Ptr getPlayer(); MWWorld::Ptr getPlayer();
bool isPlayerInCombat(); bool isPlayerInCombat();
bool canActorMoveByZAxis(const MWWorld::Ptr& actor); bool canActorMoveByZAxis(const MWWorld::Ptr& actor);
bool hasWaterWalking(const MWWorld::Ptr& actor);
template<class T> template<class T>
void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value)

@ -45,13 +45,13 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterCont
return true; //Door is no longer opening return true; //Door is no longer opening
ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door
float x = pos.pos[0] - tPos.pos[0]; float x = pos.pos[1] - tPos.pos[1];
float y = pos.pos[1] - tPos.pos[1]; float y = pos.pos[0] - tPos.pos[0];
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
// Turn away from the door and move when turn completed // Turn away from the door and move when turn completed
if (zTurn(actor, std::atan2(x,y) + getAdjustedAngle(), osg::DegreesToRadians(5.f))) if (zTurn(actor, std::atan2(y,x) + getAdjustedAngle(), osg::DegreesToRadians(5.f)))
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
else else
actor.getClass().getMovementSettings(actor).mPosition[1] = 0; actor.getClass().getMovementSettings(actor).mPosition[1] = 0;

@ -1,5 +1,7 @@
#include "aicast.hpp" #include "aicast.hpp"
#include <components/misc/constants.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -54,12 +56,12 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
if (target != actor && target.getClass().isActor()) if (target != actor && target.getClass().isActor())
{ {
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target); osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target);
targetPos.z() += halfExtents.z() * 2 * 0.75f; targetPos.z() += halfExtents.z() * 2 * Constants::TorsoHeight;
} }
osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
actorPos.z() += halfExtents.z() * 2 * 0.75f; actorPos.z() += halfExtents.z() * 2 * Constants::TorsoHeight;
osg::Vec3f dir = targetPos - actorPos; osg::Vec3f dir = targetPos - actorPos;

@ -178,19 +178,10 @@ namespace MWMechanics
} }
storage.mActionCooldown -= duration; storage.mActionCooldown -= duration;
float& timerReact = storage.mTimerReact; if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
if (timerReact < AI_REACTION_TIME) return false;
{
timerReact += duration;
}
else
{
timerReact = 0;
if (attack(actor, target, storage, characterController))
return true;
}
return false; return attack(actor, target, storage, characterController);
} }
bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController) bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController)
@ -305,7 +296,6 @@ namespace MWMechanics
const osg::Vec3f vActorPos(pos.asVec3()); const osg::Vec3f vActorPos(pos.asVec3());
const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3());
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS); storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS);
@ -313,13 +303,14 @@ namespace MWMechanics
if (isRangedCombat) if (isRangedCombat)
{ {
// rotate actor taking into account target movement direction and projectile speed // rotate actor taking into account target movement direction and projectile speed
vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); osg::Vec3f vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir); storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
} }
else else
{ {
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, false);
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
} }
@ -799,7 +790,7 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t
// idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same // idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same
osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3(); osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3();
osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, true);
float distToTarget = vDirToTarget.length(); float distToTarget = vDirToTarget.length();
osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos; osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos;

@ -10,6 +10,7 @@
#include "pathfinding.hpp" #include "pathfinding.hpp"
#include "movement.hpp" #include "movement.hpp"
#include "obstacle.hpp" #include "obstacle.hpp"
#include "aitimer.hpp"
namespace ESM namespace ESM
{ {
@ -27,7 +28,7 @@ namespace MWMechanics
struct AiCombatStorage : AiTemporaryBase struct AiCombatStorage : AiTemporaryBase
{ {
float mAttackCooldown; float mAttackCooldown;
float mTimerReact; AiReactionTimer mReaction;
float mTimerCombatMove; float mTimerCombatMove;
bool mReadyToAttack; bool mReadyToAttack;
bool mAttack; bool mAttack;
@ -60,7 +61,6 @@ namespace MWMechanics
AiCombatStorage(): AiCombatStorage():
mAttackCooldown(0.0f), mAttackCooldown(0.0f),
mTimerReact(AI_REACTION_TIME),
mTimerCombatMove(0.0f), mTimerCombatMove(0.0f),
mReadyToAttack(false), mReadyToAttack(false),
mAttack(false), mAttack(false),

@ -73,23 +73,25 @@ namespace MWMechanics
const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false); const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false);
const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3(); const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3();
const osg::Vec3f followerPos = follower.getRefData().getPosition().asVec3(); const osg::Vec3f followerPos = follower.getRefData().getPosition().asVec3();
const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
const float maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z()));
if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist) if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist)
{ {
const osg::Vec3f dest(mX, mY, mZ); const osg::Vec3f dest(mX, mY, mZ);
if (pathTo(actor, dest, duration)) //Returns true on path complete if (pathTo(actor, dest, duration, maxHalfExtent)) //Returns true on path complete
{ {
mRemainingDuration = mDuration; mRemainingDuration = mDuration;
return true; return true;
} }
mMaxDist = 450; mMaxDist = maxHalfExtent + 450.0f;
} }
else else
{ {
// Stop moving if the player is too far away // Stop moving if the player is too far away
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1);
actor.getClass().getMovementSettings(actor).mPosition[1] = 0; actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
mMaxDist = 250; mMaxDist = maxHalfExtent + 250.0f;
} }
return false; return false;

@ -28,7 +28,6 @@
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
mTypeId(typeId), mTypeId(typeId),
mOptions(options), mOptions(options),
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
mTargetActorRefId(""), mTargetActorRefId(""),
mTargetActorId(-1), mTargetActorId(-1),
mRotateOnTheRunChecks(0), mRotateOnTheRunChecks(0),
@ -64,7 +63,7 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
void MWMechanics::AiPackage::reset() void MWMechanics::AiPackage::reset()
{ {
// reset all members // reset all members
mTimer = AI_REACTION_TIME + 1.0f; mReaction.reset();
mIsShortcutting = false; mIsShortcutting = false;
mShortcutProhibited = false; mShortcutProhibited = false;
mShortcutFailPos = osg::Vec3f(); mShortcutFailPos = osg::Vec3f();
@ -75,7 +74,7 @@ void MWMechanics::AiPackage::reset()
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance) bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance)
{ {
mTimer += duration; //Update timer const Misc::TimerStatus timerStatus = mReaction.update(duration);
const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor
MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::World* world = MWBase::Environment::get().getWorld();
@ -98,7 +97,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
const bool isDestReached = (distToTarget <= destTolerance); const bool isDestReached = (distToTarget <= destTolerance);
const bool actorCanMoveByZ = canActorMoveByZAxis(actor); const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
if (!isDestReached && mTimer > AI_REACTION_TIME) if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed)
{ {
if (actor.getClass().isBipedal(actor)) if (actor.getClass().isBipedal(actor))
openDoors(actor); openDoors(actor);
@ -115,7 +114,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
if (wasShortcutting || doesPathNeedRecalc(dest, actor)) // if need to rebuild path if (wasShortcutting || doesPathNeedRecalc(dest, actor)) // if need to rebuild path
{ {
const auto pathfindingHalfExtents = world->getPathfindingHalfExtents(actor); const auto pathfindingHalfExtents = world->getPathfindingHalfExtents(actor);
mPathFinder.buildPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()),
pathfindingHalfExtents, getNavigatorFlags(actor), getAreaCosts(actor)); pathfindingHalfExtents, getNavigatorFlags(actor), getAreaCosts(actor));
mRotateOnTheRunChecks = 3; mRotateOnTheRunChecks = 3;
@ -142,8 +141,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go
} }
} }
mTimer = 0;
} }
const float actorTolerance = 2 * actor.getClass().getMaxSpeed(actor) * duration const float actorTolerance = 2 * actor.getClass().getMaxSpeed(actor) * duration
@ -414,10 +411,16 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act
DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::Ptr& actor) const DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::Ptr& actor) const
{ {
static const bool allowToFollowOverWaterSurface = Settings::Manager::getBool("allow actors to follow over water surface", "Game");
const MWWorld::Class& actorClass = actor.getClass(); const MWWorld::Class& actorClass = actor.getClass();
DetourNavigator::Flags result = DetourNavigator::Flag_none; DetourNavigator::Flags result = DetourNavigator::Flag_none;
if (actorClass.isPureWaterCreature(actor) || (getTypeId() != AiPackageTypeId::Wander && actorClass.canSwim(actor))) if (actorClass.isPureWaterCreature(actor)
|| (getTypeId() != AiPackageTypeId::Wander
&& ((allowToFollowOverWaterSurface && getTypeId() == AiPackageTypeId::Follow)
|| actorClass.canSwim(actor)
|| hasWaterWalking(actor))))
result |= DetourNavigator::Flag_swim; result |= DetourNavigator::Flag_swim;
if (actorClass.canWalk(actor)) if (actorClass.canWalk(actor))

@ -10,6 +10,7 @@
#include "obstacle.hpp" #include "obstacle.hpp"
#include "aistate.hpp" #include "aistate.hpp"
#include "aipackagetypeid.hpp" #include "aipackagetypeid.hpp"
#include "aitimer.hpp"
namespace MWWorld namespace MWWorld
{ {
@ -28,8 +29,6 @@ namespace ESM
namespace MWMechanics namespace MWMechanics
{ {
const float AI_REACTION_TIME = 0.25f;
class CharacterController; class CharacterController;
class PathgridGraph; class PathgridGraph;
@ -158,7 +157,7 @@ namespace MWMechanics
PathFinder mPathFinder; PathFinder mPathFinder;
ObstacleCheck mObstacleCheck; ObstacleCheck mObstacleCheck;
float mTimer; AiReactionTimer mReaction;
std::string mTargetActorRefId; std::string mTargetActorRefId;
mutable int mTargetActorId; mutable int mTargetActorId;

@ -0,0 +1,26 @@
#ifndef OPENMW_MECHANICS_AITIMER_H
#define OPENMW_MECHANICS_AITIMER_H
#include <components/misc/rng.hpp>
#include <components/misc/timer.hpp>
namespace MWMechanics
{
constexpr float AI_REACTION_TIME = 0.25f;
class AiReactionTimer
{
public:
static constexpr float sDeviation = 0.1f;
Misc::TimerStatus update(float duration) { return mImpl.update(duration); }
void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation)); }
private:
Misc::DeviatingPeriodicTimer mImpl {AI_REACTION_TIME, sDeviation,
Misc::Rng::deviate(0, sDeviation)};
};
}
#endif

@ -223,15 +223,10 @@ namespace MWMechanics
doPerFrameActionsForState(actor, duration, storage); doPerFrameActionsForState(actor, duration, storage);
float& lastReaction = storage.mReaction; if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
lastReaction += duration;
if (AI_REACTION_TIME <= lastReaction)
{
lastReaction = 0;
return reactionTimeActions(actor, storage, pos);
}
else
return false; return false;
return reactionTimeActions(actor, storage, pos);
} }
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos) bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)

@ -10,6 +10,7 @@
#include "pathfinding.hpp" #include "pathfinding.hpp"
#include "obstacle.hpp" #include "obstacle.hpp"
#include "aistate.hpp" #include "aistate.hpp"
#include "aitimer.hpp"
namespace ESM namespace ESM
{ {
@ -25,7 +26,7 @@ namespace MWMechanics
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive. /// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
struct AiWanderStorage : AiTemporaryBase struct AiWanderStorage : AiTemporaryBase
{ {
float mReaction; // update some actions infrequently AiReactionTimer mReaction;
// AiWander states // AiWander states
enum WanderState enum WanderState
@ -57,7 +58,6 @@ namespace MWMechanics
int mStuckCount; int mStuckCount;
AiWanderStorage(): AiWanderStorage():
mReaction(0),
mState(Wander_ChooseAction), mState(Wander_ChooseAction),
mIsWanderingManually(false), mIsWanderingManually(false),
mCanWanderAlongPathGrid(true), mCanWanderAlongPathGrid(true),

@ -228,38 +228,48 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
if(mHitState == CharState_None) if(mHitState == CharState_None)
{ {
if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0
|| mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0) || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0))
&& mAnimation->hasAnimation("knockout"))
{ {
mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds
if (isSwimming && mAnimation->hasAnimation("swimknockout")) if (isSwimming && mAnimation->hasAnimation("swimknockout"))
{ {
mHitState = CharState_SwimKnockOut; mHitState = CharState_SwimKnockOut;
mCurrentHit = "swimknockout"; mCurrentHit = "swimknockout";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
} }
else else if (!isSwimming && mAnimation->hasAnimation("knockout"))
{ {
mHitState = CharState_KnockOut; mHitState = CharState_KnockOut;
mCurrentHit = "knockout"; mCurrentHit = "knockout";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
}
else
{
// Knockout animations are missing. Fall back to idle animation, so target actor still can be killed via HtH.
mCurrentHit.erase();
} }
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true);
} }
else if(knockdown && mAnimation->hasAnimation("knockdown")) else if (knockdown)
{ {
if (isSwimming && mAnimation->hasAnimation("swimknockdown")) if (isSwimming && mAnimation->hasAnimation("swimknockdown"))
{ {
mHitState = CharState_SwimKnockDown; mHitState = CharState_SwimKnockDown;
mCurrentHit = "swimknockdown"; mCurrentHit = "swimknockdown";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
} }
else else if (!isSwimming && mAnimation->hasAnimation("knockdown"))
{ {
mHitState = CharState_KnockDown; mHitState = CharState_KnockDown;
mCurrentHit = "knockdown"; mCurrentHit = "knockdown";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
}
else
{
// Knockdown animation is missing. Cancel knockdown state.
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false);
} }
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
} }
else if (recovery) else if (recovery)
{ {
@ -3179,7 +3189,7 @@ void CharacterController::updateHeadTracking(float duration)
} }
else else
// no head node to look at, fall back to look at center of collision box // no head node to look at, fall back to look at center of collision box
direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget); direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget, false);
} }
direction.normalize(); direction.normalize();

@ -24,7 +24,7 @@ namespace MWMechanics
{ {
struct CorprusStats struct CorprusStats
{ {
static const int sWorseningPeriod = 24; static constexpr int sWorseningPeriod = 24;
int mWorsenings[ESM::Attribute::Length]; int mWorsenings[ESM::Attribute::Length];
MWWorld::TimeStamp mNextWorsening; MWWorld::TimeStamp mNextWorsening;

@ -708,7 +708,7 @@ namespace MWMechanics
// Deviating from Morrowind here: it doesn't increase disposition on marginal wins, // Deviating from Morrowind here: it doesn't increase disposition on marginal wins,
// which seems to be a bug (MCP fixes it too). // which seems to be a bug (MCP fixes it too).
// Original logic: x = 0, y = -iPerMinChange // Original logic: x = 0, y = -iPerMinChange
x = -iPerMinChange; x = iPerMinChange;
y = x; // This goes unused. y = x; // This goes unused.
} }
else else

@ -12,7 +12,7 @@ namespace MWMechanics
{ {
struct Movement; struct Movement;
static const int NUM_EVADE_DIRECTIONS = 4; static constexpr int NUM_EVADE_DIRECTIONS = 4;
/// tests actor's proximity to a closed door by default /// tests actor's proximity to a closed door by default
bool proximityToDoor(const MWWorld::Ptr& actor, float minDist); bool proximityToDoor(const MWWorld::Ptr& actor, float minDist);

@ -442,4 +442,21 @@ namespace MWMechanics
std::copy(prePath.rbegin(), prePath.rend(), std::front_inserter(mPath)); std::copy(prePath.rbegin(), prePath.rend(), std::front_inserter(mPath));
} }
void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts)
{
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
const auto maxDistance = std::min(
navigator->getMaxNavmeshAreaRealRadius(),
static_cast<float>(Constants::CellSizeInUnits)
);
const auto startToEnd = endPoint - startPoint;
const auto distance = startToEnd.length();
if (distance <= maxDistance)
return buildPath(actor, startPoint, endPoint, cell, pathgridGraph, halfExtents, flags, areaCosts);
const auto end = startPoint + startToEnd * maxDistance / distance;
buildPath(actor, startPoint, end, cell, pathgridGraph, halfExtents, flags, areaCosts);
}
} }

@ -101,6 +101,10 @@ namespace MWMechanics
void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents, void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents,
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts);
void buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts);
/// Remove front point if exist and within tolerance /// Remove front point if exist and within tolerance
void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
bool shortenIfAlmostStraight, bool canMoveByZ); bool shortenIfAlmostStraight, bool canMoveByZ);

@ -194,22 +194,12 @@ namespace MWMechanics
for (std::map<ESM::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); ) for (std::map<ESM::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
{ {
/* if(it->second == -1)
Start of tes3mp addition
If we're iterating over a SummonKey matching an actorId of -1, that means it's a summon
yet to be sent back to us by the server and we should skip over it, because deleting it
here would mean it becomes just a regular creature when the server sends it back to us
*/
if (it->second == -1)
{ {
++it; // Keep the spell effect active if we failed to spawn anything
it++;
continue; continue;
} }
/*
End of tes3mp addition
*/
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second);
if (ptr.isEmpty() || (ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished())) if (ptr.isEmpty() || (ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()))
{ {

@ -35,6 +35,7 @@ namespace MWPhysics
Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler) Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler)
: mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false) : mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false)
, mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents) , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents)
, mStuckFrames(0), mLastStuckPosition{0, 0, 0}
, mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
, mInternalCollisionMode(true) , mInternalCollisionMode(true)
, mExternalCollisionMode(true) , mExternalCollisionMode(true)
@ -213,9 +214,10 @@ bool Actor::setPosition(const osg::Vec3f& position)
if (mSkipSimulation) if (mSkipSimulation)
return false; return false;
bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged; bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
mPreviousPosition = mPosition + mPositionOffset; updateWorldPosition();
mPosition = position + mPositionOffset; applyOffsetChange();
mPositionOffset = osg::Vec3f(); mPreviousPosition = mPosition;
mPosition = position;
return hasChanged; return hasChanged;
} }
@ -232,6 +234,7 @@ void Actor::applyOffsetChange()
mWorldPosition += mPositionOffset; mWorldPosition += mPositionOffset;
mPosition += mPositionOffset; mPosition += mPositionOffset;
mPreviousPosition += mPositionOffset; mPreviousPosition += mPositionOffset;
mSimulationPosition += mPositionOffset;
mPositionOffset = osg::Vec3f(); mPositionOffset = osg::Vec3f();
mWorldPositionChanged = true; mWorldPositionChanged = true;
} }

@ -159,6 +159,24 @@ namespace MWPhysics
MWWorld::Ptr getStandingOnPtr() const; MWWorld::Ptr getStandingOnPtr() const;
void setStandingOnPtr(const MWWorld::Ptr& ptr); void setStandingOnPtr(const MWWorld::Ptr& ptr);
unsigned int getStuckFrames() const
{
return mStuckFrames;
}
void setStuckFrames(unsigned int frames)
{
mStuckFrames = frames;
}
const osg::Vec3f &getLastStuckPosition() const
{
return mLastStuckPosition;
}
void setLastStuckPosition(osg::Vec3f position)
{
mLastStuckPosition = position;
}
private: private:
MWWorld::Ptr mStandingOnPtr; MWWorld::Ptr mStandingOnPtr;
/// Removes then re-adds the collision object to the dynamics world /// Removes then re-adds the collision object to the dynamics world
@ -192,6 +210,9 @@ namespace MWPhysics
btTransform mLocalTransform; btTransform mLocalTransform;
mutable std::mutex mPositionMutex; mutable std::mutex mPositionMutex;
unsigned int mStuckFrames;
osg::Vec3f mLastStuckPosition;
osg::Vec3f mForce; osg::Vec3f mForce;
std::atomic<bool> mOnGround; std::atomic<bool> mOnGround;
std::atomic<bool> mOnSlope; std::atomic<bool> mOnSlope;

@ -3,24 +3,24 @@
namespace MWPhysics namespace MWPhysics
{ {
static const float sStepSizeUp = 34.0f; static constexpr float sStepSizeUp = 34.0f;
static const float sStepSizeDown = 62.0f; static constexpr float sStepSizeDown = 62.0f;
static const float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes static constexpr float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes
static const float sMinStep2 = 20.0f; // hack to skip over shorter but longer/wider/further unwalkable slopes static constexpr float sMinStep2 = 20.0f; // hack to skip over shorter but longer/wider/further unwalkable slopes
// whether to do the above stairstepping logic hacks to work around bad morrowind assets - disabling causes problems but improves performance // whether to do the above stairstepping logic hacks to work around bad morrowind assets - disabling causes problems but improves performance
static const bool sDoExtraStairHacks = true; static constexpr bool sDoExtraStairHacks = true;
static const float sGroundOffset = 1.0f; static constexpr float sGroundOffset = 1.0f;
static const float sMaxSlope = 49.0f; static constexpr float sMaxSlope = 49.0f;
// Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
static const int sMaxIterations = 8; static constexpr int sMaxIterations = 8;
// Allows for more precise movement solving without getting stuck or snagging too easily. // Allows for more precise movement solving without getting stuck or snagging too easily.
static const float sCollisionMargin = 0.1; static constexpr float sCollisionMargin = 0.1;
// Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily // Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily
// Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues // Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues
static const float sAllowedPenetration = 0.0; static constexpr float sAllowedPenetration = 0.0;
} }
#endif #endif

@ -204,7 +204,7 @@ namespace MWPhysics
osg::Vec3f lastSlideNormalFallback(0,0,1); osg::Vec3f lastSlideNormalFallback(0,0,1);
bool forceGroundTest = false; bool forceGroundTest = false;
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations) for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.0001f; ++iterations)
{ {
osg::Vec3f nextpos = newPosition + velocity * remainingTime; osg::Vec3f nextpos = newPosition + velocity * remainingTime;
@ -394,6 +394,12 @@ namespace MWPhysics
isOnGround = false; isOnGround = false;
} }
} }
// forcibly treat stuck actors as if they're on flat ground because buggy collisions when inside of things can/will break ground detection
if(physicActor->getStuckFrames() > 0)
{
isOnGround = true;
isOnSlope = false;
}
} }
if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying) if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying)
@ -437,13 +443,23 @@ namespace MWPhysics
auto* collisionObject = physicActor->getCollisionObject(); auto* collisionObject = physicActor->getCollisionObject();
auto tempPosition = actor.mPosition; auto tempPosition = actor.mPosition;
if(physicActor->getStuckFrames() >= 10)
{
if((physicActor->getLastStuckPosition() - actor.mPosition).length2() < 100)
return;
else
{
physicActor->setStuckFrames(0);
physicActor->setLastStuckPosition({0, 0, 0});
}
}
// use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver) // use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver)
// if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() // if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation()
const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z()); const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z());
// use a 3d approximation of the movement vector to better judge player intent // use a 3d approximation of the movement vector to better judge player intent
const ESM::Position& refpos = ptr.getRefData().getPosition(); auto velocity = (osg::Quat(actor.mRefpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRefpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
auto velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
// try to pop outside of the world before doing anything else if we're inside of it // try to pop outside of the world before doing anything else if we're inside of it
if (!physicActor->getOnGround() || physicActor->getOnSlope()) if (!physicActor->getOnGround() || physicActor->getOnSlope())
velocity += physicActor->getInertialForce(); velocity += physicActor->getInertialForce();
@ -470,6 +486,8 @@ namespace MWPhysics
auto contactCallback = gatherContacts({0.0, 0.0, 0.0}); auto contactCallback = gatherContacts({0.0, 0.0, 0.0});
if(contactCallback.mDistance < -sAllowedPenetration) if(contactCallback.mDistance < -sAllowedPenetration)
{ {
physicActor->setStuckFrames(physicActor->getStuckFrames() + 1);
physicActor->setLastStuckPosition(actor.mPosition);
// we are; try moving it out of the world // we are; try moving it out of the world
auto positionDelta = contactCallback.mContactSum; auto positionDelta = contactCallback.mContactSum;
// limit rejection delta to the largest known individual rejections // limit rejection delta to the largest known individual rejections
@ -502,6 +520,11 @@ namespace MWPhysics
} }
} }
} }
else
{
physicActor->setStuckFrames(0);
physicActor->setLastStuckPosition({0, 0, 0});
}
collisionObject->setWorldTransform(oldTransform); collisionObject->setWorldTransform(oldTransform);
actor.mPosition = tempPosition; actor.mPosition = tempPosition;

@ -9,6 +9,7 @@
#include "components/settings/settings.hpp" #include "components/settings/settings.hpp"
#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/movement.hpp" #include "../mwmechanics/movement.hpp"
#include "../mwrender/bulletdebugdraw.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/player.hpp" #include "../mwworld/player.hpp"
@ -101,7 +102,7 @@ namespace
osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
{ {
const float interpolationFactor = timeAccum / physicsDt; const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f);
return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
} }
@ -137,10 +138,12 @@ namespace
namespace MWPhysics namespace MWPhysics
{ {
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld) PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld, MWRender::DebugDrawer* debugDrawer)
: mPhysicsDt(physicsDt) : mDefaultPhysicsDt(physicsDt)
, mPhysicsDt(physicsDt)
, mTimeAccum(0.f) , mTimeAccum(0.f)
, mCollisionWorld(std::move(collisionWorld)) , mCollisionWorld(collisionWorld)
, mDebugDrawer(debugDrawer)
, mNumJobs(0) , mNumJobs(0)
, mRemainingSteps(0) , mRemainingSteps(0)
, mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics"))
@ -152,6 +155,11 @@ namespace MWPhysics
, mNextLOS(0) , mNextLOS(0)
, mFrameNumber(0) , mFrameNumber(0)
, mTimer(osg::Timer::instance()) , mTimer(osg::Timer::instance())
, mPrevStepCount(1)
, mBudget(physicsDt)
, mAsyncBudget(0.0f)
, mBudgetCursor(0)
, mAsyncStartTime(0)
, mTimeBegin(0) , mTimeBegin(0)
, mTimeEnd(0) , mTimeEnd(0)
, mFrameStart(0) , mFrameStart(0)
@ -179,7 +187,7 @@ namespace MWPhysics
if (data.mActor.lock()) if (data.mActor.lock())
{ {
std::unique_lock lock(mCollisionWorldMutex); std::unique_lock lock(mCollisionWorldMutex);
MovementSolver::unstuck(data, mCollisionWorld.get()); MovementSolver::unstuck(data, mCollisionWorld);
} }
}); });
@ -220,13 +228,61 @@ namespace MWPhysics
thread.join(); thread.join();
} }
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) std::tuple<int, float> PhysicsTaskScheduler::calculateStepConfig(float timeAccum) const
{
int maxAllowedSteps = 2;
int numSteps = timeAccum / mDefaultPhysicsDt;
// adjust maximum step count based on whether we're likely physics bottlenecked or not
// if maxAllowedSteps ends up higher than numSteps, we will not invoke delta time
// if it ends up lower than numSteps, but greater than 1, we will run a number of true delta time physics steps that we expect to be within budget
// if it ends up lower than numSteps and also 1, we will run a single delta time physics step
// if we did not do this, and had a fixed step count limit,
// we would have an unnecessarily low render framerate if we were only physics bottlenecked,
// and we would be unnecessarily invoking true delta time if we were only render bottlenecked
// get physics timing stats
float budgetMeasurement = std::max(mBudget.get(), mAsyncBudget.get());
// time spent per step in terms of the intended physics framerate
budgetMeasurement /= mDefaultPhysicsDt;
// ensure sane minimum value
budgetMeasurement = std::max(0.00001f, budgetMeasurement);
// we're spending almost or more than realtime per physics frame; limit to a single step
if (budgetMeasurement > 0.95)
maxAllowedSteps = 1;
// physics is fairly cheap; limit based on expense
if (budgetMeasurement < 0.5)
maxAllowedSteps = std::ceil(1.0/budgetMeasurement);
// limit to a reasonable amount
maxAllowedSteps = std::min(10, maxAllowedSteps);
// fall back to delta time for this frame if fixed timestep physics would fall behind
float actualDelta = mDefaultPhysicsDt;
if (numSteps > maxAllowedSteps)
{
numSteps = maxAllowedSteps;
// ensure that we do not simulate a frame ahead when doing delta time; this reduces stutter and latency
// this causes interpolation to 100% use the most recent physics result when true delta time is happening
// and we deliberately simulate up to exactly the timestamp that we want to render
actualDelta = timeAccum/float(numSteps+1);
// actually: if this results in a per-step delta less than the target physics steptime, clamp it
// this might reintroduce some stutter, but only comes into play in obscure cases
// (because numSteps is originally based on mDefaultPhysicsDt, this won't cause us to overrun)
actualDelta = std::max(actualDelta, mDefaultPhysicsDt);
}
return std::make_tuple(numSteps, actualDelta);
}
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::moveActors(float & timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{ {
// This function run in the main thread. // This function run in the main thread.
// While the mSimulationMutex is held, background physics threads can't run. // While the mSimulationMutex is held, background physics threads can't run.
std::unique_lock lock(mSimulationMutex); std::unique_lock lock(mSimulationMutex);
double timeStart = mTimer->tick();
mMovedActors.clear(); mMovedActors.clear();
// start by finishing previous background computation // start by finishing previous background computation
@ -251,14 +307,21 @@ namespace MWPhysics
mMovedActors.emplace_back(data.mActorRaw->getPtr()); mMovedActors.emplace_back(data.mActorRaw->getPtr());
} }
} }
if(mAdvanceSimulation)
mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor);
updateStats(frameStart, frameNumber, stats); updateStats(frameStart, frameNumber, stats);
} }
auto [numSteps, newDelta] = calculateStepConfig(timeAccum);
timeAccum -= numSteps*newDelta;
// init // init
for (auto& data : actorsData) for (auto& data : actorsData)
data.updatePosition(); data.updatePosition(mCollisionWorld);
mPrevStepCount = numSteps;
mRemainingSteps = numSteps; mRemainingSteps = numSteps;
mTimeAccum = timeAccum; mTimeAccum = timeAccum;
mPhysicsDt = newDelta;
mActorsFrameData = std::move(actorsData); mActorsFrameData = std::move(actorsData);
mAdvanceSimulation = (mRemainingSteps != 0); mAdvanceSimulation = (mRemainingSteps != 0);
mNewFrame = true; mNewFrame = true;
@ -269,20 +332,30 @@ namespace MWPhysics
if (mAdvanceSimulation) if (mAdvanceSimulation)
mWorldFrameData = std::make_unique<WorldFrameData>(); mWorldFrameData = std::make_unique<WorldFrameData>();
if (mAdvanceSimulation)
mBudgetCursor += 1;
if (mNumThreads == 0) if (mNumThreads == 0)
{ {
syncComputation(); syncComputation();
if(mAdvanceSimulation)
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor);
return mMovedActors; return mMovedActors;
} }
mAsyncStartTime = mTimer->tick();
lock.unlock(); lock.unlock();
mHasJob.notify_all(); mHasJob.notify_all();
if (mAdvanceSimulation)
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor);
return mMovedActors; return mMovedActors;
} }
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
{ {
std::unique_lock lock(mSimulationMutex); std::unique_lock lock(mSimulationMutex);
mBudget.reset(mDefaultPhysicsDt);
mAsyncBudget.reset(0.0f);
mMovedActors.clear(); mMovedActors.clear();
mActorsFrameData.clear(); mActorsFrameData.clear();
for (const auto& [_, actor] : actors) for (const auto& [_, actor] : actors)
@ -310,7 +383,7 @@ namespace MWPhysics
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
{ {
std::shared_lock lock(mCollisionWorldMutex); std::shared_lock lock(mCollisionWorldMutex);
ContactTestWrapper::contactTest(mCollisionWorld.get(), colObj, resultCallback); ContactTestWrapper::contactTest(mCollisionWorld, colObj, resultCallback);
} }
std::optional<btVector3> PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target) std::optional<btVector3> PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target)
@ -461,7 +534,7 @@ namespace MWPhysics
if(const auto actor = mActorsFrameData[job].mActor.lock()) if(const auto actor = mActorsFrameData[job].mActor.lock())
{ {
MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData);
} }
} }
@ -523,8 +596,8 @@ namespace MWPhysics
{ {
for (auto& actorData : mActorsFrameData) for (auto& actorData : mActorsFrameData)
{ {
MovementSolver::unstuck(actorData, mCollisionWorld.get()); MovementSolver::unstuck(actorData, mCollisionWorld);
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld, *mWorldFrameData);
} }
updateActorsPositions(); updateActorsPositions();
@ -555,4 +628,10 @@ namespace MWPhysics
mTimeBegin = mTimer->tick(); mTimeBegin = mTimer->tick();
mFrameNumber = frameNumber; mFrameNumber = frameNumber;
} }
void PhysicsTaskScheduler::debugDraw()
{
std::shared_lock lock(mCollisionWorldMutex);
mDebugDrawer->step();
}
} }

@ -13,18 +13,24 @@
#include "physicssystem.hpp" #include "physicssystem.hpp"
#include "ptrholder.hpp" #include "ptrholder.hpp"
#include "components/misc/budgetmeasurement.hpp"
namespace Misc namespace Misc
{ {
class Barrier; class Barrier;
} }
namespace MWRender
{
class DebugDrawer;
}
namespace MWPhysics namespace MWPhysics
{ {
class PhysicsTaskScheduler class PhysicsTaskScheduler
{ {
public: public:
PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld); PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld, MWRender::DebugDrawer* debugDrawer);
~PhysicsTaskScheduler(); ~PhysicsTaskScheduler();
/// @brief move actors taking into account desired movements and collisions /// @brief move actors taking into account desired movements and collisions
@ -32,7 +38,7 @@ namespace MWPhysics
/// @param timeAccum accumulated time from previous run to interpolate movements /// @param timeAccum accumulated time from previous run to interpolate movements
/// @param actorsData per actor data needed to compute new positions /// @param actorsData per actor data needed to compute new positions
/// @return new position of each actor /// @return new position of each actor
const std::vector<MWWorld::Ptr>& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); const std::vector<MWWorld::Ptr>& moveActors(float & timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
const std::vector<MWWorld::Ptr>& resetSimulation(const ActorMap& actors); const std::vector<MWWorld::Ptr>& resetSimulation(const ActorMap& actors);
@ -48,6 +54,7 @@ namespace MWPhysics
void removeCollisionObject(btCollisionObject* collisionObject); void removeCollisionObject(btCollisionObject* collisionObject);
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate=false); void updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate=false);
bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2); bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
void debugDraw();
private: private:
void syncComputation(); void syncComputation();
@ -58,14 +65,16 @@ namespace MWPhysics
void updateAabbs(); void updateAabbs();
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr); void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
std::tuple<int, float> calculateStepConfig(float timeAccum) const;
std::unique_ptr<WorldFrameData> mWorldFrameData; std::unique_ptr<WorldFrameData> mWorldFrameData;
std::vector<ActorFrameData> mActorsFrameData; std::vector<ActorFrameData> mActorsFrameData;
std::vector<MWWorld::Ptr> mMovedActors; std::vector<MWWorld::Ptr> mMovedActors;
float mDefaultPhysicsDt;
/* /*
Start of tes3mp change (major) Start of tes3mp change (major)
Turn mPhysicsDt into a non-const public variable so it can be set from elsewhere Turn mPhysicsDt into a public variable so it can be set from elsewhere
*/ */
public: public:
float mPhysicsDt; float mPhysicsDt;
@ -74,7 +83,8 @@ namespace MWPhysics
End of tes3mp change (major) End of tes3mp change (major)
*/ */
float mTimeAccum; float mTimeAccum;
std::shared_ptr<btCollisionWorld> mCollisionWorld; btCollisionWorld* mCollisionWorld;
MWRender::DebugDrawer* mDebugDrawer;
std::vector<LOSRequest> mLOSCache; std::vector<LOSRequest> mLOSCache;
std::set<std::weak_ptr<PtrHolder>, std::owner_less<std::weak_ptr<PtrHolder>>> mUpdateAabb; std::set<std::weak_ptr<PtrHolder>, std::owner_less<std::weak_ptr<PtrHolder>>> mUpdateAabb;
@ -104,6 +114,12 @@ namespace MWPhysics
unsigned int mFrameNumber; unsigned int mFrameNumber;
const osg::Timer* mTimer; const osg::Timer* mTimer;
int mPrevStepCount;
Misc::BudgetMeasurement mBudget;
Misc::BudgetMeasurement mAsyncBudget;
unsigned int mBudgetCursor;
osg::Timer_t mAsyncStartTime;
osg::Timer_t mTimeBegin; osg::Timer_t mTimeBegin;
osg::Timer_t mTimeEnd; osg::Timer_t mTimeEnd;
osg::Timer_t mFrameStart; osg::Timer_t mFrameStart;

@ -60,6 +60,22 @@
#include "movementsolver.hpp" #include "movementsolver.hpp"
#include "mtphysics.hpp" #include "mtphysics.hpp"
namespace
{
bool canMoveToWaterSurface(const MWPhysics::Actor* physicActor, const float waterlevel, btCollisionWorld* world)
{
if (!physicActor)
return false;
const float halfZ = physicActor->getHalfExtents().z();
const osg::Vec3f actorPosition = physicActor->getPosition();
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
MWPhysics::ActorTracer tracer;
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world);
return (tracer.mFraction >= 1.0f);
}
}
namespace MWPhysics namespace MWPhysics
{ {
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode) PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
@ -79,7 +95,7 @@ namespace MWPhysics
mDispatcher = std::make_unique<btCollisionDispatcher>(mCollisionConfiguration.get()); mDispatcher = std::make_unique<btCollisionDispatcher>(mCollisionConfiguration.get());
mBroadphase = std::make_unique<btDbvtBroadphase>(); mBroadphase = std::make_unique<btDbvtBroadphase>();
mCollisionWorld = std::make_shared<btCollisionWorld>(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get()); mCollisionWorld = std::make_unique<btCollisionWorld>(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get());
// Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this. // Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this.
// Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb. // Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb.
@ -97,8 +113,8 @@ namespace MWPhysics
} }
} }
mTaskScheduler = std::make_unique<PhysicsTaskScheduler>(mPhysicsDt, mCollisionWorld);
mDebugDrawer = std::make_unique<MWRender::DebugDrawer>(mParentNode, mCollisionWorld.get(), mDebugDrawEnabled); mDebugDrawer = std::make_unique<MWRender::DebugDrawer>(mParentNode, mCollisionWorld.get(), mDebugDrawEnabled);
mTaskScheduler = std::make_unique<PhysicsTaskScheduler>(mPhysicsDt, mCollisionWorld.get(), mDebugDrawer.get());
} }
PhysicsSystem::~PhysicsSystem() PhysicsSystem::~PhysicsSystem()
@ -371,16 +387,7 @@ namespace MWPhysics
bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel)
{ {
const Actor* physicActor = getActor(actor); return ::canMoveToWaterSurface(getActor(actor), waterlevel, mCollisionWorld.get());
if (!physicActor)
return false;
const float halfZ = physicActor->getHalfExtents().z();
const osg::Vec3f actorPosition = physicActor->getPosition();
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
ActorTracer tracer;
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, mCollisionWorld.get());
return (tracer.mFraction >= 1.0f);
} }
osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const
@ -768,19 +775,14 @@ namespace MWPhysics
{ {
mTimeAccum += dt; mTimeAccum += dt;
const int maxAllowedSteps = 20;
int numSteps = mTimeAccum / mPhysicsDt;
numSteps = std::min(numSteps, maxAllowedSteps);
mTimeAccum -= numSteps * mPhysicsDt;
if (skipSimulation) if (skipSimulation)
return mTaskScheduler->resetSimulation(mActors); return mTaskScheduler->resetSimulation(mActors);
return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats); // modifies mTimeAccum
return mTaskScheduler->moveActors(mTimeAccum, prepareFrameData(mTimeAccum >= mPhysicsDt), frameStart, frameNumber, stats);
} }
std::vector<ActorFrameData> PhysicsSystem::prepareFrameData(int numSteps) std::vector<ActorFrameData> PhysicsSystem::prepareFrameData(bool willSimulate)
{ {
std::vector<ActorFrameData> actorsFrameData; std::vector<ActorFrameData> actorsFrameData;
actorsFrameData.reserve(mMovementQueue.size()); actorsFrameData.reserve(mMovementQueue.size());
@ -801,16 +803,10 @@ namespace MWPhysics
const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects(); const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects();
bool waterCollision = false; bool waterCollision = false;
bool moveToWaterSurface = false;
if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude())
{ {
if (!world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3()))) if (physicActor->getCollisionMode() || !world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3())))
waterCollision = true;
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(character, waterlevel))
{
moveToWaterSurface = true;
waterCollision = true; waterCollision = true;
}
} }
physicActor->setCanWaterWalk(waterCollision); physicActor->setCanWaterWalk(waterCollision);
@ -820,10 +816,10 @@ namespace MWPhysics
// Ue current value only if we don't advance the simulation. Otherwise we might get a stale value. // Ue current value only if we don't advance the simulation. Otherwise we might get a stale value.
MWWorld::Ptr standingOn; MWWorld::Ptr standingOn;
if (numSteps == 0) if (!willSimulate)
standingOn = physicActor->getStandingOnPtr(); standingOn = physicActor->getStandingOnPtr();
actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel); actorsFrameData.emplace_back(std::move(physicActor), standingOn, waterCollision, movement, slowFall, waterlevel);
} }
mMovementQueue.clear(); mMovementQueue.clear();
return actorsFrameData; return actorsFrameData;
@ -856,7 +852,7 @@ namespace MWPhysics
void PhysicsSystem::debugDraw() void PhysicsSystem::debugDraw()
{ {
if (mDebugDrawEnabled) if (mDebugDrawEnabled)
mDebugDrawer->step(); mTaskScheduler->debugDraw();
} }
bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const
@ -966,9 +962,9 @@ namespace MWPhysics
} }
ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn,
bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel) bool waterCollision, osg::Vec3f movement, float slowFall, float waterlevel)
: mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn),
mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface), mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision),
mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos() mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos()
{ {
const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWBase::World *world = MWBase::Environment::get().getWorld();
@ -982,7 +978,7 @@ namespace MWPhysics
mWasOnGround = actor->getOnGround(); mWasOnGround = actor->getOnGround();
} }
void ActorFrameData::updatePosition() void ActorFrameData::updatePosition(btCollisionWorld* world)
{ {
mActorRaw->updateWorldPosition(); mActorRaw->updateWorldPosition();
// If physics runs "fast enough", position are interpolated without simulation // If physics runs "fast enough", position are interpolated without simulation
@ -990,10 +986,10 @@ namespace MWPhysics
// regardless of simulation speed. // regardless of simulation speed.
mActorRaw->applyOffsetChange(); mActorRaw->applyOffsetChange();
mPosition = mActorRaw->getPosition(); mPosition = mActorRaw->getPosition();
if (mMoveToWaterSurface) if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world))
{ {
mPosition.z() = mWaterlevel; mPosition.z() = mWaterlevel;
MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z()); MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z(), false);
} }
mOldHeight = mPosition.z(); mOldHeight = mPosition.z();
mRefpos = mActorRaw->getPtr().getRefData().getPosition(); mRefpos = mActorRaw->getPtr().getRefData().getPosition();

@ -79,7 +79,7 @@ namespace MWPhysics
struct ActorFrameData struct ActorFrameData
{ {
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel); ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
void updatePosition(); void updatePosition(btCollisionWorld* world);
std::weak_ptr<Actor> mActor; std::weak_ptr<Actor> mActor;
Actor* mActorRaw; Actor* mActorRaw;
MWWorld::Ptr mStandingOn; MWWorld::Ptr mStandingOn;
@ -90,7 +90,7 @@ namespace MWPhysics
bool mDidJump; bool mDidJump;
bool mFloatToSurface; bool mFloatToSurface;
bool mNeedLand; bool mNeedLand;
bool mMoveToWaterSurface; bool mWaterCollision;
float mWaterlevel; float mWaterlevel;
float mSlowFall; float mSlowFall;
float mOldHeight; float mOldHeight;
@ -262,14 +262,14 @@ namespace MWPhysics
void updateWater(); void updateWater();
std::vector<ActorFrameData> prepareFrameData(int numSteps); std::vector<ActorFrameData> prepareFrameData(bool willSimulate);
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue; osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
std::unique_ptr<btBroadphaseInterface> mBroadphase; std::unique_ptr<btBroadphaseInterface> mBroadphase;
std::unique_ptr<btDefaultCollisionConfiguration> mCollisionConfiguration; std::unique_ptr<btDefaultCollisionConfiguration> mCollisionConfiguration;
std::unique_ptr<btCollisionDispatcher> mDispatcher; std::unique_ptr<btCollisionDispatcher> mDispatcher;
std::shared_ptr<btCollisionWorld> mCollisionWorld; std::unique_ptr<btCollisionWorld> mCollisionWorld;
std::unique_ptr<PhysicsTaskScheduler> mTaskScheduler; std::unique_ptr<PhysicsTaskScheduler> mTaskScheduler;
std::unique_ptr<Resource::BulletShapeManager> mShapeManager; std::unique_ptr<Resource::BulletShapeManager> mShapeManager;

@ -102,7 +102,7 @@ public:
BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody
}; };
/* This is the number of *discrete* blend masks. */ /* This is the number of *discrete* blend masks. */
static const size_t sNumBlendMasks = 4; static constexpr size_t sNumBlendMasks = 4;
/// Holds an animation priority value for each BoneGroup. /// Holds an animation priority value for each BoneGroup.
struct AnimPriority struct AnimPriority

@ -90,7 +90,7 @@ LocalMap::LocalMap(osg::Group* root)
{ {
// Increase map resolution, if use UI scaling // Increase map resolution, if use UI scaling
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
if (uiScale > 1.0) if (uiScale > 0.f)
mMapResolution *= uiScale; mMapResolution *= uiScale;
SceneUtil::FindByNameVisitor find("Scene Root"); SceneUtil::FindByNameVisitor find("Scene Root");

@ -63,7 +63,7 @@ namespace MWRender
{ {
std::set<ESM::RefNum> mDisabled; std::set<ESM::RefNum> mDisabled;
std::set<ESM::RefNum> mBlacklist; std::set<ESM::RefNum> mBlacklist;
bool operator==(const RefTracker&other) { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; } bool operator==(const RefTracker&other) const { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; }
}; };
RefTracker mRefTracker; RefTracker mRefTracker;
RefTracker mRefTrackerNew; RefTracker mRefTrackerNew;

@ -815,6 +815,7 @@ namespace MWRender
RenderingManager::RayResult result; RenderingManager::RayResult result;
result.mHit = false; result.mHit = false;
result.mHitRefnum.mContentFile = -1; result.mHitRefnum.mContentFile = -1;
result.mHitRefnum.mIndex = -1;
result.mRatio = 0; result.mRatio = 0;
if (intersector->containsIntersections()) if (intersector->containsIntersections())
{ {

@ -45,6 +45,9 @@
#include <components/sceneutil/visitor.hpp> #include <components/sceneutil/visitor.hpp>
#include <components/sceneutil/shadow.hpp> #include <components/sceneutil/shadow.hpp>
#include <components/settings/settings.hpp>
#include <components/misc/stringops.hpp>
#include <components/nifosg/particle.hpp> #include <components/nifosg/particle.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -1164,7 +1167,7 @@ void SkyManager::create()
{ {
assert(!mCreated); assert(!mCreated);
mAtmosphereDay = mSceneManager->getInstance("meshes/sky_atmosphere.nif", mEarlyRenderBinRoot); mAtmosphereDay = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot);
ModVertexAlphaVisitor modAtmosphere(0); ModVertexAlphaVisitor modAtmosphere(0);
mAtmosphereDay->accept(modAtmosphere); mAtmosphereDay->accept(modAtmosphere);
@ -1176,10 +1179,10 @@ void SkyManager::create()
mEarlyRenderBinRoot->addChild(mAtmosphereNightNode); mEarlyRenderBinRoot->addChild(mAtmosphereNightNode);
osg::ref_ptr<osg::Node> atmosphereNight; osg::ref_ptr<osg::Node> atmosphereNight;
if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif")) if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models")))
atmosphereNight = mSceneManager->getInstance("meshes/sky_night_02.nif", mAtmosphereNightNode); atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight02", "Models"), mAtmosphereNightNode);
else else
atmosphereNight = mSceneManager->getInstance("meshes/sky_night_01.nif", mAtmosphereNightNode); atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight01", "Models"), mAtmosphereNightNode);
atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
ModVertexAlphaVisitor modStars(2); ModVertexAlphaVisitor modStars(2);
atmosphereNight->accept(modStars); atmosphereNight->accept(modStars);
@ -1193,14 +1196,14 @@ void SkyManager::create()
mCloudNode = new osg::PositionAttitudeTransform; mCloudNode = new osg::PositionAttitudeTransform;
mEarlyRenderBinRoot->addChild(mCloudNode); mEarlyRenderBinRoot->addChild(mCloudNode);
mCloudMesh = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode); mCloudMesh = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode);
ModVertexAlphaVisitor modClouds(1); ModVertexAlphaVisitor modClouds(1);
mCloudMesh->accept(modClouds); mCloudMesh->accept(modClouds);
mCloudUpdater = new CloudUpdater; mCloudUpdater = new CloudUpdater;
mCloudUpdater->setOpacity(1.f); mCloudUpdater->setOpacity(1.f);
mCloudMesh->addUpdateCallback(mCloudUpdater); mCloudMesh->addUpdateCallback(mCloudUpdater);
mCloudMesh2 = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode); mCloudMesh2 = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode);
mCloudMesh2->accept(modClouds); mCloudMesh2->accept(modClouds);
mCloudUpdater2 = new CloudUpdater; mCloudUpdater2 = new CloudUpdater;
mCloudUpdater2->setOpacity(0.f); mCloudUpdater2->setOpacity(0.f);
@ -1597,7 +1600,7 @@ void SkyManager::update(float duration)
if (mParticleNode) if (mParticleNode)
{ {
// Morrowind deliberately rotates the blizzard mesh, so so should we. // Morrowind deliberately rotates the blizzard mesh, so so should we.
if (mCurrentParticleEffect == "meshes\\blizzard.nif") if (mCurrentParticleEffect == Settings::Manager::getString("weatherblizzard", "Models"))
quat.makeRotate(osg::Vec3f(-1,0,0), mStormDirection); quat.makeRotate(osg::Vec3f(-1,0,0), mStormDirection);
mParticleNode->setAttitude(quat); mParticleNode->setAttitude(quat);
} }
@ -1897,16 +1900,16 @@ void SkyManager::setWaterHeight(float height)
void SkyManager::listAssetsToPreload(std::vector<std::string>& models, std::vector<std::string>& textures) void SkyManager::listAssetsToPreload(std::vector<std::string>& models, std::vector<std::string>& textures)
{ {
models.emplace_back("meshes/sky_atmosphere.nif"); models.emplace_back(Settings::Manager::getString("skyatmosphere", "Models"));
if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif")) if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models")))
models.emplace_back("meshes/sky_night_02.nif"); models.emplace_back(Settings::Manager::getString("skynight02", "Models"));
models.emplace_back("meshes/sky_night_01.nif"); models.emplace_back(Settings::Manager::getString("skynight01", "Models"));
models.emplace_back("meshes/sky_clouds_01.nif"); models.emplace_back(Settings::Manager::getString("skyclouds", "Models"));
models.emplace_back("meshes\\ashcloud.nif"); models.emplace_back(Settings::Manager::getString("weatherashcloud", "Models"));
models.emplace_back("meshes\\blightcloud.nif"); models.emplace_back(Settings::Manager::getString("weatherblightcloud", "Models"));
models.emplace_back("meshes\\snow.nif"); models.emplace_back(Settings::Manager::getString("weathersnow", "Models"));
models.emplace_back("meshes\\blizzard.nif"); models.emplace_back(Settings::Manager::getString("weatherblizzard", "Models"));
textures.emplace_back("textures/tx_mooncircle_full_s.dds"); textures.emplace_back("textures/tx_mooncircle_full_s.dds");
textures.emplace_back("textures/tx_mooncircle_full_m.dds"); textures.emplace_back("textures/tx_mooncircle_full_m.dds");

@ -109,7 +109,7 @@ namespace MWScript
runtime.pop(); runtime.pop();
if (count<0) if (count<0)
throw std::runtime_error ("second argument for AddItem must be non-negative"); count = static_cast<uint16_t>(count);
// no-op // no-op
if (count == 0) if (count == 0)

@ -250,13 +250,20 @@ namespace MWScript
} }
} }
bool GlobalScripts::readRecord (ESM::ESMReader& reader, uint32_t type) bool GlobalScripts::readRecord (ESM::ESMReader& reader, uint32_t type, const std::map<int, int>& contentFileMap)
{ {
if (type==ESM::REC_GSCR) if (type==ESM::REC_GSCR)
{ {
ESM::GlobalScript script; ESM::GlobalScript script;
script.load (reader); script.load (reader);
if (script.mTargetRef.hasContentFile())
{
auto iter = contentFileMap.find(script.mTargetRef.mContentFile);
if (iter != contentFileMap.end())
script.mTargetRef.mContentFile = iter->second;
}
auto iter = mScripts.find (script.mId); auto iter = mScripts.find (script.mId);
if (iter==mScripts.end()) if (iter==mScripts.end())

@ -73,7 +73,7 @@ namespace MWScript
void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; void write (ESM::ESMWriter& writer, Loading::Listener& progress) const;
bool readRecord (ESM::ESMReader& reader, uint32_t type); bool readRecord (ESM::ESMReader& reader, uint32_t type, const std::map<int, int>& contentFileMap);
///< Records for variables that do not exist are dropped silently. ///< Records for variables that do not exist are dropped silently.
/// ///
/// \return Known type? /// \return Known type?

@ -10,16 +10,6 @@
#include "../mwworld/ptr.hpp" #include "../mwworld/ptr.hpp"
namespace MWSound
{
class SoundManager;
}
namespace MWInput
{
struct MWInputManager;
}
namespace MWScript namespace MWScript
{ {
class Locals; class Locals;

@ -14,7 +14,7 @@ namespace MWScript
{ {
struct ExplicitRef struct ExplicitRef
{ {
static const bool implicit = false; static constexpr bool implicit = false;
MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true, MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true,
bool activeOnly = false) const; bool activeOnly = false) const;
@ -22,7 +22,7 @@ namespace MWScript
struct ImplicitRef struct ImplicitRef
{ {
static const bool implicit = true; static constexpr bool implicit = true;
MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true, MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true,
bool activeOnly = false) const; bool activeOnly = false) const;

@ -49,7 +49,7 @@ namespace MWScript
std::vector<MWWorld::Ptr> actors; std::vector<MWWorld::Ptr> actors;
MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors);
for (auto& actor : actors) for (auto& actor : actors)
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff); MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false);
} }
template<class R> template<class R>
@ -363,7 +363,7 @@ namespace MWScript
} }
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr, dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos)); MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true));
} }
}; };
@ -499,7 +499,7 @@ namespace MWScript
} }
else else
{ {
ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true); ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true, true);
} }
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(base,ptr); dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(base,ptr);
@ -902,7 +902,7 @@ namespace MWScript
// This approach can be used to create elevators. // This approach can be used to create elevators.
moveStandingActors(ptr, diff); moveStandingActors(ptr, diff);
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr, dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false));
} }
}; };
@ -938,7 +938,7 @@ namespace MWScript
// This approach can be used to create elevators. // This approach can be used to create elevators.
moveStandingActors(ptr, diff); moveStandingActors(ptr, diff);
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr, dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false));
} }
}; };

@ -69,15 +69,13 @@ namespace MWSound
FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs); FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs);
FFmpeg_Decoder(const FFmpeg_Decoder &rhs); FFmpeg_Decoder(const FFmpeg_Decoder &rhs);
FFmpeg_Decoder(const VFS::Manager* vfs);
public: public:
explicit FFmpeg_Decoder(const VFS::Manager* vfs);
virtual ~FFmpeg_Decoder(); virtual ~FFmpeg_Decoder();
friend class SoundManager; friend class SoundManager;
}; };
#ifndef DEFAULT_DECODER
#define DEFAULT_DECODER (::MWSound::FFmpeg_Decoder)
#endif
} }
#endif #endif

@ -98,9 +98,6 @@ namespace MWSound
OpenAL_Output(SoundManager &mgr); OpenAL_Output(SoundManager &mgr);
virtual ~OpenAL_Output(); virtual ~OpenAL_Output();
}; };
#ifndef DEFAULT_OUTPUT
#define DEFAULT_OUTPUT(x) ::MWSound::OpenAL_Output((x))
#endif
} }
#endif #endif

@ -54,7 +54,7 @@ namespace MWSound
SoundManager::SoundManager(const VFS::Manager* vfs, bool useSound) SoundManager::SoundManager(const VFS::Manager* vfs, bool useSound)
: mVFS(vfs) : mVFS(vfs)
, mOutput(new DEFAULT_OUTPUT(*this)) , mOutput(new OpenAL_Output(*this))
, mWaterSoundUpdater(makeWaterSoundUpdaterSettings()) , mWaterSoundUpdater(makeWaterSoundUpdaterSettings())
, mSoundBuffers(*vfs, *mOutput) , mSoundBuffers(*vfs, *mOutput)
, mListenerUnderwater(false) , mListenerUnderwater(false)
@ -109,7 +109,7 @@ namespace MWSound
SoundManager::~SoundManager() SoundManager::~SoundManager()
{ {
clear(); SoundManager::clear();
mSoundBuffers.clear(); mSoundBuffers.clear();
mOutput.reset(); mOutput.reset();
} }
@ -117,7 +117,7 @@ namespace MWSound
// Return a new decoder instance, used as needed by the output implementations // Return a new decoder instance, used as needed by the output implementations
DecoderPtr SoundManager::getDecoder() DecoderPtr SoundManager::getDecoder()
{ {
return DecoderPtr(new DEFAULT_DECODER (mVFS)); return std::make_shared<FFmpeg_Decoder>(mVFS);
} }
DecoderPtr SoundManager::loadVoice(const std::string &voicefile) DecoderPtr SoundManager::loadVoice(const std::string &voicefile)

@ -141,7 +141,7 @@ namespace MWSound
public: public:
SoundManager(const VFS::Manager* vfs, bool useSound); SoundManager(const VFS::Manager* vfs, bool useSound);
virtual ~SoundManager(); ~SoundManager() override;
void processChangedSettings(const Settings::CategorySettingVector& settings) override; void processChangedSettings(const Settings::CategorySettingVector& settings) override;

@ -18,14 +18,14 @@ void MWState::QuickSaveManager::visitSave(const Slot *saveSlot)
} }
} }
bool MWState::QuickSaveManager::isOldestSave(const Slot *compare) bool MWState::QuickSaveManager::isOldestSave(const Slot *compare) const
{ {
if(mOldestSlotVisited == nullptr) if(mOldestSlotVisited == nullptr)
return true; return true;
return (compare->mTimeStamp <= mOldestSlotVisited->mTimeStamp); return (compare->mTimeStamp <= mOldestSlotVisited->mTimeStamp);
} }
bool MWState::QuickSaveManager::shouldCreateNewSlot() bool MWState::QuickSaveManager::shouldCreateNewSlot() const
{ {
return (mSlotsVisited < mMaxSaves); return (mSlotsVisited < mMaxSaves);
} }

@ -13,8 +13,8 @@ namespace MWState{
unsigned int mSlotsVisited; unsigned int mSlotsVisited;
const Slot *mOldestSlotVisited; const Slot *mOldestSlotVisited;
private: private:
bool shouldCreateNewSlot(); bool shouldCreateNewSlot() const;
bool isOldestSave(const Slot *compare); bool isOldestSave(const Slot *compare) const;
public: public:
QuickSaveManager(std::string &saveName, unsigned int maxSaves); QuickSaveManager(std::string &saveName, unsigned int maxSaves);
///< A utility class to manage multiple quicksave slots ///< A utility class to manage multiple quicksave slots

@ -471,7 +471,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
case ESM::REC_GSCR: case ESM::REC_GSCR:
MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval); MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval, contentFileMap);
break; break;
case ESM::REC_GMAP: case ESM::REC_GMAP:
@ -515,6 +515,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
character->getPath().filename().string()); character->getPath().filename().string());
MWBase::Environment::get().getWindowManager()->setNewGame(false); MWBase::Environment::get().getWindowManager()->setNewGame(false);
MWBase::Environment::get().getWorld()->saveLoaded();
MWBase::Environment::get().getWorld()->setupPlayer(); MWBase::Environment::get().getWorld()->setupPlayer();
MWBase::Environment::get().getWorld()->renderPlayer(); MWBase::Environment::get().getWorld()->renderPlayer();
MWBase::Environment::get().getWindowManager()->updatePlayer(); MWBase::Environment::get().getWindowManager()->updatePlayer();

@ -155,9 +155,11 @@ void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, CellStore& cell) const
MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& reader) MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& reader)
: mStore (store), mReader (reader), : mStore (store), mReader (reader),
mIdCache (Settings::Manager::getInt("pointers cache size", "Cells"), std::pair<std::string, CellStore *> ("", (CellStore*)nullptr)),
mIdCacheIndex (0) mIdCacheIndex (0)
{} {
int cacheSize = std::max(Settings::Manager::getInt("pointers cache size", "Cells"), 0);
mIdCache = IdCache(cacheSize, std::pair<std::string, CellStore *> ("", (CellStore*)nullptr));
}
MWWorld::CellStore *MWWorld::Cells::getExterior (int x, int y) MWWorld::CellStore *MWWorld::Cells::getExterior (int x, int y)
{ {
@ -282,8 +284,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell,
MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name)
{ {
// First check the cache // First check the cache
for (std::vector<std::pair<std::string, CellStore *> >::iterator iter (mIdCache.begin()); for (IdCache::iterator iter (mIdCache.begin()); iter!=mIdCache.end(); ++iter)
iter!=mIdCache.end(); ++iter)
if (iter->first==name && iter->second) if (iter->first==name && iter->second)
{ {
Ptr ptr = getPtr (name, *iter->second); Ptr ptr = getPtr (name, *iter->second);

@ -28,11 +28,12 @@ namespace MWWorld
/// \brief Cell container /// \brief Cell container
class Cells class Cells
{ {
typedef std::vector<std::pair<std::string, CellStore *> > IdCache;
const MWWorld::ESMStore& mStore; const MWWorld::ESMStore& mStore;
std::vector<ESM::ESMReader>& mReader; std::vector<ESM::ESMReader>& mReader;
mutable std::map<std::string, CellStore> mInteriors; mutable std::map<std::string, CellStore> mInteriors;
mutable std::map<std::pair<int, int>, CellStore> mExteriors; mutable std::map<std::pair<int, int>, CellStore> mExteriors;
std::vector<std::pair<std::string, CellStore *> > mIdCache; IdCache mIdCache;
std::size_t mIdCacheIndex; std::size_t mIdCacheIndex;
Cells (const Cells&); Cells (const Cells&);

@ -90,10 +90,24 @@ MWWorld::ResolutionListener::~ResolutionListener()
{ {
if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty()) if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty())
{ {
for(const auto&& ptr : mStore) try
ptr.getRefData().setCount(0); {
for(const auto&& ptr : mStore)
ptr.getRefData().setCount(0);
}
catch(const std::exception& e)
{
Log(Debug::Warning) << "Failed to clear temporary container contents of " << mStore.mPtr.get<ESM::Container>()->mBase->mId << ": " << e.what();
}
mStore.fillNonRandom(mStore.mPtr.get<ESM::Container>()->mBase->mInventory, "", mStore.mSeed); mStore.fillNonRandom(mStore.mPtr.get<ESM::Container>()->mBase->mInventory, "", mStore.mSeed);
addScripts(mStore, mStore.mPtr.mCell); try
{
addScripts(mStore, mStore.mPtr.mCell);
}
catch(const std::exception& e)
{
Log(Debug::Warning) << "Failed to restart item scripts inside " << mStore.mPtr.get<ESM::Container>()->mBase->mId << ": " << e.what();
}
mStore.mResolved = false; mStore.mResolved = false;
} }
} }

@ -73,22 +73,22 @@ namespace MWWorld
{ {
public: public:
static const int Type_Potion = 0x0001; static constexpr int Type_Potion = 0x0001;
static const int Type_Apparatus = 0x0002; static constexpr int Type_Apparatus = 0x0002;
static const int Type_Armor = 0x0004; static constexpr int Type_Armor = 0x0004;
static const int Type_Book = 0x0008; static constexpr int Type_Book = 0x0008;
static const int Type_Clothing = 0x0010; static constexpr int Type_Clothing = 0x0010;
static const int Type_Ingredient = 0x0020; static constexpr int Type_Ingredient = 0x0020;
static const int Type_Light = 0x0040; static constexpr int Type_Light = 0x0040;
static const int Type_Lockpick = 0x0080; static constexpr int Type_Lockpick = 0x0080;
static const int Type_Miscellaneous = 0x0100; static constexpr int Type_Miscellaneous = 0x0100;
static const int Type_Probe = 0x0200; static constexpr int Type_Probe = 0x0200;
static const int Type_Repair = 0x0400; static constexpr int Type_Repair = 0x0400;
static const int Type_Weapon = 0x0800; static constexpr int Type_Weapon = 0x0800;
static const int Type_Last = Type_Weapon; static constexpr int Type_Last = Type_Weapon;
static const int Type_All = 0xffff; static constexpr int Type_All = 0xffff;
static const std::string sGoldId; static const std::string sGoldId;
@ -153,7 +153,7 @@ namespace MWWorld
virtual ~ContainerStore(); virtual ~ContainerStore();
virtual ContainerStore* clone() { return new ContainerStore(*this); } virtual std::unique_ptr<ContainerStore> clone() { return std::make_unique<ContainerStore>(*this); }
ConstContainerStoreIterator cbegin (int mask = Type_All) const; ConstContainerStoreIterator cbegin (int mask = Type_All) const;
ConstContainerStoreIterator cend() const; ConstContainerStoreIterator cend() const;
@ -276,13 +276,13 @@ namespace MWWorld
template<class From, class To, class Dummy> template<class From, class To, class Dummy>
struct IsConvertible struct IsConvertible
{ {
static const bool value = true; static constexpr bool value = true;
}; };
template<class Dummy> template<class Dummy>
struct IsConvertible<ConstPtr, Ptr, Dummy> struct IsConvertible<ConstPtr, Ptr, Dummy>
{ {
static const bool value = false; static constexpr bool value = false;
}; };
template<class T, class U> template<class T, class U>

@ -1,6 +1,8 @@
#ifndef GAME_MWWORLD_CUSTOMDATA_H #ifndef GAME_MWWORLD_CUSTOMDATA_H
#define GAME_MWWORLD_CUSTOMDATA_H #define GAME_MWWORLD_CUSTOMDATA_H
#include <memory>
namespace MWClass namespace MWClass
{ {
class CreatureCustomData; class CreatureCustomData;
@ -19,7 +21,7 @@ namespace MWWorld
virtual ~CustomData() {} virtual ~CustomData() {}
virtual CustomData *clone() const = 0; virtual std::unique_ptr<CustomData> clone() const = 0;
// Fast version of dynamic_cast<X&>. Needs to be overridden in the respective class. // Fast version of dynamic_cast<X&>. Needs to be overridden in the respective class.
@ -38,6 +40,15 @@ namespace MWWorld
virtual MWClass::CreatureLevListCustomData& asCreatureLevListCustomData(); virtual MWClass::CreatureLevListCustomData& asCreatureLevListCustomData();
virtual const MWClass::CreatureLevListCustomData& asCreatureLevListCustomData() const; virtual const MWClass::CreatureLevListCustomData& asCreatureLevListCustomData() const;
}; };
template <class T>
struct TypedCustomData : CustomData
{
std::unique_ptr<CustomData> clone() const final
{
return std::make_unique<T>(*static_cast<const T*>(this));
}
};
} }
#endif #endif

@ -48,6 +48,57 @@ namespace
} }
} }
} }
std::vector<ESM::NPC> getNPCsToReplace(const MWWorld::Store<ESM::Faction>& factions, const MWWorld::Store<ESM::Class>& classes, const std::map<std::string, ESM::NPC>& npcs)
{
// Cache first class from store - we will use it if current class is not found
std::string defaultCls;
auto it = classes.begin();
if (it != classes.end())
defaultCls = it->mId;
else
throw std::runtime_error("List of NPC classes is empty!");
// Validate NPCs for non-existing class and faction.
// We will replace invalid entries by fixed ones
std::vector<ESM::NPC> npcsToReplace;
for (auto it : npcs)
{
ESM::NPC npc = it.second;
bool changed = false;
const std::string npcFaction = npc.mFaction;
if (!npcFaction.empty())
{
const ESM::Faction *fact = factions.search(npcFaction);
if (!fact)
{
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it.";
npc.mFaction.clear();
npc.mNpdt.mRank = 0;
changed = true;
}
}
std::string npcClass = npc.mClass;
if (!npcClass.empty())
{
const ESM::Class *cls = classes.search(npcClass);
if (!cls)
{
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement.";
npc.mClass = defaultCls;
changed = true;
}
}
if (changed)
npcsToReplace.push_back(npc);
}
return npcsToReplace;
}
} }
namespace MWWorld namespace MWWorld
@ -218,49 +269,7 @@ int ESMStore::getRefCount(const std::string& id) const
void ESMStore::validate() void ESMStore::validate()
{ {
// Cache first class from store - we will use it if current class is not found std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mStatic);
std::string defaultCls = "";
Store<ESM::Class>::iterator it = mClasses.begin();
if (it != mClasses.end())
defaultCls = it->mId;
else
throw std::runtime_error("List of NPC classes is empty!");
// Validate NPCs for non-existing class and faction.
// We will replace invalid entries by fixed ones
std::vector<ESM::NPC> npcsToReplace;
for (ESM::NPC npc : mNpcs)
{
bool changed = false;
const std::string npcFaction = npc.mFaction;
if (!npcFaction.empty())
{
const ESM::Faction *fact = mFactions.search(npcFaction);
if (!fact)
{
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it.";
npc.mFaction.clear();
npc.mNpdt.mRank = 0;
changed = true;
}
}
std::string npcClass = npc.mClass;
if (!npcClass.empty())
{
const ESM::Class *cls = mClasses.search(npcClass);
if (!cls)
{
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement.";
npc.mClass = defaultCls;
changed = true;
}
}
if (changed)
npcsToReplace.push_back(npc);
}
for (const ESM::NPC &npc : npcsToReplace) for (const ESM::NPC &npc : npcsToReplace)
{ {
@ -331,6 +340,14 @@ void ESMStore::validate()
} }
} }
void ESMStore::validateDynamic()
{
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mDynamic);
for (const ESM::NPC &npc : npcsToReplace)
mNpcs.insert(npc);
}
int ESMStore::countSavedGameRecords() const int ESMStore::countSavedGameRecords() const
{ {
return 1 // DYNA (dynamic name counter) return 1 // DYNA (dynamic name counter)
@ -384,12 +401,14 @@ void ESMStore::validate()
case ESM::REC_ENCH: case ESM::REC_ENCH:
case ESM::REC_SPEL: case ESM::REC_SPEL:
case ESM::REC_WEAP: case ESM::REC_WEAP:
case ESM::REC_NPC_:
case ESM::REC_LEVI: case ESM::REC_LEVI:
case ESM::REC_LEVC: case ESM::REC_LEVC:
mStores[type]->read (reader);
return true;
case ESM::REC_NPC_:
case ESM::REC_CREA: case ESM::REC_CREA:
case ESM::REC_CONT: case ESM::REC_CONT:
mStores[type]->read (reader); mStores[type]->read (reader, true);
return true; return true;
case ESM::REC_DYNA: case ESM::REC_DYNA:

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

Loading…
Cancel
Save