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:
- 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:
extends: .Debian
cache:
@ -93,24 +116,49 @@ Debian_Clang_tests:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
MacOS:
.MacOS:
image: macos-11-xcode-12
tags:
- macos
stage: build
only:
variables:
- $CI_PROJECT_ID == "7107382"
cache:
paths:
- ccache/
script:
- 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_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
- ccache -s
artifacts:
paths:
- build/OpenMW-*.dmg
- "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
targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard"
package: "Engine"

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

@ -4,7 +4,7 @@ Contributors
The OpenMW project was started in 2008 by Nicolay Korslund.
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
@ -24,7 +24,7 @@ Programmers
Alex McKibben
alexanderkjall
Alexander Nadeau (wareya)
Alexander Olofsson (Ace)
Alexander Olofsson (Ananace)
Alex Rice
Alex S (docwest)
Allofich
@ -131,6 +131,7 @@ Programmers
Martin Otto (MAtahualpa)
Mateusz Kołaczek (PL_kolek)
Mateusz Malisz (malice)
Max Henzerling (SaintMercury)
megaton
Michael Hogan (Xethik)
Michael Mc Donnell
@ -183,6 +184,7 @@ Programmers
sergoz
ShadowRadiance
Siimacore
Simon Meulenbeek (simonmb)
sir_herrbatka
smbas
Sophie Kirschner (pineapplemachine)
@ -196,6 +198,7 @@ Programmers
Sylvain Thesnieres (Garvek)
t6
terrorfisch
Tess (tescoShoppah)
thegriglat
Thomas Luppi (Digmaster)
tlmullis
@ -234,7 +237,8 @@ Documentation
Packagers
---------
Alexander Olofsson (Ace) - Windows
Alexander Olofsson (Ananace) - Windows and Flatpak
Alexey Sokolov (DarthGandalf) - Gentoo Linux
Bret Curtis (psi29a) - Debian and Ubuntu Linux
Edmondo Tommasina (edmondo) - Gentoo Linux
Julian Ospald (hasufell) - Gentoo Linux

@ -57,6 +57,7 @@
Bug #5424: Creatures do not headtrack player
Bug #5425: Poison effect only appears for one frame
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 #5441: Enemies can't push a player character when in critical strike stance
Bug #5451: Magic projectiles don't disappear with the caster
@ -87,6 +88,7 @@
Bug #5656: Sneaking characters block hits while standing
Bug #5661: Region sounds don't play at the right interval
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 #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
@ -111,6 +113,10 @@
Bug #5899: Visible modal windows and dropdowns crashing game on exit
Bug #5902: NiZBufferProperty is unable to disable the depth test
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 #832: OpenMW-CS: Handle deleted references
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 #2686: Timestamps in openmw.log
Feature #3171: OpenMW-CS: Instance drag selection
Feature #3983: Wizard: Add link to buy Morrowind
Feature #4894: Consider actors as obstacles for pathfinding
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
@ -129,6 +136,7 @@
Feature #5456: Basic collada animation support
Feature #5457: Realistic diagonal movement
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 #5524: Resume failed script execution after reload
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 #5771: ori command should report where a mesh is loaded from and whether the x version is used.
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 #5520: Improve cell name autocompleter implementation
Task #5844: Update 'toggle sneak' documentation

@ -848,9 +848,11 @@ fi
wrappedExit 1
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..."
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
popd > /dev/null

@ -16,7 +16,7 @@ cmake \
-D CMAKE_CXX_FLAGS="-stdlib=libc++" \
-D CMAKE_C_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 OPENMW_OSX_DEPLOYMENT=TRUE \
-D BUILD_OPENMW=TRUE \

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

@ -20,6 +20,7 @@ struct Arguments
std::string mode;
std::string filename;
std::string extractfile;
std::string addfile;
std::string outdir;
bool longformat;
@ -36,6 +37,10 @@ bool parseOptions (int argc, char** argv, Arguments &info)
" Extract a file from the input archive.\n\n"
" bsatool extractall archivefile [output_directory]\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");
desc.add_options()
@ -95,7 +100,7 @@ bool parseOptions (int argc, char** argv, Arguments &info)
}
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"
<< 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)
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)
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 extract(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)
{
@ -157,6 +174,12 @@ int main(int argc, char** argv)
else
bsa = std::make_unique<Bsa::BSAFile>(Bsa::BSAFile());
if (info.mode == "create")
{
bsa->open(info.filename);
return 0;
}
bsa->open(info.filename);
if (info.mode == "list")
@ -165,6 +188,8 @@ int main(int argc, char** argv)
return extract(bsa, info);
else if (info.mode == "extractall")
return extractAll(bsa, info);
else if (info.mode == "add")
return add(bsa, info);
else
{
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
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 << "@ 0x" << std::hex << file.offset << std::endl;
std::cout.flags(f);
}
else
std::cout << file.name << std::endl;
std::cout << file.name() << std::endl;
}
return 0;
@ -253,7 +278,7 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
{
for (const auto &file : bsa->getList())
{
std::string extractPath(file.name);
std::string extractPath(file.name());
Misc::StringUtils::replaceAll(extractPath, "\\", "/");
// 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
// (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);
// Write the file to disk
@ -283,3 +308,11 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
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/textinputdialog.cpp
utils/lineedit.cpp
utils/openalutil.cpp
${CMAKE_SOURCE_DIR}/files/windows/launcher.rc
)
@ -31,6 +32,7 @@ set(LAUNCHER_HEADER
utils/profilescombobox.hpp
utils/textinputdialog.hpp
utils/lineedit.hpp
utils/openalutil.hpp
)
# Headers that must be pre-processed
@ -47,6 +49,7 @@ set(LAUNCHER_HEADER_MOC
utils/textinputdialog.hpp
utils/profilescombobox.hpp
utils/lineedit.hpp
utils/openalutil.hpp
)
@ -95,6 +98,7 @@ endif (WIN32)
target_link_libraries(openmw-launcher
${SDL2_LIBRARY_ONLY}
${OPENAL_LIBRARY}
components
)

@ -5,11 +5,14 @@
#include <QFileDialog>
#include <QCompleter>
#include <QProxyStyle>
#include <QString>
#include <components/contentselector/view/contentselector.hpp>
#include <components/contentselector/model/esmfile.hpp>
#include <cmath>
#include "utils/openalutil.hpp"
Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
Settings::Manager &engineSettings, QWidget *parent)
: QWidget(parent)
@ -19,7 +22,17 @@ Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
setObjectName ("AdvancedPage");
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();
mCellNameCompleter.setModel(&mCellNameCompleterModel);
startDefaultCharacterAtField->setCompleter(&mCellNameCompleter);
}
@ -95,6 +108,7 @@ bool Launcher::AdvancedPage::loadSettings()
int numPhysicsThreads = mEngineSettings.getInt("async num threads", "Physics");
if (numPhysicsThreads >= 0)
physicsThreadsSpinBox->setValue(numPhysicsThreads);
loadSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game");
}
// Visuals
@ -126,6 +140,34 @@ bool Launcher::AdvancedPage::loadSettings()
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
{
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
{
saveSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");

@ -20,6 +20,9 @@
QString getAspect(int x, int y)
{
int gcd = std::gcd (x, y);
if (gcd == 0)
return QString();
int xaspect = x / gcd;
int yaspect = y / gcd;
// special case: 8 : 5 is usually referred to as 16:10
@ -298,9 +301,9 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
return result;
}
QString aspect = getAspect(mode.w, 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")) {
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 resetState();
static const int MaxKeys = 4;
static constexpr int MaxKeys = 4;
QPushButton* mButton;

@ -56,7 +56,9 @@ void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData
CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns)
: InventoryColumns (columns) {}
: InventoryColumns (columns)
, mEffects(nullptr)
{}
CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& 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)
: InventoryColumns (columns) {}
: InventoryColumns (columns)
, mTime(nullptr)
, mRadius(nullptr)
, mColor(nullptr)
, mSound(nullptr)
, mEmitterType(nullptr)
{}
CSMWorld::LightRefIdAdapter::LightRefIdAdapter (const LightColumns& 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)
: EnchantableColumns (columns) {}
: EnchantableColumns (columns)
, mType(nullptr)
, mHealth(nullptr)
, mSpeed(nullptr)
, mReach(nullptr)
, mChop{nullptr}
, mSlash{nullptr}
, mThrust{nullptr}
{}
CSMWorld::WeaponRefIdAdapter::WeaponRefIdAdapter (const WeaponColumns& columns)
: EnchantableRefIdAdapter<ESM::Weapon> (UniversalId::Type_Weapon, columns), mColumns (columns)

@ -178,7 +178,11 @@ namespace CSMWorld
const RefIdColumn *mName;
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)
@ -247,7 +251,12 @@ namespace CSMWorld
const RefIdColumn *mWeight;
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
@ -405,7 +414,11 @@ namespace CSMWorld
const RefIdColumn *mEnchantment;
const RefIdColumn *mEnchantmentPoints;
EnchantableColumns (const InventoryColumns& base) : InventoryColumns (base) {}
EnchantableColumns (const InventoryColumns& base)
: InventoryColumns (base)
, mEnchantment(nullptr)
, mEnchantmentPoints(nullptr)
{}
};
/// \brief Adapter for enchantable IDs
@ -474,7 +487,11 @@ namespace CSMWorld
const RefIdColumn *mQuality;
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)
@ -549,7 +566,17 @@ namespace CSMWorld
const RefIdColumn *mAiPackages;
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)
@ -2054,7 +2081,11 @@ namespace CSMWorld
const RefIdColumn *mLevList;
const RefIdColumn *mNestedListLevList;
LevListColumns (const BaseColumns& base) : BaseColumns (base) {}
LevListColumns (const BaseColumns& base)
: BaseColumns (base)
, mLevList(nullptr)
, mNestedListLevList(nullptr)
{}
};
template<typename RecordT>

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

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

@ -401,13 +401,13 @@ namespace MWBase
virtual void deleteObject (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
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
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
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
@ -587,6 +587,8 @@ namespace MWBase
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
virtual void disableDeferredPreviewRotation() = 0;
virtual void saveLoaded() = 0;
virtual void setupPlayer() = 0;
virtual void renderPlayer() = 0;
@ -799,7 +801,7 @@ namespace MWBase
/// Return a vector aiming the actor's weapon towards a 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.
virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0;

@ -58,11 +58,6 @@ namespace MWClass
mStore.readState(inventory);
}
MWWorld::CustomData *ContainerCustomData::clone() const
{
return new ContainerCustomData (*this);
}
ContainerCustomData& ContainerCustomData::asContainerCustomData()
{
return *this;
@ -84,7 +79,7 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
// 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());
}
@ -381,7 +376,7 @@ namespace MWClass
return;
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

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

@ -68,14 +68,16 @@ namespace
namespace MWClass
{
class CreatureCustomData : public MWWorld::CustomData
class CreatureCustomData : public MWWorld::TypedCustomData<CreatureCustomData>
{
public:
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;
MWWorld::CustomData *clone() const override;
CreatureCustomData() = default;
CreatureCustomData(const CreatureCustomData& other);
CreatureCustomData(CreatureCustomData&& other) noexcept = default;
CreatureCustomData& asCreatureCustomData() override
{
@ -85,16 +87,13 @@ namespace MWClass
{
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()
@ -165,16 +164,16 @@ namespace MWClass
// inventory
bool hasInventory = hasInventoryStore(ptr);
if (hasInventory)
data->mContainerStore = new MWWorld::InventoryStore();
data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
else
data->mContainerStore = new MWWorld::ContainerStore();
data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold);
data->mCreatureStats.setNeedRecalcDynamicStats(false);
// store
ptr.getRefData().setCustomData(data.release());
ptr.getRefData().setCustomData(std::move(data));
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
@ -946,11 +945,11 @@ namespace MWClass
std::unique_ptr<CreatureCustomData> data (new CreatureCustomData);
if (hasInventoryStore(ptr))
data->mContainerStore = new MWWorld::InventoryStore();
data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
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

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

@ -43,13 +43,11 @@
namespace MWClass
{
class DoorCustomData : public MWWorld::CustomData
class DoorCustomData : public MWWorld::TypedCustomData<DoorCustomData>
{
public:
MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle;
MWWorld::CustomData *clone() const override;
DoorCustomData& asDoorCustomData() override
{
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
{
if (!model.empty())
@ -419,8 +412,7 @@ namespace MWClass
{
if (!ptr.getRefData().getCustomData())
{
std::unique_ptr<DoorCustomData> data(new DoorCustomData);
ptr.getRefData().setCustomData(data.release());
ptr.getRefData().setCustomData(std::make_unique<DoorCustomData>());
}
}

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

@ -1,5 +1,7 @@
#include "bookpage.hpp"
#include <optional>
#include "MyGUI_RenderItem.h"
#include "MyGUI_RenderManager.h"
#include "MyGUI_TextureUtility.h"
@ -894,6 +896,27 @@ protected:
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:
typedef TypesetBookImpl::StyleImpl Style;
@ -952,16 +975,10 @@ public:
void onMouseMove (int left, int top)
{
if (!mBook)
return;
if (mPage >= mBook->mPages.size())
return;
left -= mCroppedParent->getAbsoluteLeft ();
top -= mCroppedParent->getAbsoluteTop ();
Style * hit = mBook->hitTestWithMargin (left, mViewTop + top);
Style * hit = nullptr;
if(auto pos = getAdjustedPos(left, top, true))
if(pos->top <= mViewBottom)
hit = mBook->hitTestWithMargin (pos->left, pos->top);
if (mLastDown == MyGUI::MouseButton::None)
{
@ -991,24 +1008,11 @@ public:
void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id)
{
if (!mBook)
return;
auto pos = getAdjustedPos(left, top);
if (mPage >= mBook->mPages.size())
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)
if (pos && 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;
dirtyFocusItem ();
@ -1019,25 +1023,11 @@ public:
void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id)
{
if (!mBook)
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 ();
auto pos = getAdjustedPos(left, top);
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;

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

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

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

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

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

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

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

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

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

@ -405,7 +405,8 @@ namespace MWGui
MWWorld::Ptr player = MWMechanics::getPlayer();
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}");
return;
@ -413,8 +414,6 @@ namespace MWGui
mSpell.mName = mNameEdit->getCaption();
int price = MyGUI::utility::parseInt(mPriceLabel->getCaption());
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player);
// add gold to NPC trading gold pool

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

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

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

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

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

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

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

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

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

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

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

@ -5,6 +5,8 @@
#include "../mwmechanics/actorutil.hpp"
#include <components/misc/timer.hpp>
namespace MWRender
{
class Animation;
@ -41,12 +43,18 @@ namespace MWMechanics
bool isTurningToPlayer() const;
void setTurningToPlayer(bool turning);
Misc::TimerStatus updateEngageCombatTimer(float duration)
{
return mEngageCombat.update(duration);
}
private:
std::unique_ptr<CharacterController> mCharacterController;
int mGreetingTimer{0};
float mTargetAngleRadians{0.f};
GreetingState mGreetingState{Greet_None};
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())
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)
return;
@ -2022,14 +2026,11 @@ namespace MWMechanics
{
if(!paused)
{
static float timerUpdateAITargets = 0;
static float timerUpdateHeadTrack = 0;
static float timerUpdateEquippedLight = 0;
static float timerUpdateHello = 0;
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 (timerUpdateHello >= 0.25f) timerUpdateHello = 0;
if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0;
@ -2124,6 +2125,8 @@ namespace MWMechanics
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
if (iter->first.getClass().getCreatureStats(iter->first).isDead())
{
@ -2161,7 +2164,7 @@ namespace MWMechanics
if (inProcessingRange && (aiActive || isLocalActor || isDedicatedActor))
{
if (timerUpdateAITargets == 0 && (isLocalActor || aiActive))
if (engageCombatTimerStatus == Misc::TimerStatus::Elapsed && (isLocalActor || aiActive))
{
if (!isPlayer)
adjustCommandedActor(iter->first);
@ -2251,7 +2254,6 @@ namespace MWMechanics
if (avoidCollisions)
predictAndAvoidCollisions();
timerUpdateAITargets += duration;
timerUpdateHeadTrack += duration;
timerUpdateEquippedLight += duration;
timerUpdateHello += duration;

@ -23,4 +23,10 @@ namespace MWMechanics
MWBase::World* world = MWBase::Environment::get().getWorld();
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();
bool isPlayerInCombat();
bool canActorMoveByZAxis(const MWWorld::Ptr& actor);
bool hasWaterWalking(const MWWorld::Ptr& actor);
template<class T>
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
ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door
float x = pos.pos[0] - tPos.pos[0];
float y = pos.pos[1] - tPos.pos[1];
float x = pos.pos[1] - tPos.pos[1];
float y = pos.pos[0] - tPos.pos[0];
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
// 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;
else
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;

@ -1,5 +1,7 @@
#include "aicast.hpp"
#include <components/misc/constants.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.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())
{
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 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;

@ -178,19 +178,10 @@ namespace MWMechanics
}
storage.mActionCooldown -= duration;
float& timerReact = storage.mTimerReact;
if (timerReact < AI_REACTION_TIME)
{
timerReact += duration;
}
else
{
timerReact = 0;
if (attack(actor, target, storage, characterController))
return true;
}
if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
return false;
return false;
return attack(actor, target, storage, 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 vTargetPos(target.getRefData().getPosition().asVec3());
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS);
@ -313,13 +303,14 @@ namespace MWMechanics
if (isRangedCombat)
{
// 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[2] = getZAngleToDir(vAimDir);
}
else
{
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, false);
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
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
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();
osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos;

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

@ -73,23 +73,25 @@ namespace MWMechanics
const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false);
const osg::Vec3f leaderPos = actor.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)
{
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;
return true;
}
mMaxDist = 450;
mMaxDist = maxHalfExtent + 450.0f;
}
else
{
// Stop moving if the player is too far away
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1);
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
mMaxDist = 250;
mMaxDist = maxHalfExtent + 250.0f;
}
return false;

@ -28,7 +28,6 @@
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
mTypeId(typeId),
mOptions(options),
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
mTargetActorRefId(""),
mTargetActorId(-1),
mRotateOnTheRunChecks(0),
@ -64,7 +63,7 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
void MWMechanics::AiPackage::reset()
{
// reset all members
mTimer = AI_REACTION_TIME + 1.0f;
mReaction.reset();
mIsShortcutting = false;
mShortcutProhibited = false;
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)
{
mTimer += duration; //Update timer
const Misc::TimerStatus timerStatus = mReaction.update(duration);
const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor
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 actorCanMoveByZ = canActorMoveByZAxis(actor);
if (!isDestReached && mTimer > AI_REACTION_TIME)
if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed)
{
if (actor.getClass().isBipedal(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
{
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));
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
}
}
mTimer = 0;
}
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
{
static const bool allowToFollowOverWaterSurface = Settings::Manager::getBool("allow actors to follow over water surface", "Game");
const MWWorld::Class& actorClass = actor.getClass();
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;
if (actorClass.canWalk(actor))

@ -10,6 +10,7 @@
#include "obstacle.hpp"
#include "aistate.hpp"
#include "aipackagetypeid.hpp"
#include "aitimer.hpp"
namespace MWWorld
{
@ -28,8 +29,6 @@ namespace ESM
namespace MWMechanics
{
const float AI_REACTION_TIME = 0.25f;
class CharacterController;
class PathgridGraph;
@ -158,7 +157,7 @@ namespace MWMechanics
PathFinder mPathFinder;
ObstacleCheck mObstacleCheck;
float mTimer;
AiReactionTimer mReaction;
std::string mTargetActorRefId;
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);
float& lastReaction = storage.mReaction;
lastReaction += duration;
if (AI_REACTION_TIME <= lastReaction)
{
lastReaction = 0;
return reactionTimeActions(actor, storage, pos);
}
else
if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
return false;
return reactionTimeActions(actor, storage, pos);
}
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)

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

@ -228,38 +228,48 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
if(mHitState == CharState_None)
{
if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0
|| mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0)
&& mAnimation->hasAnimation("knockout"))
|| mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0))
{
mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds
if (isSwimming && mAnimation->hasAnimation("swimknockout"))
{
mHitState = CharState_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;
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);
}
else if(knockdown && mAnimation->hasAnimation("knockdown"))
else if (knockdown)
{
if (isSwimming && mAnimation->hasAnimation("swimknockdown"))
{
mHitState = CharState_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;
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)
{
@ -3179,7 +3189,7 @@ void CharacterController::updateHeadTracking(float duration)
}
else
// 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();

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

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

@ -12,7 +12,7 @@ namespace MWMechanics
{
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
bool proximityToDoor(const MWWorld::Ptr& actor, float minDist);

@ -442,4 +442,21 @@ namespace MWMechanics
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,
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
void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
bool shortenIfAlmostStraight, bool canMoveByZ);

@ -194,22 +194,12 @@ namespace MWMechanics
for (std::map<ESM::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
{
/*
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)
if(it->second == -1)
{
++it;
// Keep the spell effect active if we failed to spawn anything
it++;
continue;
}
/*
End of tes3mp addition
*/
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second);
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)
: mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false)
, 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)
, mInternalCollisionMode(true)
, mExternalCollisionMode(true)
@ -213,9 +214,10 @@ bool Actor::setPosition(const osg::Vec3f& position)
if (mSkipSimulation)
return false;
bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
mPreviousPosition = mPosition + mPositionOffset;
mPosition = position + mPositionOffset;
mPositionOffset = osg::Vec3f();
updateWorldPosition();
applyOffsetChange();
mPreviousPosition = mPosition;
mPosition = position;
return hasChanged;
}
@ -232,6 +234,7 @@ void Actor::applyOffsetChange()
mWorldPosition += mPositionOffset;
mPosition += mPositionOffset;
mPreviousPosition += mPositionOffset;
mSimulationPosition += mPositionOffset;
mPositionOffset = osg::Vec3f();
mWorldPositionChanged = true;
}

@ -159,6 +159,24 @@ namespace MWPhysics
MWWorld::Ptr getStandingOnPtr() const;
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:
MWWorld::Ptr mStandingOnPtr;
/// Removes then re-adds the collision object to the dynamics world
@ -192,6 +210,9 @@ namespace MWPhysics
btTransform mLocalTransform;
mutable std::mutex mPositionMutex;
unsigned int mStuckFrames;
osg::Vec3f mLastStuckPosition;
osg::Vec3f mForce;
std::atomic<bool> mOnGround;
std::atomic<bool> mOnSlope;

@ -3,24 +3,24 @@
namespace MWPhysics
{
static const float sStepSizeUp = 34.0f;
static const float sStepSizeDown = 62.0f;
static constexpr float sStepSizeUp = 34.0f;
static constexpr float sStepSizeDown = 62.0f;
static const 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 sMinStep = 10.0f; // hack to skip over tiny 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
static const bool sDoExtraStairHacks = true;
static constexpr bool sDoExtraStairHacks = true;
static const float sGroundOffset = 1.0f;
static const float sMaxSlope = 49.0f;
static constexpr float sGroundOffset = 1.0f;
static constexpr float sMaxSlope = 49.0f;
// 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.
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
// 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

@ -204,7 +204,7 @@ namespace MWPhysics
osg::Vec3f lastSlideNormalFallback(0,0,1);
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;
@ -394,6 +394,12 @@ namespace MWPhysics
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)
@ -437,13 +443,23 @@ namespace MWPhysics
auto* collisionObject = physicActor->getCollisionObject();
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)
// 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());
// use a 3d approximation of the movement vector to better judge player intent
const ESM::Position& refpos = ptr.getRefData().getPosition();
auto velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
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;
// try to pop outside of the world before doing anything else if we're inside of it
if (!physicActor->getOnGround() || physicActor->getOnSlope())
velocity += physicActor->getInertialForce();
@ -470,6 +486,8 @@ namespace MWPhysics
auto contactCallback = gatherContacts({0.0, 0.0, 0.0});
if(contactCallback.mDistance < -sAllowedPenetration)
{
physicActor->setStuckFrames(physicActor->getStuckFrames() + 1);
physicActor->setLastStuckPosition(actor.mPosition);
// we are; try moving it out of the world
auto positionDelta = contactCallback.mContactSum;
// 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);
actor.mPosition = tempPosition;

@ -9,6 +9,7 @@
#include "components/settings/settings.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwrender/bulletdebugdraw.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/player.hpp"
@ -101,7 +102,7 @@ namespace
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);
}
@ -137,10 +138,12 @@ namespace
namespace MWPhysics
{
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld)
: mPhysicsDt(physicsDt)
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld, MWRender::DebugDrawer* debugDrawer)
: mDefaultPhysicsDt(physicsDt)
, mPhysicsDt(physicsDt)
, mTimeAccum(0.f)
, mCollisionWorld(std::move(collisionWorld))
, mCollisionWorld(collisionWorld)
, mDebugDrawer(debugDrawer)
, mNumJobs(0)
, mRemainingSteps(0)
, mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics"))
@ -152,6 +155,11 @@ namespace MWPhysics
, mNextLOS(0)
, mFrameNumber(0)
, mTimer(osg::Timer::instance())
, mPrevStepCount(1)
, mBudget(physicsDt)
, mAsyncBudget(0.0f)
, mBudgetCursor(0)
, mAsyncStartTime(0)
, mTimeBegin(0)
, mTimeEnd(0)
, mFrameStart(0)
@ -179,7 +187,7 @@ namespace MWPhysics
if (data.mActor.lock())
{
std::unique_lock lock(mCollisionWorldMutex);
MovementSolver::unstuck(data, mCollisionWorld.get());
MovementSolver::unstuck(data, mCollisionWorld);
}
});
@ -220,13 +228,61 @@ namespace MWPhysics
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.
// While the mSimulationMutex is held, background physics threads can't run.
std::unique_lock lock(mSimulationMutex);
double timeStart = mTimer->tick();
mMovedActors.clear();
// start by finishing previous background computation
@ -251,14 +307,21 @@ namespace MWPhysics
mMovedActors.emplace_back(data.mActorRaw->getPtr());
}
}
if(mAdvanceSimulation)
mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor);
updateStats(frameStart, frameNumber, stats);
}
auto [numSteps, newDelta] = calculateStepConfig(timeAccum);
timeAccum -= numSteps*newDelta;
// init
for (auto& data : actorsData)
data.updatePosition();
data.updatePosition(mCollisionWorld);
mPrevStepCount = numSteps;
mRemainingSteps = numSteps;
mTimeAccum = timeAccum;
mPhysicsDt = newDelta;
mActorsFrameData = std::move(actorsData);
mAdvanceSimulation = (mRemainingSteps != 0);
mNewFrame = true;
@ -269,20 +332,30 @@ namespace MWPhysics
if (mAdvanceSimulation)
mWorldFrameData = std::make_unique<WorldFrameData>();
if (mAdvanceSimulation)
mBudgetCursor += 1;
if (mNumThreads == 0)
{
syncComputation();
if(mAdvanceSimulation)
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor);
return mMovedActors;
}
mAsyncStartTime = mTimer->tick();
lock.unlock();
mHasJob.notify_all();
if (mAdvanceSimulation)
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor);
return mMovedActors;
}
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
{
std::unique_lock lock(mSimulationMutex);
mBudget.reset(mDefaultPhysicsDt);
mAsyncBudget.reset(0.0f);
mMovedActors.clear();
mActorsFrameData.clear();
for (const auto& [_, actor] : actors)
@ -310,7 +383,7 @@ namespace MWPhysics
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
{
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)
@ -461,7 +534,7 @@ namespace MWPhysics
if(const auto actor = mActorsFrameData[job].mActor.lock())
{
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)
{
MovementSolver::unstuck(actorData, mCollisionWorld.get());
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
MovementSolver::unstuck(actorData, mCollisionWorld);
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld, *mWorldFrameData);
}
updateActorsPositions();
@ -555,4 +628,10 @@ namespace MWPhysics
mTimeBegin = mTimer->tick();
mFrameNumber = frameNumber;
}
void PhysicsTaskScheduler::debugDraw()
{
std::shared_lock lock(mCollisionWorldMutex);
mDebugDrawer->step();
}
}

@ -13,18 +13,24 @@
#include "physicssystem.hpp"
#include "ptrholder.hpp"
#include "components/misc/budgetmeasurement.hpp"
namespace Misc
{
class Barrier;
}
namespace MWRender
{
class DebugDrawer;
}
namespace MWPhysics
{
class PhysicsTaskScheduler
{
public:
PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld);
PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld, MWRender::DebugDrawer* debugDrawer);
~PhysicsTaskScheduler();
/// @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 actorsData per actor data needed to compute new positions
/// @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);
@ -48,6 +54,7 @@ namespace MWPhysics
void removeCollisionObject(btCollisionObject* collisionObject);
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate=false);
bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
void debugDraw();
private:
void syncComputation();
@ -58,14 +65,16 @@ namespace MWPhysics
void updateAabbs();
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
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::vector<ActorFrameData> mActorsFrameData;
std::vector<MWWorld::Ptr> mMovedActors;
float mDefaultPhysicsDt;
/*
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:
float mPhysicsDt;
@ -74,7 +83,8 @@ namespace MWPhysics
End of tes3mp change (major)
*/
float mTimeAccum;
std::shared_ptr<btCollisionWorld> mCollisionWorld;
btCollisionWorld* mCollisionWorld;
MWRender::DebugDrawer* mDebugDrawer;
std::vector<LOSRequest> mLOSCache;
std::set<std::weak_ptr<PtrHolder>, std::owner_less<std::weak_ptr<PtrHolder>>> mUpdateAabb;
@ -104,6 +114,12 @@ namespace MWPhysics
unsigned int mFrameNumber;
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 mTimeEnd;
osg::Timer_t mFrameStart;

@ -60,6 +60,22 @@
#include "movementsolver.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
{
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
@ -79,7 +95,7 @@ namespace MWPhysics
mDispatcher = std::make_unique<btCollisionDispatcher>(mCollisionConfiguration.get());
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.
// 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);
mTaskScheduler = std::make_unique<PhysicsTaskScheduler>(mPhysicsDt, mCollisionWorld.get(), mDebugDrawer.get());
}
PhysicsSystem::~PhysicsSystem()
@ -371,16 +387,7 @@ namespace MWPhysics
bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel)
{
const Actor* physicActor = getActor(actor);
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);
return ::canMoveToWaterSurface(getActor(actor), waterlevel, mCollisionWorld.get());
}
osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const
@ -768,19 +775,14 @@ namespace MWPhysics
{
mTimeAccum += dt;
const int maxAllowedSteps = 20;
int numSteps = mTimeAccum / mPhysicsDt;
numSteps = std::min(numSteps, maxAllowedSteps);
mTimeAccum -= numSteps * mPhysicsDt;
if (skipSimulation)
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;
actorsFrameData.reserve(mMovementQueue.size());
@ -801,16 +803,10 @@ namespace MWPhysics
const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects();
bool waterCollision = false;
bool moveToWaterSurface = false;
if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude())
{
if (!world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3())))
waterCollision = true;
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(character, waterlevel))
{
moveToWaterSurface = true;
if (physicActor->getCollisionMode() || !world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3())))
waterCollision = true;
}
}
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.
MWWorld::Ptr standingOn;
if (numSteps == 0)
if (!willSimulate)
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();
return actorsFrameData;
@ -856,7 +852,7 @@ namespace MWPhysics
void PhysicsSystem::debugDraw()
{
if (mDebugDrawEnabled)
mDebugDrawer->step();
mTaskScheduler->debugDraw();
}
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,
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),
mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface),
mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision),
mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos()
{
const MWBase::World *world = MWBase::Environment::get().getWorld();
@ -982,7 +978,7 @@ namespace MWPhysics
mWasOnGround = actor->getOnGround();
}
void ActorFrameData::updatePosition()
void ActorFrameData::updatePosition(btCollisionWorld* world)
{
mActorRaw->updateWorldPosition();
// If physics runs "fast enough", position are interpolated without simulation
@ -990,10 +986,10 @@ namespace MWPhysics
// regardless of simulation speed.
mActorRaw->applyOffsetChange();
mPosition = mActorRaw->getPosition();
if (mMoveToWaterSurface)
if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world))
{
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();
mRefpos = mActorRaw->getPtr().getRefData().getPosition();

@ -79,7 +79,7 @@ namespace MWPhysics
struct ActorFrameData
{
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;
Actor* mActorRaw;
MWWorld::Ptr mStandingOn;
@ -90,7 +90,7 @@ namespace MWPhysics
bool mDidJump;
bool mFloatToSurface;
bool mNeedLand;
bool mMoveToWaterSurface;
bool mWaterCollision;
float mWaterlevel;
float mSlowFall;
float mOldHeight;
@ -262,14 +262,14 @@ namespace MWPhysics
void updateWater();
std::vector<ActorFrameData> prepareFrameData(int numSteps);
std::vector<ActorFrameData> prepareFrameData(bool willSimulate);
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
std::unique_ptr<btBroadphaseInterface> mBroadphase;
std::unique_ptr<btDefaultCollisionConfiguration> mCollisionConfiguration;
std::unique_ptr<btCollisionDispatcher> mDispatcher;
std::shared_ptr<btCollisionWorld> mCollisionWorld;
std::unique_ptr<btCollisionWorld> mCollisionWorld;
std::unique_ptr<PhysicsTaskScheduler> mTaskScheduler;
std::unique_ptr<Resource::BulletShapeManager> mShapeManager;

@ -102,7 +102,7 @@ public:
BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody
};
/* 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.
struct AnimPriority

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

@ -63,7 +63,7 @@ namespace MWRender
{
std::set<ESM::RefNum> mDisabled;
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 mRefTrackerNew;

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

@ -45,6 +45,9 @@
#include <components/sceneutil/visitor.hpp>
#include <components/sceneutil/shadow.hpp>
#include <components/settings/settings.hpp>
#include <components/misc/stringops.hpp>
#include <components/nifosg/particle.hpp>
#include "../mwbase/environment.hpp"
@ -1164,7 +1167,7 @@ void SkyManager::create()
{
assert(!mCreated);
mAtmosphereDay = mSceneManager->getInstance("meshes/sky_atmosphere.nif", mEarlyRenderBinRoot);
mAtmosphereDay = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot);
ModVertexAlphaVisitor modAtmosphere(0);
mAtmosphereDay->accept(modAtmosphere);
@ -1176,10 +1179,10 @@ void SkyManager::create()
mEarlyRenderBinRoot->addChild(mAtmosphereNightNode);
osg::ref_ptr<osg::Node> atmosphereNight;
if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif"))
atmosphereNight = mSceneManager->getInstance("meshes/sky_night_02.nif", mAtmosphereNightNode);
if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models")))
atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight02", "Models"), mAtmosphereNightNode);
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);
ModVertexAlphaVisitor modStars(2);
atmosphereNight->accept(modStars);
@ -1193,14 +1196,14 @@ void SkyManager::create()
mCloudNode = new osg::PositionAttitudeTransform;
mEarlyRenderBinRoot->addChild(mCloudNode);
mCloudMesh = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode);
mCloudMesh = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode);
ModVertexAlphaVisitor modClouds(1);
mCloudMesh->accept(modClouds);
mCloudUpdater = new CloudUpdater;
mCloudUpdater->setOpacity(1.f);
mCloudMesh->addUpdateCallback(mCloudUpdater);
mCloudMesh2 = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode);
mCloudMesh2 = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode);
mCloudMesh2->accept(modClouds);
mCloudUpdater2 = new CloudUpdater;
mCloudUpdater2->setOpacity(0.f);
@ -1597,7 +1600,7 @@ void SkyManager::update(float duration)
if (mParticleNode)
{
// 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);
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)
{
models.emplace_back("meshes/sky_atmosphere.nif");
if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif"))
models.emplace_back("meshes/sky_night_02.nif");
models.emplace_back("meshes/sky_night_01.nif");
models.emplace_back("meshes/sky_clouds_01.nif");
models.emplace_back("meshes\\ashcloud.nif");
models.emplace_back("meshes\\blightcloud.nif");
models.emplace_back("meshes\\snow.nif");
models.emplace_back("meshes\\blizzard.nif");
models.emplace_back(Settings::Manager::getString("skyatmosphere", "Models"));
if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models")))
models.emplace_back(Settings::Manager::getString("skynight02", "Models"));
models.emplace_back(Settings::Manager::getString("skynight01", "Models"));
models.emplace_back(Settings::Manager::getString("skyclouds", "Models"));
models.emplace_back(Settings::Manager::getString("weatherashcloud", "Models"));
models.emplace_back(Settings::Manager::getString("weatherblightcloud", "Models"));
models.emplace_back(Settings::Manager::getString("weathersnow", "Models"));
models.emplace_back(Settings::Manager::getString("weatherblizzard", "Models"));
textures.emplace_back("textures/tx_mooncircle_full_s.dds");
textures.emplace_back("textures/tx_mooncircle_full_m.dds");

@ -109,7 +109,7 @@ namespace MWScript
runtime.pop();
if (count<0)
throw std::runtime_error ("second argument for AddItem must be non-negative");
count = static_cast<uint16_t>(count);
// no-op
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)
{
ESM::GlobalScript script;
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);
if (iter==mScripts.end())

@ -73,7 +73,7 @@ namespace MWScript
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.
///
/// \return Known type?

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

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

@ -49,7 +49,7 @@ namespace MWScript
std::vector<MWWorld::Ptr> actors;
MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors);
for (auto& actor : actors)
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff);
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false);
}
template<class R>
@ -363,7 +363,7 @@ namespace MWScript
}
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
{
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);
@ -902,7 +902,7 @@ namespace MWScript
// This approach can be used to create elevators.
moveStandingActors(ptr, diff);
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.
moveStandingActors(ptr, diff);
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(const FFmpeg_Decoder &rhs);
FFmpeg_Decoder(const VFS::Manager* vfs);
public:
explicit FFmpeg_Decoder(const VFS::Manager* vfs);
virtual ~FFmpeg_Decoder();
friend class SoundManager;
};
#ifndef DEFAULT_DECODER
#define DEFAULT_DECODER (::MWSound::FFmpeg_Decoder)
#endif
}
#endif

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

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

@ -141,7 +141,7 @@ namespace MWSound
public:
SoundManager(const VFS::Manager* vfs, bool useSound);
virtual ~SoundManager();
~SoundManager() 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)
return true;
return (compare->mTimeStamp <= mOldestSlotVisited->mTimeStamp);
}
bool MWState::QuickSaveManager::shouldCreateNewSlot()
bool MWState::QuickSaveManager::shouldCreateNewSlot() const
{
return (mSlotsVisited < mMaxSaves);
}

@ -13,8 +13,8 @@ namespace MWState{
unsigned int mSlotsVisited;
const Slot *mOldestSlotVisited;
private:
bool shouldCreateNewSlot();
bool isOldestSave(const Slot *compare);
bool shouldCreateNewSlot() const;
bool isOldestSave(const Slot *compare) const;
public:
QuickSaveManager(std::string &saveName, unsigned int maxSaves);
///< 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:
MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval);
MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval, contentFileMap);
break;
case ESM::REC_GMAP:
@ -515,6 +515,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
character->getPath().filename().string());
MWBase::Environment::get().getWindowManager()->setNewGame(false);
MWBase::Environment::get().getWorld()->saveLoaded();
MWBase::Environment::get().getWorld()->setupPlayer();
MWBase::Environment::get().getWorld()->renderPlayer();
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)
: mStore (store), mReader (reader),
mIdCache (Settings::Manager::getInt("pointers cache size", "Cells"), std::pair<std::string, CellStore *> ("", (CellStore*)nullptr)),
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)
{
@ -282,8 +284,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell,
MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name)
{
// First check the cache
for (std::vector<std::pair<std::string, CellStore *> >::iterator iter (mIdCache.begin());
iter!=mIdCache.end(); ++iter)
for (IdCache::iterator iter (mIdCache.begin()); iter!=mIdCache.end(); ++iter)
if (iter->first==name && iter->second)
{
Ptr ptr = getPtr (name, *iter->second);

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

@ -90,10 +90,24 @@ MWWorld::ResolutionListener::~ResolutionListener()
{
if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty())
{
for(const auto&& ptr : mStore)
ptr.getRefData().setCount(0);
try
{
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);
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;
}
}

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

@ -1,6 +1,8 @@
#ifndef GAME_MWWORLD_CUSTOMDATA_H
#define GAME_MWWORLD_CUSTOMDATA_H
#include <memory>
namespace MWClass
{
class CreatureCustomData;
@ -19,7 +21,7 @@ namespace MWWorld
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.
@ -38,6 +40,15 @@ namespace MWWorld
virtual MWClass::CreatureLevListCustomData& asCreatureLevListCustomData();
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

@ -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
@ -218,49 +269,7 @@ int ESMStore::getRefCount(const std::string& id) const
void ESMStore::validate()
{
// Cache first class from store - we will use it if current class is not found
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);
}
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mStatic);
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
{
return 1 // DYNA (dynamic name counter)
@ -384,12 +401,14 @@ void ESMStore::validate()
case ESM::REC_ENCH:
case ESM::REC_SPEL:
case ESM::REC_WEAP:
case ESM::REC_NPC_:
case ESM::REC_LEVI:
case ESM::REC_LEVC:
mStores[type]->read (reader);
return true;
case ESM::REC_NPC_:
case ESM::REC_CREA:
case ESM::REC_CONT:
mStores[type]->read (reader);
mStores[type]->read (reader, true);
return true;
case ESM::REC_DYNA:

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

Loading…
Cancel
Save