1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-30 16:15:31 +00:00

Merge remote-tracking branch 'upstream/master' into why_are_the_christmas_lights_still_up

This commit is contained in:
glassmancody.info 2021-04-12 01:09:52 -07:00
commit 582f7b52cf
111 changed files with 1756 additions and 1205 deletions

View file

@ -30,6 +30,29 @@ stages:
paths: paths:
- build/install/ - build/install/
Coverity:
extends: .Debian
only:
- schedules
before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
- 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
- cd build
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build . -- -j $(nproc)
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: 2h
Debian_GCC: Debian_GCC:
extends: .Debian extends: .Debian
cache: cache:
@ -94,6 +117,7 @@ Debian_Clang_tests:
BUILD_TESTS_ONLY: 1 BUILD_TESTS_ONLY: 1
MacOS: MacOS:
image: macos-11-xcode-12
tags: tags:
- macos - macos
stage: build stage: build
@ -104,13 +128,17 @@ MacOS:
- rm -fr build/* # remove anything in the build directory - rm -fr build/* # remove anything in the build directory
- CI/before_install.osx.sh - CI/before_install.osx.sh
- CI/before_script.osx.sh - CI/before_script.osx.sh
- cd build; make -j2 package - cd build; make -j $(sysctl -n hw.logicalcpu) package
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done
artifacts: artifacts:
paths: paths:
- build/OpenMW-*.dmg - build/OpenMW-*.dmg
- "build/**/*.log" - "build/**/*.log"
macOS10.15_Xcode11:
extends: MacOS
image: macos-10.15-xcode-11
variables: &engine-targets variables: &engine-targets
targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard"
package: "Engine" package: "Engine"

View file

@ -10,7 +10,7 @@ addons:
- sourceline: 'ppa:openmw/openmw' - sourceline: 'ppa:openmw/openmw'
packages: [ packages: [
# Dev # Dev
build-essential, cmake, clang-tools, ccache, build-essential, cmake, clang-tools-9, ccache,
# Boost # Boost
libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev, libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev,
# FFmpeg # FFmpeg
@ -37,8 +37,8 @@ matrix:
os: linux os: linux
dist: focal dist: focal
env: env:
- MATRIX_EVAL="CC=clang && CXX=clang++" - MATRIX_EVAL="CC=clang-9 && CXX=clang++-9"
- ANALYZE="scan-build --force-analyze-debug-code --use-cc clang --use-c++ clang++" - ANALYZE="scan-build-9 --force-analyze-debug-code --use-cc clang-9 --use-c++ clang++-9"
compiler: clang compiler: clang
before_install: before_install:
@ -66,11 +66,13 @@ deploy:
repo: OpenMW/openmw repo: OpenMW/openmw
notifications: notifications:
email: email:
if: repository_slug = OpenMW/openmw AND branch = master
recipients: recipients:
- corrmage+travis-ci@gmail.com - corrmage+travis-ci@gmail.com
on_success: change on_success: change
on_failure: always on_failure: always
irc: irc:
if: repository_slug = OpenMW/openmw AND branch = master
channels: channels:
- "chat.freenode.net#openmw" - "chat.freenode.net#openmw"
on_success: change on_success: change

View file

@ -185,6 +185,7 @@ Programmers
sergoz sergoz
ShadowRadiance ShadowRadiance
Siimacore Siimacore
Simon Meulenbeek (simonmb)
sir_herrbatka sir_herrbatka
smbas smbas
Sophie Kirschner (pineapplemachine) Sophie Kirschner (pineapplemachine)
@ -198,6 +199,7 @@ Programmers
Sylvain Thesnieres (Garvek) Sylvain Thesnieres (Garvek)
t6 t6
terrorfisch terrorfisch
Tess (tescoShoppah)
thegriglat thegriglat
Thomas Luppi (Digmaster) Thomas Luppi (Digmaster)
tlmullis tlmullis

View file

@ -115,6 +115,8 @@
Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs
Bug #5912: ImprovedBound mod doesn't work Bug #5912: ImprovedBound mod doesn't work
Bug #5914: BM: The Swimmer can't reach destination Bug #5914: BM: The Swimmer can't reach destination
Bug #5923: Clicking on empty spaces between journal entries might show random topics
Bug #5934: AddItem command doesn't accept negative values
Feature #390: 3rd person look "over the shoulder" Feature #390: 3rd person look "over the shoulder"
Feature #832: OpenMW-CS: Handle deleted references Feature #832: OpenMW-CS: Handle deleted references
Feature #1536: Show more information about level on menu Feature #1536: Show more information about level on menu
@ -122,6 +124,7 @@
Feature #2404: Levelled List can not be placed into a container Feature #2404: Levelled List can not be placed into a container
Feature #2686: Timestamps in openmw.log Feature #2686: Timestamps in openmw.log
Feature #3171: OpenMW-CS: Instance drag selection Feature #3171: OpenMW-CS: Instance drag selection
Feature #3983: Wizard: Add link to buy Morrowind
Feature #4894: Consider actors as obstacles for pathfinding Feature #4894: Consider actors as obstacles for pathfinding
Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing
Feature #4977: Use the "default icon.tga" when an item's icon is not found Feature #4977: Use the "default icon.tga" when an item's icon is not found
@ -133,6 +136,7 @@
Feature #5456: Basic collada animation support Feature #5456: Basic collada animation support
Feature #5457: Realistic diagonal movement Feature #5457: Realistic diagonal movement
Feature #5486: Fixes trainers to choose their training skills based on their base skill points Feature #5486: Fixes trainers to choose their training skills based on their base skill points
Feature #5511: Add in game option to toggle HRTF support in OpenMW
Feature #5519: Code Patch tab in launcher Feature #5519: Code Patch tab in launcher
Feature #5524: Resume failed script execution after reload Feature #5524: Resume failed script execution after reload
Feature #5545: Option to allow stealing from an unconscious NPC during combat Feature #5545: Option to allow stealing from an unconscious NPC during combat
@ -147,6 +151,7 @@
Feature #5730: Add graphic herbalism option to the launcher and documents Feature #5730: Add graphic herbalism option to the launcher and documents
Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used.
Feature #5813: Instanced groundcover support Feature #5813: Instanced groundcover support
Feature #5814: Bsatool should be able to create BSA archives, not only to extract it
Feature #5828: Support more than 8 lights Feature #5828: Support more than 8 lights
Feature #5910: Fall back to delta time when physics can't keep up Feature #5910: Fall back to delta time when physics can't keep up
Task #5480: Drop Qt4 support Task #5480: Drop Qt4 support

View file

@ -16,7 +16,7 @@ cmake \
-D CMAKE_CXX_FLAGS="-stdlib=libc++" \ -D CMAKE_CXX_FLAGS="-stdlib=libc++" \
-D CMAKE_C_FLAGS_RELEASE="-g -O0" \ -D CMAKE_C_FLAGS_RELEASE="-g -O0" \
-D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \ -D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.12" \ -D CMAKE_OSX_DEPLOYMENT_TARGET="10.14" \
-D CMAKE_BUILD_TYPE=RELEASE \ -D CMAKE_BUILD_TYPE=RELEASE \
-D OPENMW_OSX_DEPLOYMENT=TRUE \ -D OPENMW_OSX_DEPLOYMENT=TRUE \
-D BUILD_OPENMW=TRUE \ -D BUILD_OPENMW=TRUE \

View file

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

View file

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

View file

@ -7,11 +7,14 @@
#include <QFileDialog> #include <QFileDialog>
#include <QCompleter> #include <QCompleter>
#include <QProxyStyle> #include <QProxyStyle>
#include <QString>
#include <components/contentselector/view/contentselector.hpp> #include <components/contentselector/view/contentselector.hpp>
#include <components/contentselector/model/esmfile.hpp> #include <components/contentselector/model/esmfile.hpp>
#include <cmath> #include <cmath>
#include "utils/openalutil.hpp"
Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
Settings::Manager &engineSettings, QWidget *parent) Settings::Manager &engineSettings, QWidget *parent)
: QWidget(parent) : QWidget(parent)
@ -21,7 +24,17 @@ Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
setObjectName ("AdvancedPage"); setObjectName ("AdvancedPage");
setupUi(this); setupUi(this);
for(const char * name : Launcher::enumerateOpenALDevices())
{
audioDeviceSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name));
}
for(const char * name : Launcher::enumerateOpenALDevicesHrtf())
{
hrtfProfileSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name));
}
loadSettings(); loadSettings();
mCellNameCompleter.setModel(&mCellNameCompleterModel); mCellNameCompleter.setModel(&mCellNameCompleterModel);
startDefaultCharacterAtField->setCompleter(&mCellNameCompleter); startDefaultCharacterAtField->setCompleter(&mCellNameCompleter);
} }
@ -135,6 +148,34 @@ bool Launcher::AdvancedPage::loadSettings()
lightingMethodComboBox->setCurrentIndex(lightingMethod); lightingMethodComboBox->setCurrentIndex(lightingMethod);
} }
// Audio
{
std::string selectedAudioDevice = mEngineSettings.getString("device", "Sound");
if (selectedAudioDevice.empty() == false)
{
int audioDeviceIndex = audioDeviceSelectorComboBox->findData(QString::fromStdString(selectedAudioDevice));
if (audioDeviceIndex != -1)
{
audioDeviceSelectorComboBox->setCurrentIndex(audioDeviceIndex);
}
}
int hrtfEnabledIndex = mEngineSettings.getInt("hrtf enable", "Sound");
if (hrtfEnabledIndex >= -1 && hrtfEnabledIndex <= 1)
{
enableHRTFComboBox->setCurrentIndex(hrtfEnabledIndex + 1);
}
std::string selectedHRTFProfile = mEngineSettings.getString("hrtf", "Sound");
if (selectedHRTFProfile.empty() == false)
{
int hrtfProfileIndex = hrtfProfileSelectorComboBox->findData(QString::fromStdString(selectedHRTFProfile));
if (hrtfProfileIndex != -1)
{
hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex);
}
}
}
// Camera // Camera
{ {
loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera"); loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
@ -259,6 +300,33 @@ void Launcher::AdvancedPage::saveSettings()
static std::array<std::string, 3> lightingMethodMap = {"legacy", "shaders compatibility", "shaders"}; static std::array<std::string, 3> lightingMethodMap = {"legacy", "shaders compatibility", "shaders"};
mEngineSettings.setString("lighting method", "Shaders", lightingMethodMap[lightingMethodComboBox->currentIndex()]); mEngineSettings.setString("lighting method", "Shaders", lightingMethodMap[lightingMethodComboBox->currentIndex()]);
} }
// Audio
{
int audioDeviceIndex = audioDeviceSelectorComboBox->currentIndex();
if (audioDeviceIndex != 0)
{
mEngineSettings.setString("device", "Sound", audioDeviceSelectorComboBox->currentText().toUtf8().constData());
}
else
{
mEngineSettings.setString("device", "Sound", "");
}
int hrtfEnabledIndex = enableHRTFComboBox->currentIndex() - 1;
if (hrtfEnabledIndex != mEngineSettings.getInt("hrtf enable", "Sound"))
{
mEngineSettings.setInt("hrtf enable", "Sound", hrtfEnabledIndex);
}
int selectedHRTFProfileIndex = hrtfProfileSelectorComboBox->currentIndex();
if (selectedHRTFProfileIndex != 0)
{
mEngineSettings.setString("hrtf", "Sound", hrtfProfileSelectorComboBox->currentText().toUtf8().constData());
}
else
{
mEngineSettings.setString("hrtf", "Sound", "");
}
}
// Camera // Camera
{ {

View file

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

View file

@ -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;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -228,7 +228,6 @@ namespace MWMechanics
const osg::Vec3f vActorPos(pos.asVec3()); const osg::Vec3f vActorPos(pos.asVec3());
const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3());
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS); storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS);
@ -236,13 +235,14 @@ namespace MWMechanics
if (isRangedCombat) if (isRangedCombat)
{ {
// rotate actor taking into account target movement direction and projectile speed // rotate actor taking into account target movement direction and projectile speed
vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); osg::Vec3f vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir); storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
} }
else else
{ {
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, false);
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
} }
@ -698,7 +698,7 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t
// idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same // idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same
osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3(); osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3();
osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, true);
float distToTarget = vDirToTarget.length(); float distToTarget = vDirToTarget.length();
osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos; osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos;

View file

@ -2955,7 +2955,7 @@ void CharacterController::updateHeadTracking(float duration)
} }
else else
// no head node to look at, fall back to look at center of collision box // no head node to look at, fall back to look at center of collision box
direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget); direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget, false);
} }
direction.normalize(); direction.normalize();

View file

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

View file

@ -179,6 +179,7 @@ bool Actor::setPosition(const osg::Vec3f& position)
if (mSkipSimulation) if (mSkipSimulation)
return false; return false;
bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged; bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
updateWorldPosition();
applyOffsetChange(); applyOffsetChange();
mPreviousPosition = mPosition; mPreviousPosition = mPosition;
mPosition = position; mPosition = position;

View file

@ -317,7 +317,7 @@ namespace MWPhysics
// init // init
for (auto& data : actorsData) for (auto& data : actorsData)
data.updatePosition(); data.updatePosition(mCollisionWorld);
mPrevStepCount = numSteps; mPrevStepCount = numSteps;
mRemainingSteps = numSteps; mRemainingSteps = numSteps;
mTimeAccum = timeAccum; mTimeAccum = timeAccum;

View file

@ -60,6 +60,22 @@
#include "movementsolver.hpp" #include "movementsolver.hpp"
#include "mtphysics.hpp" #include "mtphysics.hpp"
namespace
{
bool canMoveToWaterSurface(const MWPhysics::Actor* physicActor, const float waterlevel, btCollisionWorld* world)
{
if (!physicActor)
return false;
const float halfZ = physicActor->getHalfExtents().z();
const osg::Vec3f actorPosition = physicActor->getPosition();
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
MWPhysics::ActorTracer tracer;
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world);
return (tracer.mFraction >= 1.0f);
}
}
namespace MWPhysics namespace MWPhysics
{ {
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode) PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
@ -347,16 +363,7 @@ namespace MWPhysics
bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel)
{ {
const Actor* physicActor = getActor(actor); return ::canMoveToWaterSurface(getActor(actor), waterlevel, mCollisionWorld.get());
if (!physicActor)
return false;
const float halfZ = physicActor->getHalfExtents().z();
const osg::Vec3f actorPosition = physicActor->getPosition();
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
ActorTracer tracer;
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, mCollisionWorld.get());
return (tracer.mFraction >= 1.0f);
} }
osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const
@ -772,16 +779,10 @@ namespace MWPhysics
const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects(); const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects();
bool waterCollision = false; bool waterCollision = false;
bool moveToWaterSurface = false;
if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude())
{ {
if (!world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3()))) if (physicActor->getCollisionMode() || !world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3())))
waterCollision = true; waterCollision = true;
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(character, waterlevel))
{
moveToWaterSurface = true;
waterCollision = true;
}
} }
physicActor->setCanWaterWalk(waterCollision); physicActor->setCanWaterWalk(waterCollision);
@ -794,7 +795,7 @@ namespace MWPhysics
if (!willSimulate) if (!willSimulate)
standingOn = physicActor->getStandingOnPtr(); standingOn = physicActor->getStandingOnPtr();
actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel); actorsFrameData.emplace_back(std::move(physicActor), standingOn, waterCollision, movement, slowFall, waterlevel);
} }
mMovementQueue.clear(); mMovementQueue.clear();
return actorsFrameData; return actorsFrameData;
@ -937,9 +938,9 @@ namespace MWPhysics
} }
ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn,
bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel) bool waterCollision, osg::Vec3f movement, float slowFall, float waterlevel)
: mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn),
mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface), mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision),
mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos() mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos()
{ {
const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWBase::World *world = MWBase::Environment::get().getWorld();
@ -953,7 +954,7 @@ namespace MWPhysics
mWasOnGround = actor->getOnGround(); mWasOnGround = actor->getOnGround();
} }
void ActorFrameData::updatePosition() void ActorFrameData::updatePosition(btCollisionWorld* world)
{ {
mActorRaw->updateWorldPosition(); mActorRaw->updateWorldPosition();
// If physics runs "fast enough", position are interpolated without simulation // If physics runs "fast enough", position are interpolated without simulation
@ -961,10 +962,10 @@ namespace MWPhysics
// regardless of simulation speed. // regardless of simulation speed.
mActorRaw->applyOffsetChange(); mActorRaw->applyOffsetChange();
mPosition = mActorRaw->getPosition(); mPosition = mActorRaw->getPosition();
if (mMoveToWaterSurface) if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world))
{ {
mPosition.z() = mWaterlevel; mPosition.z() = mWaterlevel;
MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z()); MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z(), false);
} }
mOldHeight = mPosition.z(); mOldHeight = mPosition.z();
mRefpos = mActorRaw->getPtr().getRefData().getPosition(); mRefpos = mActorRaw->getPtr().getRefData().getPosition();

View file

@ -79,7 +79,7 @@ namespace MWPhysics
struct ActorFrameData struct ActorFrameData
{ {
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel); ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
void updatePosition(); void updatePosition(btCollisionWorld* world);
std::weak_ptr<Actor> mActor; std::weak_ptr<Actor> mActor;
Actor* mActorRaw; Actor* mActorRaw;
MWWorld::Ptr mStandingOn; MWWorld::Ptr mStandingOn;
@ -90,7 +90,7 @@ namespace MWPhysics
bool mDidJump; bool mDidJump;
bool mFloatToSurface; bool mFloatToSurface;
bool mNeedLand; bool mNeedLand;
bool mMoveToWaterSurface; bool mWaterCollision;
float mWaterlevel; float mWaterlevel;
float mSlowFall; float mSlowFall;
float mOldHeight; float mOldHeight;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -32,7 +32,7 @@ namespace MWScript
std::vector<MWWorld::Ptr> actors; std::vector<MWWorld::Ptr> actors;
MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors);
for (auto& actor : actors) for (auto& actor : actors)
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff); MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false);
} }
template<class R> template<class R>
@ -290,11 +290,11 @@ namespace MWScript
float terrainHeight = -std::numeric_limits<float>::max(); float terrainHeight = -std::numeric_limits<float>::max();
if (ptr.getCell()->isExterior()) if (ptr.getCell()->isExterior())
terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(curPos); terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(curPos);
if (pos < terrainHeight) if (pos < terrainHeight)
pos = terrainHeight; pos = terrainHeight;
} }
newPos[2] = pos; newPos[2] = pos;
} }
else else
@ -303,7 +303,7 @@ namespace MWScript
} }
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr, dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos)); MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true));
} }
}; };
@ -439,7 +439,7 @@ namespace MWScript
} }
else else
{ {
ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true); ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true, true);
} }
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(base,ptr); dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(base,ptr);
@ -726,7 +726,7 @@ namespace MWScript
// This approach can be used to create elevators. // This approach can be used to create elevators.
moveStandingActors(ptr, diff); moveStandingActors(ptr, diff);
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr, dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false));
} }
}; };
@ -762,7 +762,7 @@ namespace MWScript
// This approach can be used to create elevators. // This approach can be used to create elevators.
moveStandingActors(ptr, diff); moveStandingActors(ptr, diff);
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr, dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false));
} }
}; };

View file

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

View file

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

View file

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

View file

@ -461,7 +461,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
case ESM::REC_GSCR: case ESM::REC_GSCR:
MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval); MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval, contentFileMap);
break; break;
case ESM::REC_GMAP: case ESM::REC_GMAP:

View file

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

View file

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

View file

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

View file

@ -153,7 +153,7 @@ namespace MWWorld
virtual ~ContainerStore(); virtual ~ContainerStore();
virtual ContainerStore* clone() { return new ContainerStore(*this); } virtual std::unique_ptr<ContainerStore> clone() { return std::make_unique<ContainerStore>(*this); }
ConstContainerStoreIterator cbegin (int mask = Type_All) const; ConstContainerStoreIterator cbegin (int mask = Type_All) const;
ConstContainerStoreIterator cend() const; ConstContainerStoreIterator cend() const;

View file

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

View file

@ -123,7 +123,7 @@ namespace MWWorld
InventoryStore& operator= (const InventoryStore& store); InventoryStore& operator= (const InventoryStore& store);
InventoryStore* clone() override { return new InventoryStore(*this); } std::unique_ptr<ContainerStore> clone() override { return std::make_unique<InventoryStore>(*this); }
ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true, bool resolve = true) override; ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true, bool resolve = true) override;
///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed)

View file

@ -40,8 +40,6 @@ namespace MWWorld
void RefData::cleanup() void RefData::cleanup()
{ {
mBaseNode = nullptr; mBaseNode = nullptr;
delete mCustomData;
mCustomData = nullptr; mCustomData = nullptr;
} }
@ -223,21 +221,20 @@ namespace MWWorld
return mPosition; return mPosition;
} }
void RefData::setCustomData (CustomData *data) void RefData::setCustomData(std::unique_ptr<CustomData>&& value) noexcept
{ {
mChanged = true; // We do not currently track CustomData, so assume anything with a CustomData is changed mChanged = true; // We do not currently track CustomData, so assume anything with a CustomData is changed
delete mCustomData; mCustomData = std::move(value);
mCustomData = data;
} }
CustomData *RefData::getCustomData() CustomData *RefData::getCustomData()
{ {
return mCustomData; return mCustomData.get();
} }
const CustomData *RefData::getCustomData() const const CustomData *RefData::getCustomData() const
{ {
return mCustomData; return mCustomData.get();
} }
bool RefData::hasChanged() const bool RefData::hasChanged() const

View file

@ -5,8 +5,10 @@
#include <components/esm/animationstate.hpp> #include <components/esm/animationstate.hpp>
#include "../mwscript/locals.hpp" #include "../mwscript/locals.hpp"
#include "../mwworld/customdata.hpp"
#include <string> #include <string>
#include <memory>
namespace SceneUtil namespace SceneUtil
{ {
@ -44,7 +46,7 @@ namespace MWWorld
ESM::AnimationState mAnimationState; ESM::AnimationState mAnimationState;
CustomData *mCustomData; std::unique_ptr<CustomData> mCustomData;
void copy (const RefData& refData); void copy (const RefData& refData);
@ -68,6 +70,7 @@ namespace MWWorld
/// perform these operations). /// perform these operations).
RefData (const RefData& refData); RefData (const RefData& refData);
RefData (RefData&& other) noexcept = default;
~RefData(); ~RefData();
@ -76,6 +79,7 @@ namespace MWWorld
/// perform this operations). /// perform this operations).
RefData& operator= (const RefData& refData); RefData& operator= (const RefData& refData);
RefData& operator= (RefData&& other) noexcept = default;
/// Return base node (can be a null pointer). /// Return base node (can be a null pointer).
SceneUtil::PositionAttitudeTransform* getBaseNode(); SceneUtil::PositionAttitudeTransform* getBaseNode();
@ -117,7 +121,7 @@ namespace MWWorld
void setPosition (const ESM::Position& pos); void setPosition (const ESM::Position& pos);
const ESM::Position& getPosition() const; const ESM::Position& getPosition() const;
void setCustomData (CustomData *data); void setCustomData(std::unique_ptr<CustomData>&& value) noexcept;
///< Set custom data (potentially replacing old custom data). The ownership of \a data is ///< Set custom data (potentially replacing old custom data). The ownership of \a data is
/// transferred to this. /// transferred to this.

View file

@ -36,7 +36,6 @@ namespace Loading
namespace DetourNavigator namespace DetourNavigator
{ {
struct Navigator; struct Navigator;
class Water;
} }
namespace MWRender namespace MWRender

View file

@ -862,19 +862,6 @@ namespace MWWorld
if (reference == getPlayerPtr()) if (reference == getPlayerPtr())
throw std::runtime_error("can not disable player object"); throw std::runtime_error("can not disable player object");
// A common pattern to teleport NPC in scripts is a sequence of SetPos/Disable/Enable
// Disable/Enable create a new physics actor, and so the SetPos call is lost
// Call moveObject so that the newly created physics actor will have up-to-date position
if (reference.getClass().isActor())
{
auto* physactor = mPhysics->getActor(reference);
if (physactor)
{
physactor->applyOffsetChange();
const auto position = physactor->getSimulationPosition();
moveObject(reference, position.x(), position.y(), position.z(), true);
}
}
reference.getRefData().disable(); reference.getRefData().disable();
if (reference.getCellRef().getRefNum().hasContentFile()) if (reference.getCellRef().getRefNum().hasContentFile())
@ -1251,7 +1238,7 @@ namespace MWWorld
return newPtr; return newPtr;
} }
MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive) MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive)
{ {
int cellX, cellY; int cellX, cellY;
positionToIndex(x, y, cellX, cellY); positionToIndex(x, y, cellX, cellY);
@ -1266,21 +1253,14 @@ namespace MWWorld
return moveObject(ptr, cell, x, y, z, movePhysics); return moveObject(ptr, cell, x, y, z, movePhysics);
} }
MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive) MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive)
{
return moveObjectImp(ptr, x, y, z, true, moveToActive);
}
MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec)
{ {
auto* actor = mPhysics->getActor(ptr); auto* actor = mPhysics->getActor(ptr);
if (actor) if (actor)
{
actor->adjustPosition(vec); actor->adjustPosition(vec);
return ptr;
}
osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec; osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec;
return moveObject(ptr, newpos.x(), newpos.y(), newpos.z()); return moveObject(ptr, newpos.x(), newpos.y(), newpos.z(), false, moveToActive && ptr != getPlayerPtr());
} }
void World::scaleObject (const Ptr& ptr, float scale) void World::scaleObject (const Ptr& ptr, float scale)
@ -1546,7 +1526,7 @@ namespace MWWorld
auto* physactor = mPhysics->getActor(actor); auto* physactor = mPhysics->getActor(actor);
assert(physactor); assert(physactor);
const auto position = physactor->getSimulationPosition(); const auto position = physactor->getSimulationPosition();
moveObjectImp(actor, position.x(), position.y(), position.z(), false); moveObject(actor, position.x(), position.y(), position.z(), false, false);
} }
} }
@ -1556,7 +1536,7 @@ namespace MWWorld
auto* physactor = mPhysics->getActor(*player); auto* physactor = mPhysics->getActor(*player);
assert(physactor); assert(physactor);
const auto position = physactor->getSimulationPosition(); const auto position = physactor->getSimulationPosition();
moveObjectImp(*player, position.x(), position.y(), position.z(), false); moveObject(*player, position.x(), position.y(), position.z(), false, false);
} }
} }
@ -3885,14 +3865,12 @@ namespace MWWorld
return false; return false;
} }
osg::Vec3f World::aimToTarget(const ConstPtr &actor, const ConstPtr &target) osg::Vec3f World::aimToTarget(const ConstPtr &actor, const ConstPtr &target, bool isRangedCombat)
{ {
osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3();
osg::Vec3f weaponHalfExtents = mPhysics->getHalfExtents(actor); float heightRatio = isRangedCombat ? 2.f * Constants::TorsoHeight : 1.f;
osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); weaponPos.z() += mPhysics->getHalfExtents(actor).z() * heightRatio;
osg::Vec3f targetHalfExtents = mPhysics->getHalfExtents(target); osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target);
weaponPos.z() += weaponHalfExtents.z() * 2 * Constants::TorsoHeight;
targetPos.z() += targetHalfExtents.z();
return (targetPos - weaponPos); return (targetPos - weaponPos);
} }

View file

@ -60,8 +60,6 @@ namespace ToUTF8
class Utf8Encoder; class Utf8Encoder;
} }
struct ContentLoader;
namespace MWPhysics namespace MWPhysics
{ {
class Object; class Object;
@ -138,9 +136,6 @@ namespace MWWorld
void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags); void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags);
Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false);
///< @return an updated Ptr in case the Ptr's cell changes
Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos);
void updateSoundListener(); void updateSoundListener();
@ -376,13 +371,13 @@ namespace MWWorld
void undeleteObject (const Ptr& ptr) override; void undeleteObject (const Ptr& ptr) override;
MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive=false) override; MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false) override;
///< @return an updated Ptr in case the Ptr's cell changes ///< @return an updated Ptr in case the Ptr's cell changes
MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override; MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override;
///< @return an updated Ptr ///< @return an updated Ptr
MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec) override; MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive) override;
///< @return an updated Ptr ///< @return an updated Ptr
void scaleObject (const Ptr& ptr, float scale) override; void scaleObject (const Ptr& ptr, float scale) override;
@ -705,7 +700,7 @@ namespace MWWorld
/// Return a vector aiming the actor's weapon towards a target. /// Return a vector aiming the actor's weapon towards a target.
/// @note The length of the vector is the distance between actor and target. /// @note The length of the vector is the distance between actor and target.
osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) override; osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target, bool isRangedCombat) override;
/// Return the distance between actor's weapon and target's collision box. /// Return the distance between actor's weapon and target's collision box.
float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) override; float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) override;

View file

@ -13,6 +13,7 @@ if (GTEST_FOUND AND GMOCK_FOUND)
mwdialogue/test_keywordsearch.cpp mwdialogue/test_keywordsearch.cpp
esm/test_fixed_string.cpp esm/test_fixed_string.cpp
esm/variant.cpp
misc/test_stringops.cpp misc/test_stringops.cpp
misc/test_endianness.cpp misc/test_endianness.cpp

View file

@ -0,0 +1,513 @@
#include <components/esm/variant.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/esmreader.hpp>
#include <components/esm/loadglob.hpp>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <functional>
namespace
{
using namespace testing;
using namespace ESM;
Variant makeVariant(VarType type)
{
Variant v;
v.setType(type);
return v;
}
Variant makeVariant(VarType type, int value)
{
Variant v;
v.setType(type);
v.setInteger(value);
return v;
}
TEST(ESMVariantTest, move_constructed_should_have_data)
{
Variant a(int{42});
const Variant b(std::move(a));
ASSERT_EQ(b.getInteger(), 42);
}
TEST(ESMVariantTest, copy_constructed_is_equal_to_source)
{
const Variant a(int{42});
const Variant b(a);
ASSERT_EQ(a, b);
}
TEST(ESMVariantTest, copy_constructed_does_not_share_data_with_source)
{
const Variant a(int{42});
Variant b(a);
b.setInteger(13);
ASSERT_EQ(a.getInteger(), 42);
ASSERT_EQ(b.getInteger(), 13);
}
TEST(ESMVariantTest, move_assigned_should_have_data)
{
Variant b;
{
Variant a(int{42});
b = std::move(a);
}
ASSERT_EQ(b.getInteger(), 42);
}
TEST(ESMVariantTest, copy_assigned_is_equal_to_source)
{
const Variant a(int{42});
Variant b;
b = a;
ASSERT_EQ(a, b);
}
TEST(ESMVariantTest, not_equal_is_negation_of_equal)
{
const Variant a(int{42});
Variant b;
b = a;
ASSERT_TRUE(!(a != b));
}
TEST(ESMVariantTest, different_types_are_not_equal)
{
ASSERT_NE(Variant(int{42}), Variant(float{2.7f}));
}
struct ESMVariantWriteToOStreamTest : TestWithParam<std::tuple<Variant, std::string>> {};
TEST_P(ESMVariantWriteToOStreamTest, should_write)
{
const auto [variant, result] = GetParam();
std::ostringstream s;
s << variant;
ASSERT_EQ(s.str(), result);
}
INSTANTIATE_TEST_SUITE_P(VariantAsString, ESMVariantWriteToOStreamTest, Values(
std::make_tuple(Variant(), "variant none"),
std::make_tuple(Variant(int{42}), "variant long: 42"),
std::make_tuple(Variant(float{2.7f}), "variant float: 2.7"),
std::make_tuple(Variant(std::string("foo")), "variant string: \"foo\""),
std::make_tuple(makeVariant(VT_Unknown), "variant unknown"),
std::make_tuple(makeVariant(VT_Short, 42), "variant short: 42"),
std::make_tuple(makeVariant(VT_Int, 42), "variant int: 42")
));
struct ESMVariantGetTypeTest : Test {};
TEST(ESMVariantGetTypeTest, default_constructed_should_return_none)
{
ASSERT_EQ(Variant().getType(), VT_None);
}
TEST(ESMVariantGetTypeTest, for_constructed_from_int_should_return_long)
{
ASSERT_EQ(Variant(int{}).getType(), VT_Long);
}
TEST(ESMVariantGetTypeTest, for_constructed_from_float_should_return_float)
{
ASSERT_EQ(Variant(float{}).getType(), VT_Float);
}
TEST(ESMVariantGetTypeTest, for_constructed_from_lvalue_string_should_return_string)
{
const std::string string;
ASSERT_EQ(Variant(string).getType(), VT_String);
}
TEST(ESMVariantGetTypeTest, for_constructed_from_rvalue_string_should_return_string)
{
ASSERT_EQ(Variant(std::string{}).getType(), VT_String);
}
struct ESMVariantGetIntegerTest : Test {};
TEST(ESMVariantGetIntegerTest, for_default_constructed_should_throw_exception)
{
ASSERT_THROW(Variant().getInteger(), std::runtime_error);
}
TEST(ESMVariantGetIntegerTest, for_constructed_from_int_should_return_same_value)
{
const Variant variant(int{42});
ASSERT_EQ(variant.getInteger(), 42);
}
TEST(ESMVariantGetIntegerTest, for_constructed_from_float_should_return_casted_to_int)
{
const Variant variant(float{2.7});
ASSERT_EQ(variant.getInteger(), 2);
}
TEST(ESMVariantGetIntegerTest, for_constructed_from_string_should_throw_exception)
{
const Variant variant(std::string("foo"));
ASSERT_THROW(variant.getInteger(), std::runtime_error);
}
TEST(ESMVariantGetFloatTest, for_default_constructed_should_throw_exception)
{
ASSERT_THROW(Variant().getFloat(), std::runtime_error);
}
TEST(ESMVariantGetFloatTest, for_constructed_from_int_should_return_casted_to_float)
{
const Variant variant(int{42});
ASSERT_EQ(variant.getFloat(), 42);
}
TEST(ESMVariantGetFloatTest, for_constructed_from_float_should_return_same_value)
{
const Variant variant(float{2.7f});
ASSERT_EQ(variant.getFloat(), 2.7f);
}
TEST(ESMVariantGetFloatTest, for_constructed_from_string_should_throw_exception)
{
const Variant variant(std::string("foo"));
ASSERT_THROW(variant.getFloat(), std::runtime_error);
}
TEST(ESMVariantGetStringTest, for_default_constructed_should_throw_exception)
{
ASSERT_THROW(Variant().getString(), std::bad_variant_access);
}
TEST(ESMVariantGetStringTest, for_constructed_from_int_should_throw_exception)
{
const Variant variant(int{42});
ASSERT_THROW(variant.getString(), std::bad_variant_access);
}
TEST(ESMVariantGetStringTest, for_constructed_from_float_should_throw_exception)
{
const Variant variant(float{2.7});
ASSERT_THROW(variant.getString(), std::bad_variant_access);
}
TEST(ESMVariantGetStringTest, for_constructed_from_string_should_return_same_value)
{
const Variant variant(std::string("foo"));
ASSERT_EQ(variant.getString(), "foo");
}
TEST(ESMVariantSetTypeTest, for_unknown_should_reset_data)
{
Variant variant(int{42});
variant.setType(VT_Unknown);
ASSERT_THROW(variant.getInteger(), std::runtime_error);
}
TEST(ESMVariantSetTypeTest, for_none_should_reset_data)
{
Variant variant(int{42});
variant.setType(VT_None);
ASSERT_THROW(variant.getInteger(), std::runtime_error);
}
TEST(ESMVariantSetTypeTest, for_same_type_should_not_change_value)
{
Variant variant(int{42});
variant.setType(VT_Long);
ASSERT_EQ(variant.getInteger(), 42);
}
TEST(ESMVariantSetTypeTest, for_float_replaced_by_int_should_cast_float_to_int)
{
Variant variant(float{2.7f});
variant.setType(VT_Int);
ASSERT_EQ(variant.getInteger(), 2);
}
TEST(ESMVariantSetTypeTest, for_string_replaced_by_int_should_set_default_initialized_data)
{
Variant variant(std::string("foo"));
variant.setType(VT_Int);
ASSERT_EQ(variant.getInteger(), 0);
}
TEST(ESMVariantSetTypeTest, for_default_constructed_replaced_by_float_should_set_default_initialized_value)
{
Variant variant;
variant.setType(VT_Float);
ASSERT_EQ(variant.getInteger(), 0.0f);
}
TEST(ESMVariantSetTypeTest, for_float_replaced_by_short_should_cast_data_to_int)
{
Variant variant(float{2.7f});
variant.setType(VT_Short);
ASSERT_EQ(variant.getInteger(), 2);
}
TEST(ESMVariantSetTypeTest, for_float_replaced_by_long_should_cast_data_to_int)
{
Variant variant(float{2.7f});
variant.setType(VT_Long);
ASSERT_EQ(variant.getInteger(), 2);
}
TEST(ESMVariantSetTypeTest, for_int_replaced_by_float_should_cast_data_to_float)
{
Variant variant(int{42});
variant.setType(VT_Float);
ASSERT_EQ(variant.getFloat(), 42.0f);
}
TEST(ESMVariantSetTypeTest, for_int_replaced_by_string_should_set_default_initialized_data)
{
Variant variant(int{42});
variant.setType(VT_String);
ASSERT_EQ(variant.getString(), "");
}
TEST(ESMVariantSetIntegerTest, for_default_constructed_should_throw_exception)
{
Variant variant;
ASSERT_THROW(variant.setInteger(42), std::runtime_error);
}
TEST(ESMVariantSetIntegerTest, for_unknown_should_throw_exception)
{
Variant variant;
variant.setType(VT_Unknown);
ASSERT_THROW(variant.setInteger(42), std::runtime_error);
}
TEST(ESMVariantSetIntegerTest, for_default_int_should_change_value)
{
Variant variant(int{13});
variant.setInteger(42);
ASSERT_EQ(variant.getInteger(), 42);
}
TEST(ESMVariantSetIntegerTest, for_int_should_change_value)
{
Variant variant;
variant.setType(VT_Int);
variant.setInteger(42);
ASSERT_EQ(variant.getInteger(), 42);
}
TEST(ESMVariantSetIntegerTest, for_short_should_change_value)
{
Variant variant;
variant.setType(VT_Short);
variant.setInteger(42);
ASSERT_EQ(variant.getInteger(), 42);
}
TEST(ESMVariantSetIntegerTest, for_float_should_change_value)
{
Variant variant(float{2.7f});
variant.setInteger(42);
ASSERT_EQ(variant.getFloat(), 42.0f);
}
TEST(ESMVariantSetIntegerTest, for_string_should_throw_exception)
{
Variant variant(std::string{});
ASSERT_THROW(variant.setInteger(42), std::runtime_error);
}
TEST(ESMVariantSetFloatTest, for_default_constructed_should_throw_exception)
{
Variant variant;
ASSERT_THROW(variant.setFloat(2.7f), std::runtime_error);
}
TEST(ESMVariantSetFloatTest, for_unknown_should_throw_exception)
{
Variant variant;
variant.setType(VT_Unknown);
ASSERT_THROW(variant.setFloat(2.7f), std::runtime_error);
}
TEST(ESMVariantSetFloatTest, for_default_int_should_change_value)
{
Variant variant(int{13});
variant.setFloat(2.7f);
ASSERT_EQ(variant.getInteger(), 2);
}
TEST(ESMVariantSetFloatTest, for_int_should_change_value)
{
Variant variant;
variant.setType(VT_Int);
variant.setFloat(2.7f);
ASSERT_EQ(variant.getInteger(), 2);
}
TEST(ESMVariantSetFloatTest, for_short_should_change_value)
{
Variant variant;
variant.setType(VT_Short);
variant.setFloat(2.7f);
ASSERT_EQ(variant.getInteger(), 2);
}
TEST(ESMVariantSetFloatTest, for_float_should_change_value)
{
Variant variant(float{2.7f});
variant.setFloat(3.14f);
ASSERT_EQ(variant.getFloat(), 3.14f);
}
TEST(ESMVariantSetFloatTest, for_string_should_throw_exception)
{
Variant variant(std::string{});
ASSERT_THROW(variant.setFloat(2.7f), std::runtime_error);
}
TEST(ESMVariantSetStringTest, for_default_constructed_should_throw_exception)
{
Variant variant;
ASSERT_THROW(variant.setString("foo"), std::bad_variant_access);
}
TEST(ESMVariantSetStringTest, for_unknown_should_throw_exception)
{
Variant variant;
variant.setType(VT_Unknown);
ASSERT_THROW(variant.setString("foo"), std::bad_variant_access);
}
TEST(ESMVariantSetStringTest, for_default_int_should_throw_exception)
{
Variant variant(int{13});
ASSERT_THROW(variant.setString("foo"), std::bad_variant_access);
}
TEST(ESMVariantSetStringTest, for_int_should_throw_exception)
{
Variant variant;
variant.setType(VT_Int);
ASSERT_THROW(variant.setString("foo"), std::bad_variant_access);
}
TEST(ESMVariantSetStringTest, for_short_should_throw_exception)
{
Variant variant;
variant.setType(VT_Short);
ASSERT_THROW(variant.setString("foo"), std::bad_variant_access);
}
TEST(ESMVariantSetStringTest, for_float_should_throw_exception)
{
Variant variant(float{2.7f});
ASSERT_THROW(variant.setString("foo"), std::bad_variant_access);
}
TEST(ESMVariantSetStringTest, for_string_should_change_value)
{
Variant variant(std::string("foo"));
variant.setString("bar");
ASSERT_EQ(variant.getString(), "bar");
}
struct WriteToESMTestCase
{
Variant mVariant;
Variant::Format mFormat;
std::size_t mDataSize {};
};
std::string write(const Variant& variant, const Variant::Format format)
{
std::ostringstream out;
ESM::ESMWriter writer;
writer.save(out);
variant.write(writer, format);
writer.close();
return out.str();
}
Variant read(const Variant::Format format, const std::string& data)
{
Variant result;
ESM::ESMReader reader;
reader.open(std::make_shared<std::istringstream>(data), "");
result.read(reader, format);
return result;
}
Variant writeAndRead(const Variant& variant, const Variant::Format format, std::size_t dataSize)
{
const std::string data = write(variant, format);
EXPECT_EQ(data.size(), dataSize);
return read(format, data);
}
struct ESMVariantToESMTest : TestWithParam<WriteToESMTestCase> {};
TEST_P(ESMVariantToESMTest, deserialized_is_equal_to_serialized)
{
const auto param = GetParam();
const auto result = writeAndRead(param.mVariant, param.mFormat, param.mDataSize);
ASSERT_EQ(param.mVariant, result);
}
INSTANTIATE_TEST_SUITE_P(VariantAndData, ESMVariantToESMTest, Values(
WriteToESMTestCase {Variant(), Variant::Format_Gmst, 324},
WriteToESMTestCase {Variant(int{42}), Variant::Format_Global, 345},
WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Global, 345},
WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Info, 336},
WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Local, 336},
WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Global, 345},
WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Local, 334},
WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Info, 336},
WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Local, 336}
));
struct ESMVariantToESMNoneTest : TestWithParam<WriteToESMTestCase> {};
TEST_P(ESMVariantToESMNoneTest, deserialized_is_none)
{
const auto param = GetParam();
const auto result = writeAndRead(param.mVariant, param.mFormat, param.mDataSize);
ASSERT_EQ(Variant(), result);
}
INSTANTIATE_TEST_SUITE_P(VariantAndData, ESMVariantToESMNoneTest, Values(
WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Gmst, 336},
WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Gmst, 335},
WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Gmst, 336}
));
struct ESMVariantWriteToESMFailTest : TestWithParam<WriteToESMTestCase> {};
TEST_P(ESMVariantWriteToESMFailTest, write_is_not_supported)
{
const auto param = GetParam();
std::ostringstream out;
ESM::ESMWriter writer;
writer.save(out);
ASSERT_THROW(param.mVariant.write(writer, param.mFormat), std::runtime_error);
}
INSTANTIATE_TEST_SUITE_P(VariantAndFormat, ESMVariantWriteToESMFailTest, Values(
WriteToESMTestCase {Variant(), Variant::Format_Global},
WriteToESMTestCase {Variant(), Variant::Format_Info},
WriteToESMTestCase {Variant(), Variant::Format_Local},
WriteToESMTestCase {Variant(int{42}), Variant::Format_Gmst},
WriteToESMTestCase {Variant(int{42}), Variant::Format_Info},
WriteToESMTestCase {Variant(int{42}), Variant::Format_Local},
WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Global},
WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Info},
WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Local},
WriteToESMTestCase {makeVariant(VT_Unknown), Variant::Format_Global},
WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Global},
WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Gmst},
WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Info}
));
}

View file

@ -1,6 +1,9 @@
#include "methodselectionpage.hpp" #include "methodselectionpage.hpp"
#include "mainwizard.hpp" #include "mainwizard.hpp"
#include <QUrl>
#include <QDesktopServices>
Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) : Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) :
QWizardPage(parent) QWizardPage(parent)
{ {
@ -11,9 +14,12 @@ Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) :
#ifndef OPENMW_USE_UNSHIELD #ifndef OPENMW_USE_UNSHIELD
retailDiscRadioButton->setEnabled(false); retailDiscRadioButton->setEnabled(false);
existingLocationRadioButton->setChecked(true); existingLocationRadioButton->setChecked(true);
buyLinkButton->released();
#endif #endif
registerField(QLatin1String("installation.retailDisc"), retailDiscRadioButton); registerField(QLatin1String("installation.retailDisc"), retailDiscRadioButton);
connect(buyLinkButton, SIGNAL(released()), this, SLOT(handleBuyButton()));
} }
int Wizard::MethodSelectionPage::nextId() const int Wizard::MethodSelectionPage::nextId() const
@ -24,3 +30,8 @@ int Wizard::MethodSelectionPage::nextId() const
return MainWizard::Page_ExistingInstallation; return MainWizard::Page_ExistingInstallation;
} }
} }
void Wizard::MethodSelectionPage::handleBuyButton()
{
QDesktopServices::openUrl(QUrl("https://openmw.org/faq/#do_i_need_morrowind"));
}

View file

@ -17,6 +17,9 @@ namespace Wizard
int nextId() const override; int nextId() const override;
private slots:
void handleBuyButton();
private: private:
MainWizard *mWizard; MainWizard *mWizard;

View file

@ -1,15 +1,11 @@
# Get the Google C++ Mocking Framework. # Get the Google C++ Mocking Framework.
# (This file is almost an copy of the original FindGTest.cmake file, # (This file is almost an copy of the original FindGTest.cmake file for GMock,
# altered to download and compile GMock and GTest if not found
# in GMOCK_ROOT or GTEST_ROOT respectively,
# feel free to use it as it is or modify it for your own needs.) # feel free to use it as it is or modify it for your own needs.)
# #
# Defines the following variables: # Defines the following variables:
# #
# GMOCK_FOUND - Found or got the Google Mocking framework # GMOCK_FOUND - Found or got the Google Mocking framework
# GTEST_FOUND - Found or got the Google Testing framework
# GMOCK_INCLUDE_DIRS - GMock include directory # GMOCK_INCLUDE_DIRS - GMock include directory
# GTEST_INCLUDE_DIRS - GTest include direcotry
# #
# Also defines the library variables below as normal variables # Also defines the library variables below as normal variables
# #
@ -17,14 +13,8 @@
# GMOCK_LIBRARIES - libgmock # GMOCK_LIBRARIES - libgmock
# GMOCK_MAIN_LIBRARIES - libgmock-main # GMOCK_MAIN_LIBRARIES - libgmock-main
# #
# GTEST_BOTH_LIBRARIES - Both libgtest & libgtest_main
# GTEST_LIBRARIES - libgtest
# GTEST_MAIN_LIBRARIES - libgtest_main
#
# Accepts the following variables as input: # Accepts the following variables as input:
# #
# GMOCK_ROOT - The root directory of the gmock install prefix
# GTEST_ROOT - The root directory of the gtest install prefix
# GMOCK_SRC_DIR -The directory of the gmock sources # GMOCK_SRC_DIR -The directory of the gmock sources
# GMOCK_VER - The version of the gmock sources to be downloaded # GMOCK_VER - The version of the gmock sources to be downloaded
# #
@ -101,48 +91,6 @@
# #
# * Kitware, Inc. # * Kitware, Inc.
#============================================================================= #=============================================================================
# Thanks to Daniel Blezek <blezek@gmail.com> for the GTEST_ADD_TESTS code
function(gtest_add_tests executable extra_args)
if(NOT ARGN)
message(FATAL_ERROR "Missing ARGN: Read the documentation for GTEST_ADD_TESTS")
endif()
if(ARGN STREQUAL "AUTO")
# obtain sources used for building that executable
get_property(ARGN TARGET ${executable} PROPERTY SOURCES)
endif()
set(gtest_case_name_regex ".*\\( *([A-Za-z_0-9]+) *, *([A-Za-z_0-9]+) *\\).*")
set(gtest_test_type_regex "(TYPED_TEST|TEST_?[FP]?)")
foreach(source ${ARGN})
file(READ "${source}" contents)
string(REGEX MATCHALL "${gtest_test_type_regex} *\\(([A-Za-z_0-9 ,]+)\\)" found_tests ${contents})
foreach(hit ${found_tests})
string(REGEX MATCH "${gtest_test_type_regex}" test_type ${hit})
# Parameterized tests have a different signature for the filter
if("x${test_type}" STREQUAL "xTEST_P")
string(REGEX REPLACE ${gtest_case_name_regex} "*/\\1.\\2/*" test_name ${hit})
elseif("x${test_type}" STREQUAL "xTEST_F" OR "x${test_type}" STREQUAL "xTEST")
string(REGEX REPLACE ${gtest_case_name_regex} "\\1.\\2" test_name ${hit})
elseif("x${test_type}" STREQUAL "xTYPED_TEST")
string(REGEX REPLACE ${gtest_case_name_regex} "\\1/*.\\2" test_name ${hit})
else()
message(WARNING "Could not parse GTest ${hit} for adding to CTest.")
continue()
endif()
add_test(NAME ${test_name} COMMAND ${executable} --gtest_filter=${test_name} ${extra_args})
endforeach()
endforeach()
endfunction()
function(_append_debugs _endvar _library)
if(${_library} AND ${_library}_DEBUG)
set(_output optimized ${${_library}} debug ${${_library}_DEBUG})
else()
set(_output ${${_library}})
endif()
set(${_endvar} ${_output} PARENT_SCOPE)
endfunction()
function(_gmock_find_library _name) function(_gmock_find_library _name)
find_library(${_name} find_library(${_name}
@ -155,38 +103,20 @@ function(_gmock_find_library _name)
mark_as_advanced(${_name}) mark_as_advanced(${_name})
endfunction() endfunction()
function(_gtest_find_library _name)
find_library(${_name}
NAMES ${ARGN}
HINTS
ENV GTEST_ROOT
${GTEST_ROOT}
PATH_SUFFIXES ${_gtest_libpath_suffixes}
)
mark_as_advanced(${_name})
endfunction()
if(NOT DEFINED GMOCK_MSVC_SEARCH) if(NOT DEFINED GMOCK_MSVC_SEARCH)
set(GMOCK_MSVC_SEARCH MD) set(GMOCK_MSVC_SEARCH MD)
endif() endif()
set(_gmock_libpath_suffixes lib) set(_gmock_libpath_suffixes lib)
set(_gtest_libpath_suffixes lib)
if(MSVC) if(MSVC)
if(GMOCK_MSVC_SEARCH STREQUAL "MD") if(GMOCK_MSVC_SEARCH STREQUAL "MD")
list(APPEND _gmock_libpath_suffixes list(APPEND _gmock_libpath_suffixes
msvc/gmock-md/Debug msvc/gmock-md/Debug
msvc/gmock-md/Release) msvc/gmock-md/Release)
list(APPEND _gtest_libpath_suffixes
msvc/gtest-md/Debug
msvc/gtest-md/Release)
elseif(GMOCK_MSVC_SEARCH STREQUAL "MT") elseif(GMOCK_MSVC_SEARCH STREQUAL "MT")
list(APPEND _gmock_libpath_suffixes list(APPEND _gmock_libpath_suffixes
msvc/gmock/Debug msvc/gmock/Debug
msvc/gmock/Release) msvc/gmock/Release)
list(APPEND _gtest_libpath_suffixes
msvc/gtest/Debug
msvc/gtest/Release)
endif() endif()
endif() endif()
@ -197,13 +127,6 @@ find_path(GMOCK_INCLUDE_DIR gmock/gmock.h
) )
mark_as_advanced(GMOCK_INCLUDE_DIR) mark_as_advanced(GMOCK_INCLUDE_DIR)
find_path(GTEST_INCLUDE_DIR gtest/gtest.h
HINTS
$ENV{GTEST_ROOT}/include
${GTEST_ROOT}/include
)
mark_as_advanced(GTEST_INCLUDE_DIR)
if(MSVC AND GMOCK_MSVC_SEARCH STREQUAL "MD") if(MSVC AND GMOCK_MSVC_SEARCH STREQUAL "MD")
# The provided /MD project files for Google Mock add -md suffixes to the # The provided /MD project files for Google Mock add -md suffixes to the
# library names. # library names.
@ -211,28 +134,12 @@ if(MSVC AND GMOCK_MSVC_SEARCH STREQUAL "MD")
_gmock_find_library(GMOCK_LIBRARY_DEBUG gmock-mdd gmockd) _gmock_find_library(GMOCK_LIBRARY_DEBUG gmock-mdd gmockd)
_gmock_find_library(GMOCK_MAIN_LIBRARY gmock_main-md gmock_main) _gmock_find_library(GMOCK_MAIN_LIBRARY gmock_main-md gmock_main)
_gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_main-mdd gmock_maind) _gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_main-mdd gmock_maind)
_gtest_find_library(GTEST_LIBRARY gtest-md gtest)
_gtest_find_library(GTEST_LIBRARY_DEBUG gtest-mdd gtestd)
_gtest_find_library(GTEST_MAIN_LIBRARY gtest_main-md gtest_main)
_gtest_find_library(GTEST_MAIN_LIBRARY_DEBUG gtest_main-mdd gtest_maind)
else() else()
_gmock_find_library(GMOCK_LIBRARY gmock) _gmock_find_library(GMOCK_LIBRARY gmock)
_gmock_find_library(GMOCK_LIBRARY_DEBUG gmockd) _gmock_find_library(GMOCK_LIBRARY_DEBUG gmockd)
_gmock_find_library(GMOCK_MAIN_LIBRARY gmock_main) _gmock_find_library(GMOCK_MAIN_LIBRARY gmock_main)
_gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_maind) _gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_maind)
_gtest_find_library(GTEST_LIBRARY gtest)
_gtest_find_library(GTEST_LIBRARY_DEBUG gtestd)
_gtest_find_library(GTEST_MAIN_LIBRARY gtest_main)
_gtest_find_library(GTEST_MAIN_LIBRARY_DEBUG gtest_maind)
endif()
if(NOT TARGET GTest::GTest)
add_library(GTest::GTest UNKNOWN IMPORTED)
endif()
if(NOT TARGET GTest::Main)
add_library(GTest::Main UNKNOWN IMPORTED)
endif() endif()
if(NOT TARGET GMock::GMock) if(NOT TARGET GMock::GMock)
@ -244,224 +151,17 @@ if(NOT TARGET GMock::Main)
endif() endif()
set(GMOCK_LIBRARY_EXISTS OFF) set(GMOCK_LIBRARY_EXISTS OFF)
set(GTEST_LIBRARY_EXISTS OFF)
if(EXISTS "${GMOCK_LIBRARY}" OR EXISTS "${GMOCK_LIBRARY_DEBUG}" AND GMOCK_INCLUDE_DIR) if(EXISTS "${GMOCK_LIBRARY}" OR EXISTS "${GMOCK_LIBRARY_DEBUG}" AND GMOCK_INCLUDE_DIR)
set(GMOCK_LIBRARY_EXISTS ON) set(GMOCK_LIBRARY_EXISTS ON)
endif() endif()
if(EXISTS "${GTEST_LIBRARY}" OR EXISTS "${GTEST_LIBRARY_DEBUG}" AND GTEST_INCLUDE_DIR)
set(GTEST_LIBRARY_EXISTS ON)
endif()
if(NOT (${GMOCK_LIBRARY_EXISTS} AND ${GTEST_LIBRARY_EXISTS}))
include(ExternalProject)
if(GTEST_USE_STATIC_LIBS)
set(GTEST_CMAKE_ARGS -Dgtest_force_shared_crt:BOOL=ON -DBUILD_SHARED_LIBS=OFF)
if(BUILD_SHARED_LIBS)
list(APPEND GTEST_CMAKE_ARGS
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
-Dgtest_hide_internal_symbols=ON
-DCMAKE_CXX_VISIBILITY_PRESET=hidden
-DCMAKE_VISIBILITY_INLINES_HIDDEN=ON
-DCMAKE_POLICY_DEFAULT_CMP0063=NEW
)
endif()
set(GTEST_LIBRARY_PREFIX ${CMAKE_STATIC_LIBRARY_PREFIX})
else()
set(GTEST_CMAKE_ARGS -DBUILD_SHARED_LIBS=ON)
set(GTEST_LIBRARY_PREFIX ${CMAKE_SHARED_LIBRARY_PREFIX})
endif()
if(WIN32)
list(APPEND GTEST_CMAKE_ARGS -Dgtest_disable_pthreads=ON)
endif()
if("${GMOCK_SRC_DIR}" STREQUAL "")
message(STATUS "Downloading GMock / GTest version ${GMOCK_VER} from git")
if("${GMOCK_VER}" STREQUAL "1.6.0" OR "${GMOCK_VER}" STREQUAL "1.7.0")
set(GTEST_BIN_DIR "${GMOCK_ROOT}/src/gtest-build")
set(GTEST_LIBRARY "${GTEST_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(GTEST_MAIN_LIBRARY "${GTEST_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}")
mark_as_advanced(GTEST_LIBRARY)
mark_as_advanced(GTEST_MAIN_LIBRARY)
externalproject_add(
gtest
GIT_REPOSITORY "https://github.com/google/googletest.git"
GIT_TAG "release-${GMOCK_VER}"
PREFIX ${GMOCK_ROOT}
INSTALL_COMMAND ""
LOG_DOWNLOAD ON
LOG_CONFIGURE ON
LOG_BUILD ON
CMAKE_ARGS
${GTEST_CMAKE_ARGS}
BINARY_DIR ${GTEST_BIN_DIR}
BUILD_BYPRODUCTS
"${GTEST_LIBRARY}"
"${GTEST_MAIN_LIBRARY}"
)
set(GMOCK_BIN_DIR "${GMOCK_ROOT}/src/gmock-build")
set(GMOCK_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(GMOCK_MAIN_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock_main${CMAKE_STATIC_LIBRARY_SUFFIX}")
mark_as_advanced(GMOCK_LIBRARY)
mark_as_advanced(GMOCK_MAIN_LIBRARY)
externalproject_add(
gmock
GIT_REPOSITORY "https://github.com/google/googlemock.git"
GIT_TAG "release-${GMOCK_VER}"
PREFIX ${GMOCK_ROOT}
INSTALL_COMMAND ""
LOG_DOWNLOAD ON
LOG_CONFIGURE ON
LOG_BUILD ON
CMAKE_ARGS
${GTEST_CMAKE_ARGS}
BINARY_DIR ${GMOCK_BIN_DIR}
BUILD_BYPRODUCTS
"${GMOCK_LIBRARY}"
"${GMOCK_MAIN_LIBRARY}"
)
add_dependencies(gmock gtest)
add_dependencies(GTest::GTest gtest)
add_dependencies(GTest::Main gtest)
add_dependencies(GMock::GMock gmock)
add_dependencies(GMock::Main gmock)
externalproject_get_property(gtest source_dir)
set(GTEST_INCLUDE_DIR "${source_dir}/include")
mark_as_advanced(GTEST_INCLUDE_DIR)
externalproject_get_property(gmock source_dir)
set(GMOCK_INCLUDE_DIR "${source_dir}/include")
mark_as_advanced(GMOCK_INCLUDE_DIR)
else() #1.8.0
set(GMOCK_BIN_DIR "${GMOCK_ROOT}/src/gmock-build")
set(GTEST_LIBRARY "${GMOCK_BIN_DIR}/googlemock/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(GTEST_MAIN_LIBRARY "${GMOCK_BIN_DIR}/googlemock/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(GMOCK_LIBRARY "${GMOCK_BIN_DIR}/googlemock/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(GMOCK_MAIN_LIBRARY "${GMOCK_BIN_DIR}/googlemock/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock_main${CMAKE_STATIC_LIBRARY_SUFFIX}")
mark_as_advanced(GTEST_LIBRARY)
mark_as_advanced(GTEST_MAIN_LIBRARY)
mark_as_advanced(GMOCK_LIBRARY)
mark_as_advanced(GMOCK_MAIN_LIBRARY)
externalproject_add(
gmock
GIT_REPOSITORY "https://github.com/google/googletest.git"
GIT_TAG "release-${GMOCK_VER}"
PREFIX ${GMOCK_ROOT}
INSTALL_COMMAND ""
LOG_DOWNLOAD ON
LOG_CONFIGURE ON
LOG_BUILD ON
CMAKE_ARGS
${GTEST_CMAKE_ARGS}
BINARY_DIR "${GMOCK_BIN_DIR}"
BUILD_BYPRODUCTS
"${GTEST_LIBRARY}"
"${GTEST_MAIN_LIBRARY}"
"${GMOCK_LIBRARY}"
"${GMOCK_MAIN_LIBRARY}"
)
add_dependencies(GTest::GTest gmock)
add_dependencies(GTest::Main gmock)
add_dependencies(GMock::GMock gmock)
add_dependencies(GMock::Main gmock)
externalproject_get_property(gmock source_dir)
set(GTEST_INCLUDE_DIR "${source_dir}/googletest/include")
set(GMOCK_INCLUDE_DIR "${source_dir}/googlemock/include")
mark_as_advanced(GMOCK_INCLUDE_DIR)
mark_as_advanced(GTEST_INCLUDE_DIR)
endif()
# Prevent CMake from complaining about these directories missing when the libgtest/libgmock targets get used as dependencies
file(MAKE_DIRECTORY ${GTEST_INCLUDE_DIR} ${GMOCK_INCLUDE_DIR})
else()
message(STATUS "Building Gmock / Gtest from dir ${GMOCK_SRC_DIR}")
set(GMOCK_BIN_DIR "${GMOCK_ROOT}/src/gmock-build")
set(GTEST_LIBRARY "${GMOCK_BIN_DIR}/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(GTEST_MAIN_LIBRARY "${GMOCK_BIN_DIR}/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(GMOCK_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(GMOCK_MAIN_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock_main${CMAKE_STATIC_LIBRARY_SUFFIX}")
mark_as_advanced(GTEST_LIBRARY)
mark_as_advanced(GTEST_MAIN_LIBRARY)
mark_as_advanced(GMOCK_LIBRARY)
mark_as_advanced(GMOCK_MAIN_LIBRARY)
if(EXISTS "${GMOCK_SRC_DIR}/gtest/include/gtest/gtest.h")
set(GTEST_INCLUDE_DIR "${GMOCK_SRC_DIR}/gtest/include")
mark_as_advanced(GTEST_INCLUDE_DIR)
endif()
if(EXISTS "${GMOCK_SRC_DIR}/include/gmock/gmock.h")
set(GMOCK_INCLUDE_DIR "${GMOCK_SRC_DIR}/include")
mark_as_advanced(GMOCK_INCLUDE_DIR)
elseif(EXISTS "${GMOCK_SRC_DIR}/../../include/gmock/gmock.h")
set(GMOCK_INCLUDE_DIR "${GMOCK_SRC_DIR}/../../include")
if(IS_ABSOLUTE "${GMOCK_INCLUDE_DIR}")
get_filename_component(GMOCK_INCLUDE_DIR "${GMOCK_INCLUDE_DIR}" ABSOLUTE)
endif()
mark_as_advanced(GMOCK_INCLUDE_DIR)
endif()
externalproject_add(
gmock
SOURCE_DIR ${GMOCK_SRC_DIR}
PREFIX ${GMOCK_ROOT}
INSTALL_COMMAND ""
LOG_DOWNLOAD ON
LOG_CONFIGURE ON
LOG_BUILD ON
CMAKE_ARGS
${GTEST_CMAKE_ARGS}
BINARY_DIR "${GMOCK_BIN_DIR}"
BUILD_BYPRODUCTS
"${GTEST_LIBRARY}"
"${GTEST_MAIN_LIBRARY}"
"${GMOCK_LIBRARY}"
"${GMOCK_MAIN_LIBRARY}"
)
add_dependencies(GTest::GTest gmock)
add_dependencies(GTest::Main gmock)
add_dependencies(GMock::GMock gmock)
add_dependencies(GMock::Main gmock)
endif()
endif()
include(FindPackageHandleStandardArgs) include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(GTest DEFAULT_MSG GTEST_LIBRARY GTEST_INCLUDE_DIR GTEST_MAIN_LIBRARY)
find_package_handle_standard_args(GMock DEFAULT_MSG GMOCK_LIBRARY GMOCK_INCLUDE_DIR GMOCK_MAIN_LIBRARY) find_package_handle_standard_args(GMock DEFAULT_MSG GMOCK_LIBRARY GMOCK_INCLUDE_DIR GMOCK_MAIN_LIBRARY)
include(CMakeFindDependencyMacro) include(CMakeFindDependencyMacro)
find_dependency(Threads) find_dependency(Threads)
set_target_properties(GTest::GTest PROPERTIES
INTERFACE_LINK_LIBRARIES "Threads::Threads"
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION "${GTEST_LIBRARY}"
)
if(GTEST_INCLUDE_DIR)
set_target_properties(GTest::GTest PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}"
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}"
)
endif()
set_target_properties(GTest::Main PROPERTIES
INTERFACE_LINK_LIBRARIES "GTest::GTest"
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION "${GTEST_MAIN_LIBRARY}")
set_target_properties(GMock::GMock PROPERTIES set_target_properties(GMock::GMock PROPERTIES
INTERFACE_LINK_LIBRARIES "Threads::Threads" INTERFACE_LINK_LIBRARIES "Threads::Threads"
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
@ -477,13 +177,6 @@ if(GMOCK_INCLUDE_DIR)
# so just specify it on the link interface. # so just specify it on the link interface.
set_property(TARGET GMock::GMock APPEND PROPERTY set_property(TARGET GMock::GMock APPEND PROPERTY
INTERFACE_LINK_LIBRARIES GTest::GTest) INTERFACE_LINK_LIBRARIES GTest::GTest)
elseif(GTEST_INCLUDE_DIR)
# GMock 1.7 and beyond doesn't have it as a link-time dependency anymore,
# so merge it's compile-time interface (include dirs) with ours.
set_property(TARGET GMock::GMock APPEND PROPERTY
INTERFACE_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}")
set_property(TARGET GMock::GMock APPEND PROPERTY
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}")
endif() endif()
endif() endif()
@ -492,17 +185,6 @@ set_target_properties(GMock::Main PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION "${GMOCK_MAIN_LIBRARY}") IMPORTED_LOCATION "${GMOCK_MAIN_LIBRARY}")
if(GTEST_FOUND)
set(GTEST_INCLUDE_DIRS ${GTEST_INCLUDE_DIR})
set(GTEST_LIBRARIES GTest::GTest)
set(GTEST_MAIN_LIBRARIES GTest::Main)
set(GTEST_BOTH_LIBRARIES ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES})
if(VERBOSE)
message(STATUS "GTest includes: ${GTEST_INCLUDE_DIRS}")
message(STATUS "GTest libs: ${GTEST_BOTH_LIBRARIES}")
endif()
endif()
if(GMOCK_FOUND) if(GMOCK_FOUND)
set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR}) set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR})
set(GMOCK_LIBRARIES GMock::GMock) set(GMOCK_LIBRARIES GMock::GMock)

View file

@ -27,6 +27,7 @@
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp> #include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/operations.hpp>
using namespace Bsa; using namespace Bsa;
@ -37,6 +38,31 @@ void BSAFile::fail(const std::string &msg)
throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + mFilename); throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + mFilename);
} }
//the getHash code is from bsapack from ghostwheel
//the code is also the same as in https://github.com/arviceblot/bsatool_rs/commit/67cb59ec3aaeedc0849222ea387f031c33e48c81
BSAFile::Hash getHash(const std::string& name)
{
BSAFile::Hash hash;
unsigned l = (name.size() >> 1);
unsigned sum, off, temp, i, n;
for (sum = off = i = 0; i < l; i++) {
sum ^= (((unsigned)(name[i])) << (off & 0x1F));
off += 8;
}
hash.low = sum;
for (sum = off = 0; i < name.size(); i++) {
temp = (((unsigned)(name[i])) << (off & 0x1F));
sum ^= temp;
n = temp & 0x1F;
sum = (sum << (32 - n)) | (sum >> n); // binary "rotate right"
off += 8;
}
hash.high = sum;
return hash;
}
/// Read header information from the input source /// Read header information from the input source
void BSAFile::readHeader() void BSAFile::readHeader()
{ {
@ -113,14 +139,17 @@ void BSAFile::readHeader()
// Read the offset info into a temporary buffer // Read the offset info into a temporary buffer
std::vector<uint32_t> offsets(3*filenum); std::vector<uint32_t> offsets(3*filenum);
input.read(reinterpret_cast<char*>(&offsets[0]), 12*filenum); input.read(reinterpret_cast<char*>(offsets.data()), 12*filenum);
// Read the string table // Read the string table
mStringBuf.resize(dirsize-12*filenum); mStringBuf.resize(dirsize-12*filenum);
input.read(&mStringBuf[0], mStringBuf.size()); input.read(mStringBuf.data(), mStringBuf.size());
// Check our position // Check our position
assert(input.tellg() == std::streampos(12+dirsize)); assert(input.tellg() == std::streampos(12+dirsize));
std::vector<Hash> hashes(filenum);
static_assert(sizeof(Hash) == 8);
input.read(reinterpret_cast<char*>(hashes.data()), 8*filenum);
// Calculate the offset of the data buffer. All file offsets are // Calculate the offset of the data buffer. All file offsets are
// relative to this. 12 header bytes + directory + hash table // relative to this. 12 header bytes + directory + hash table
@ -129,23 +158,72 @@ void BSAFile::readHeader()
// Set up the the FileStruct table // Set up the the FileStruct table
mFiles.resize(filenum); mFiles.resize(filenum);
size_t endOfNameBuffer = 0;
for(size_t i=0;i<filenum;i++) for(size_t i=0;i<filenum;i++)
{ {
FileStruct &fs = mFiles[i]; FileStruct &fs = mFiles[i];
fs.fileSize = offsets[i*2]; fs.fileSize = offsets[i*2];
fs.offset = offsets[i*2+1] + fileDataOffset; fs.offset = offsets[i*2+1] + fileDataOffset;
fs.name = &mStringBuf[offsets[2*filenum+i]]; auto namesOffset = offsets[2*filenum+i];
fs.setNameInfos(namesOffset, &mStringBuf);
fs.hash = hashes[i];
endOfNameBuffer = std::max(endOfNameBuffer, namesOffset + std::strlen(fs.name())+1);
assert(endOfNameBuffer <= mStringBuf.size());
if(fs.offset + fs.fileSize > fsize) if(fs.offset + fs.fileSize > fsize)
fail("Archive contains offsets outside itself"); fail("Archive contains offsets outside itself");
// Add the file name to the lookup // Add the file name to the lookup
mLookup[fs.name] = i; mLookup[fs.name()] = i;
} }
mStringBuf.resize(endOfNameBuffer);
std::sort(mFiles.begin(), mFiles.end(), [](const FileStruct& left, const FileStruct& right) {
return left.offset < right.offset;
});
mIsLoaded = true; mIsLoaded = true;
} }
/// Write header information to the output sink
void Bsa::BSAFile::writeHeader()
{
namespace bfs = boost::filesystem;
bfs::fstream output(mFilename, std::ios::binary | std::ios::in | std::ios::out);
uint32_t head[3];
head[0] = 0x100;
auto fileDataOffset = mFiles.empty() ? 12 : mFiles.front().offset;
head[1] = fileDataOffset - 12 - 8*mFiles.size();
output.seekp(0, std::ios_base::end);
head[2] = mFiles.size();
output.seekp(0);
output.write(reinterpret_cast<char*>(head), 12);
std::sort(mFiles.begin(), mFiles.end(), [](const FileStruct& left, const FileStruct& right) {
return std::make_pair(left.hash.low, left.hash.high) < std::make_pair(right.hash.low, right.hash.high);
});
size_t filenum = mFiles.size();
std::vector<uint32_t> offsets(3* filenum);
std::vector<Hash> hashes(filenum);
for(size_t i=0;i<filenum;i++)
{
auto& f = mFiles[i];
offsets[i*2] = f.fileSize;
offsets[i*2+1] = f.offset - fileDataOffset;
offsets[2*filenum+i] = f.namesOffset;
hashes[i] = f.hash;
}
output.write(reinterpret_cast<char*>(offsets.data()), sizeof(uint32_t)*offsets.size());
output.write(reinterpret_cast<char*>(mStringBuf.data()), mStringBuf.size());
output.seekp(fileDataOffset - 8*mFiles.size(), std::ios_base::beg);
output.write(reinterpret_cast<char*>(hashes.data()), sizeof(Hash)*hashes.size());
}
/// Get the index of a given file name, or -1 if not found /// Get the index of a given file name, or -1 if not found
int BSAFile::getIndex(const char *str) const int BSAFile::getIndex(const char *str) const
{ {
@ -162,7 +240,22 @@ int BSAFile::getIndex(const char *str) const
void BSAFile::open(const std::string &file) void BSAFile::open(const std::string &file)
{ {
mFilename = file; mFilename = file;
readHeader(); if(boost::filesystem::exists(file))
readHeader();
else
{
{ boost::filesystem::fstream(mFilename, std::ios::binary | std::ios::out); }
writeHeader();
}
}
/// Close the archive, write the updated headers to the file
void Bsa::BSAFile::close()
{
if (!mHasChanged)
return;
writeHeader();
} }
Files::IStreamPtr BSAFile::getFile(const char *file) Files::IStreamPtr BSAFile::getFile(const char *file)
@ -181,3 +274,56 @@ Files::IStreamPtr BSAFile::getFile(const FileStruct *file)
{ {
return Files::openConstrainedFileStream (mFilename.c_str (), file->offset, file->fileSize); return Files::openConstrainedFileStream (mFilename.c_str (), file->offset, file->fileSize);
} }
void Bsa::BSAFile::addFile(const std::string& filename, std::istream& file)
{
namespace bfs = boost::filesystem;
auto newStartOfDataBuffer = 12 + (12 + 8) * (mFiles.size() + 1) + mStringBuf.size() + filename.size() + 1;
if (mFiles.empty())
bfs::resize_file(mFilename, newStartOfDataBuffer);
bfs::fstream stream(mFilename, std::ios::binary | std::ios::in | std::ios::out);
FileStruct newFile;
file.seekg(0, std::ios::end);
newFile.fileSize = file.tellg();
newFile.setNameInfos(mStringBuf.size(), &mStringBuf);
newFile.hash = getHash(filename);
if(mFiles.empty())
newFile.offset = newStartOfDataBuffer;
else
{
std::vector<char> buffer;
while (mFiles.front().offset < newStartOfDataBuffer) {
FileStruct& firstFile = mFiles.front();
buffer.resize(firstFile.fileSize);
stream.seekg(firstFile.offset, std::ios::beg);
stream.read(buffer.data(), firstFile.fileSize);
stream.seekp(0, std::ios::end);
firstFile.offset = stream.tellp();
stream.write(buffer.data(), firstFile.fileSize);
//ensure sort order is preserved
std::rotate(mFiles.begin(), mFiles.begin() + 1, mFiles.end());
}
stream.seekp(0, std::ios::end);
newFile.offset = stream.tellp();
}
mStringBuf.insert(mStringBuf.end(), filename.begin(), filename.end());
mStringBuf.push_back('\0');
mFiles.push_back(newFile);
mHasChanged = true;
mLookup[filename.c_str()] = mFiles.size() - 1;
stream.seekp(0, std::ios::end);
file.seekg(0, std::ios::beg);
stream << file.rdbuf();
}

View file

@ -43,20 +43,42 @@ namespace Bsa
class BSAFile class BSAFile
{ {
public: public:
#pragma pack(push)
#pragma pack(1)
struct Hash
{
uint32_t low, high;
};
#pragma pack(pop)
/// Represents one file entry in the archive /// Represents one file entry in the archive
struct FileStruct struct FileStruct
{ {
void setNameInfos(size_t index,
std::vector<char>* stringBuf
) {
namesOffset = index;
namesBuffer = stringBuf;
}
// File size and offset in file. We store the offset from the // File size and offset in file. We store the offset from the
// beginning of the file, not the offset into the data buffer // beginning of the file, not the offset into the data buffer
// (which is what is stored in the archive.) // (which is what is stored in the archive.)
uint32_t fileSize, offset; uint32_t fileSize, offset;
Hash hash;
// Zero-terminated file name // Zero-terminated file name
const char *name; const char* name() const { return &(*namesBuffer)[namesOffset]; };
uint32_t namesOffset = 0;
std::vector<char>* namesBuffer = nullptr;
}; };
typedef std::vector<FileStruct> FileList; typedef std::vector<FileStruct> FileList;
protected: protected:
bool mHasChanged = false;
/// Table of files in this archive /// Table of files in this archive
FileList mFiles; FileList mFiles;
@ -72,7 +94,7 @@ protected:
/// Case insensitive string comparison /// Case insensitive string comparison
struct iltstr struct iltstr
{ {
bool operator()(const char *s1, const char *s2) const bool operator()(const std::string& s1, const std::string& s2) const
{ return Misc::StringUtils::ciLess(s1, s2); } { return Misc::StringUtils::ciLess(s1, s2); }
}; };
@ -80,7 +102,7 @@ protected:
the files[] vector above. The iltstr ensures that file name the files[] vector above. The iltstr ensures that file name
checks are case insensitive. checks are case insensitive.
*/ */
typedef std::map<const char*, int, iltstr> Lookup; typedef std::map<std::string, int, iltstr> Lookup;
Lookup mLookup; Lookup mLookup;
/// Error handling /// Error handling
@ -88,9 +110,7 @@ protected:
/// Read header information from the input source /// Read header information from the input source
virtual void readHeader(); virtual void readHeader();
virtual void writeHeader();
/// Read header information from the input source
/// Get the index of a given file name, or -1 if not found /// Get the index of a given file name, or -1 if not found
/// @note Thread safe. /// @note Thread safe.
@ -106,11 +126,16 @@ public:
: mIsLoaded(false) : mIsLoaded(false)
{ } { }
virtual ~BSAFile() = default; virtual ~BSAFile()
{
close();
}
/// Open an archive file. /// Open an archive file.
void open(const std::string &file); void open(const std::string &file);
void close();
/* ----------------------------------- /* -----------------------------------
* Archive file routines * Archive file routines
* ----------------------------------- * -----------------------------------
@ -131,6 +156,8 @@ public:
*/ */
virtual Files::IStreamPtr getFile(const FileStruct* file); virtual Files::IStreamPtr getFile(const FileStruct* file);
virtual void addFile(const std::string& filename, std::istream& file);
/// Get a list of all files /// Get a list of all files
/// @note Thread safe. /// @note Thread safe.
const FileList &getList() const const FileList &getList() const

View file

@ -226,7 +226,6 @@ void CompressedBSAFile::readHeader()
FileStruct fileStruct{}; FileStruct fileStruct{};
fileStruct.fileSize = file.getSizeWithoutCompressionFlag(); fileStruct.fileSize = file.getSizeWithoutCompressionFlag();
fileStruct.offset = file.offset; fileStruct.offset = file.offset;
fileStruct.name = nullptr;
mFiles.push_back(fileStruct); mFiles.push_back(fileStruct);
fullPaths.push_back(folder); fullPaths.push_back(folder);
@ -249,7 +248,7 @@ void CompressedBSAFile::readHeader()
} }
//The vector guarantees that its elements occupy contiguous memory //The vector guarantees that its elements occupy contiguous memory
mFiles[fileIndex].name = reinterpret_cast<char*>(mStringBuf.data() + mStringBuffOffset); mFiles[fileIndex].setNameInfos(mStringBuffOffset, &mStringBuf);
fullPaths.at(fileIndex) += "\\" + std::string(mStringBuf.data() + mStringBuffOffset); fullPaths.at(fileIndex) += "\\" + std::string(mStringBuf.data() + mStringBuffOffset);
@ -276,7 +275,7 @@ void CompressedBSAFile::readHeader()
fullPaths.at(fileIndex).c_str() + stringLength + 1u, fullPaths.at(fileIndex).c_str() + stringLength + 1u,
mStringBuf.data() + mStringBuffOffset); mStringBuf.data() + mStringBuffOffset);
mFiles[fileIndex].name = reinterpret_cast<char*>(mStringBuf.data() + mStringBuffOffset); mFiles[fileIndex].setNameInfos(mStringBuffOffset, &mStringBuf);
mLookup[reinterpret_cast<char*>(mStringBuf.data() + mStringBuffOffset)] = fileIndex; mLookup[reinterpret_cast<char*>(mStringBuf.data() + mStringBuffOffset)] = fileIndex;
mStringBuffOffset += stringLength + 1u; mStringBuffOffset += stringLength + 1u;
@ -320,13 +319,19 @@ CompressedBSAFile::FileRecord CompressedBSAFile::getFileRecord(const std::string
Files::IStreamPtr CompressedBSAFile::getFile(const FileStruct* file) Files::IStreamPtr CompressedBSAFile::getFile(const FileStruct* file)
{ {
FileRecord fileRec = getFileRecord(file->name); FileRecord fileRec = getFileRecord(file->name());
if (!fileRec.isValid()) { if (!fileRec.isValid()) {
fail("File not found: " + std::string(file->name)); fail("File not found: " + std::string(file->name()));
} }
return getFile(fileRec); return getFile(fileRec);
} }
void CompressedBSAFile::addFile(const std::string& filename, std::istream& file)
{
assert(false); //not implemented yet
fail("Add file is not implemented for compressed BSA: " + filename);
}
Files::IStreamPtr CompressedBSAFile::getFile(const char* file) Files::IStreamPtr CompressedBSAFile::getFile(const char* file)
{ {
FileRecord fileRec = getFileRecord(file); FileRecord fileRec = getFileRecord(file);
@ -376,8 +381,10 @@ Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord)
LZ4F_decompressionContext_t context = nullptr; LZ4F_decompressionContext_t context = nullptr;
LZ4F_createDecompressionContext(&context, LZ4F_VERSION); LZ4F_createDecompressionContext(&context, LZ4F_VERSION);
LZ4F_decompressOptions_t options = {}; LZ4F_decompressOptions_t options = {};
LZ4F_decompress(context, memoryStreamPtr->getRawData(), &uncompressedSize, buffer.get(), &size, &options); LZ4F_errorCode_t errorCode = LZ4F_decompress(context, memoryStreamPtr->getRawData(), &uncompressedSize, buffer.get(), &size, &options);
LZ4F_errorCode_t errorCode = LZ4F_freeDecompressionContext(context); if (LZ4F_isError(errorCode))
fail("LZ4 decompression error (file " + mFilename + "): " + LZ4F_getErrorName(errorCode));
errorCode = LZ4F_freeDecompressionContext(context);
if (LZ4F_isError(errorCode)) if (LZ4F_isError(errorCode))
fail("LZ4 decompression error (file " + mFilename + "): " + LZ4F_getErrorName(errorCode)); fail("LZ4 decompression error (file " + mFilename + "): " + LZ4F_getErrorName(errorCode));
} }
@ -430,10 +437,10 @@ void CompressedBSAFile::convertCompressedSizesToUncompressed()
{ {
for (auto & mFile : mFiles) for (auto & mFile : mFiles)
{ {
const FileRecord& fileRecord = getFileRecord(mFile.name); const FileRecord& fileRecord = getFileRecord(mFile.name());
if (!fileRecord.isValid()) if (!fileRecord.isValid())
{ {
fail("Could not find file " + std::string(mFile.name) + " in BSA"); fail("Could not find file " + std::string(mFile.name()) + " in BSA");
} }
if (!fileRecord.isCompressed(mCompressedByDefault)) if (!fileRecord.isCompressed(mCompressedByDefault))

View file

@ -94,7 +94,7 @@ namespace Bsa
Files::IStreamPtr getFile(const char* filePath) override; Files::IStreamPtr getFile(const char* filePath) override;
Files::IStreamPtr getFile(const FileStruct* fileStruct) override; Files::IStreamPtr getFile(const FileStruct* fileStruct) override;
void addFile(const std::string& filename, std::istream& file) override;
}; };
} }

View file

@ -146,11 +146,10 @@ static void gdb_info(pid_t pid)
/* /*
* Create a temp file to put gdb commands into. * Create a temp file to put gdb commands into.
* Note: POSIX.1-2008 declares that the file should be already created with mode 0600 by default. * Note: POSIX.1-2008 declares that the file should be already created with mode 0600 by default.
* Modern systems implement it and and suggest to do not touch masks in multithreaded applications. * Modern systems implement it and suggest to do not touch masks in multithreaded applications.
* So CoverityScan warning is valid only for ancient versions of stdlib. * So CoverityScan warning is valid only for ancient versions of stdlib.
*/ */
strcpy(respfile, "/tmp/gdb-respfile-XXXXXX"); strcpy(respfile, "/tmp/gdb-respfile-XXXXXX");
// coverity[secure_temp]
if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != nullptr) if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != nullptr)
{ {
fprintf(f, "attach %d\n" fprintf(f, "attach %d\n"

View file

@ -42,6 +42,7 @@ void ESM::Header::load (ESMReader &esm)
MasterData m; MasterData m;
m.name = esm.getHString(); m.name = esm.getHString();
m.size = esm.getHNLong ("DATA"); m.size = esm.getHNLong ("DATA");
m.index = -1;
mMaster.push_back (m); mMaster.push_back (m);
} }

View file

@ -14,106 +14,53 @@ namespace
const uint32_t INTV = ESM::FourCC<'I','N','T','V'>::value; const uint32_t INTV = ESM::FourCC<'I','N','T','V'>::value;
const uint32_t FLTV = ESM::FourCC<'F','L','T','V'>::value; const uint32_t FLTV = ESM::FourCC<'F','L','T','V'>::value;
const uint32_t STTV = ESM::FourCC<'S','T','T','V'>::value; const uint32_t STTV = ESM::FourCC<'S','T','T','V'>::value;
}
ESM::Variant::Variant() : mType (VT_None), mData (nullptr) {} template <typename T, bool orDefault = false>
struct GetValue
ESM::Variant::Variant(const std::string &value)
{
mData = nullptr;
mType = VT_None;
setType(VT_String);
setString(value);
}
ESM::Variant::Variant(int value)
{
mData = nullptr;
mType = VT_None;
setType(VT_Long);
setInteger(value);
}
ESM::Variant::Variant(float value)
{
mData = nullptr;
mType = VT_None;
setType(VT_Float);
setFloat(value);
}
ESM::Variant::~Variant()
{
delete mData;
}
ESM::Variant& ESM::Variant::operator= (const Variant& variant)
{
if (&variant!=this)
{ {
VariantDataBase *newData = variant.mData ? variant.mData->clone() : nullptr; T operator()(int value) const { return static_cast<T>(value); }
delete mData; T operator()(float value) const { return static_cast<T>(value); }
mType = variant.mType; template <typename V>
mData = newData; T operator()(const V&) const
} {
if constexpr (orDefault)
return T {};
else
throw std::runtime_error("cannot convert variant");
}
};
return *this; template <typename T>
} struct SetValue
ESM::Variant& ESM::Variant::operator= (Variant&& variant)
{
if (&variant!=this)
{ {
delete mData; T mValue;
mType = variant.mType; explicit SetValue(T value) : mValue(value) {}
mData = variant.mData;
variant.mData = nullptr; void operator()(int& value) const { value = static_cast<int>(mValue); }
}
return *this; void operator()(float& value) const { value = static_cast<float>(mValue); }
}
ESM::Variant::Variant (const Variant& variant) template <typename V>
: mType (variant.mType), mData (variant.mData ? variant.mData->clone() : nullptr) void operator()(V&) const { throw std::runtime_error("cannot convert variant"); }
{} };
ESM::Variant::Variant(Variant&& variant)
: mType (variant.mType), mData (variant.mData)
{
variant.mData = nullptr;
}
ESM::VarType ESM::Variant::getType() const
{
return mType;
} }
std::string ESM::Variant::getString() const std::string ESM::Variant::getString() const
{ {
if (!mData) return std::get<std::string>(mData);
throw std::runtime_error ("can not convert empty variant to string");
return mData->getString();
} }
int ESM::Variant::getInteger() const int ESM::Variant::getInteger() const
{ {
if (!mData) return std::visit(GetValue<int>{}, mData);
throw std::runtime_error ("can not convert empty variant to integer");
return mData->getInteger();
} }
float ESM::Variant::getFloat() const float ESM::Variant::getFloat() const
{ {
if (!mData) return std::visit(GetValue<float>{}, mData);
throw std::runtime_error ("can not convert empty variant to float");
return mData->getFloat();
} }
void ESM::Variant::read (ESMReader& esm, Format format) void ESM::Variant::read (ESMReader& esm, Format format)
@ -202,9 +149,7 @@ void ESM::Variant::read (ESMReader& esm, Format format)
setType (type); setType (type);
// data std::visit(ReadESMVariantValue {esm, format, mType}, mData);
if (mData)
mData->read (esm, format, mType);
} }
void ESM::Variant::write (ESMWriter& esm, Format format) const void ESM::Variant::write (ESMWriter& esm, Format format) const
@ -227,7 +172,7 @@ void ESM::Variant::write (ESMWriter& esm, Format format) const
// nothing to do here for GMST format // nothing to do here for GMST format
} }
else else
mData->write (esm, format, mType); std::visit(WriteESMVariantValue {esm, format, mType}, mData);
} }
void ESM::Variant::write (std::ostream& stream) const void ESM::Variant::write (std::ostream& stream) const
@ -246,27 +191,27 @@ void ESM::Variant::write (std::ostream& stream) const
case VT_Short: case VT_Short:
stream << "variant short: " << mData->getInteger(); stream << "variant short: " << std::get<int>(mData);
break; break;
case VT_Int: case VT_Int:
stream << "variant int: " << mData->getInteger(); stream << "variant int: " << std::get<int>(mData);
break; break;
case VT_Long: case VT_Long:
stream << "variant long: " << mData->getInteger(); stream << "variant long: " << std::get<int>(mData);
break; break;
case VT_Float: case VT_Float:
stream << "variant float: " << mData->getFloat(); stream << "variant float: " << std::get<float>(mData);
break; break;
case VT_String: case VT_String:
stream << "variant string: \"" << mData->getString() << "\""; stream << "variant string: \"" << std::get<std::string>(mData) << "\"";
break; break;
} }
} }
@ -275,74 +220,50 @@ void ESM::Variant::setType (VarType type)
{ {
if (type!=mType) if (type!=mType)
{ {
VariantDataBase *newData = nullptr;
switch (type) switch (type)
{ {
case VT_Unknown: case VT_Unknown:
case VT_None: case VT_None:
mData = std::monostate {};
break; // no data break;
case VT_Short: case VT_Short:
case VT_Int: case VT_Int:
case VT_Long: case VT_Long:
mData = std::visit(GetValue<int, true>{}, mData);
newData = new VariantIntegerData (mData);
break; break;
case VT_Float: case VT_Float:
mData = std::visit(GetValue<float, true>{}, mData);
newData = new VariantFloatData (mData);
break; break;
case VT_String: case VT_String:
mData = std::string {};
newData = new VariantStringData (mData);
break; break;
} }
delete mData;
mData = newData;
mType = type; mType = type;
} }
} }
void ESM::Variant::setString (const std::string& value) void ESM::Variant::setString (const std::string& value)
{ {
if (!mData) std::get<std::string>(mData) = value;
throw std::runtime_error ("can not assign string to empty variant"); }
mData->setString (value); void ESM::Variant::setString (std::string&& value)
{
std::get<std::string>(mData) = std::move(value);
} }
void ESM::Variant::setInteger (int value) void ESM::Variant::setInteger (int value)
{ {
if (!mData) std::visit(SetValue(value), mData);
throw std::runtime_error ("can not assign integer to empty variant");
mData->setInteger (value);
} }
void ESM::Variant::setFloat (float value) void ESM::Variant::setFloat (float value)
{ {
if (!mData) std::visit(SetValue(value), mData);
throw std::runtime_error ("can not assign float to empty variant");
mData->setFloat (value);
}
bool ESM::Variant::isEqual (const Variant& value) const
{
if (mType!=value.mType)
return false;
if (!mData)
return true;
assert (value.mData);
return mData->isEqual (*value.mData);
} }
std::ostream& ESM::operator<< (std::ostream& stream, const Variant& value) std::ostream& ESM::operator<< (std::ostream& stream, const Variant& value)
@ -350,13 +271,3 @@ std::ostream& ESM::operator<< (std::ostream& stream, const Variant& value)
value.write (stream); value.write (stream);
return stream; return stream;
} }
bool ESM::operator== (const Variant& left, const Variant& right)
{
return left.isEqual (right);
}
bool ESM::operator!= (const Variant& left, const Variant& right)
{
return !(left==right);
}

View file

@ -3,6 +3,8 @@
#include <string> #include <string>
#include <iosfwd> #include <iosfwd>
#include <variant>
#include <tuple>
namespace ESM namespace ESM
{ {
@ -20,12 +22,10 @@ namespace ESM
VT_String VT_String
}; };
class VariantDataBase;
class Variant class Variant
{ {
VarType mType; VarType mType;
VariantDataBase *mData; std::variant<std::monostate, int, float, std::string> mData;
public: public:
@ -37,21 +37,17 @@ namespace ESM
Format_Local // local script variables in save game files Format_Local // local script variables in save game files
}; };
Variant(); Variant() : mType (VT_None), mData (std::monostate{}) {}
Variant (const std::string& value); explicit Variant(const std::string& value) : mType(VT_String), mData(value) {}
Variant (int value);
Variant (float value);
~Variant(); explicit Variant(std::string&& value) : mType(VT_String), mData(std::move(value)) {}
Variant& operator= (const Variant& variant); explicit Variant(int value) : mType(VT_Long), mData(value) {}
Variant& operator= (Variant && variant);
Variant (const Variant& variant); explicit Variant(float value) : mType(VT_Float), mData(value) {}
Variant (Variant&& variant);
VarType getType() const; VarType getType() const { return mType; }
std::string getString() const; std::string getString() const;
///< Will throw an exception, if value can not be represented as a string. ///< Will throw an exception, if value can not be represented as a string.
@ -75,19 +71,27 @@ namespace ESM
void setString (const std::string& value); void setString (const std::string& value);
///< Will throw an exception, if type is not compatible with string. ///< Will throw an exception, if type is not compatible with string.
void setString (std::string&& value);
///< Will throw an exception, if type is not compatible with string.
void setInteger (int value); void setInteger (int value);
///< Will throw an exception, if type is not compatible with integer. ///< Will throw an exception, if type is not compatible with integer.
void setFloat (float value); void setFloat (float value);
///< Will throw an exception, if type is not compatible with float. ///< Will throw an exception, if type is not compatible with float.
bool isEqual (const Variant& value) const; friend bool operator==(const Variant& left, const Variant& right)
{
return std::tie(left.mType, left.mData) == std::tie(right.mType, right.mData);
}
friend bool operator!=(const Variant& left, const Variant& right)
{
return !(left == right);
}
}; };
std::ostream& operator<<(std::ostream& stream, const Variant& value); std::ostream& operator<<(std::ostream& stream, const Variant& value);
bool operator== (const Variant& left, const Variant& right);
bool operator!= (const Variant& left, const Variant& right);
} }
#endif #endif

View file

@ -5,71 +5,7 @@
#include "esmreader.hpp" #include "esmreader.hpp"
#include "esmwriter.hpp" #include "esmwriter.hpp"
ESM::VariantDataBase::~VariantDataBase() {} void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, std::string& out)
std::string ESM::VariantDataBase::getString (bool default_) const
{
if (default_)
return "";
throw std::runtime_error ("can not convert variant to string");
}
int ESM::VariantDataBase::getInteger (bool default_) const
{
if (default_)
return 0;
throw std::runtime_error ("can not convert variant to integer");
}
float ESM::VariantDataBase::getFloat (bool default_) const
{
if (default_)
return 0;
throw std::runtime_error ("can not convert variant to float");
}
void ESM::VariantDataBase::setString (const std::string& value)
{
throw std::runtime_error ("conversion of string to variant not possible");
}
void ESM::VariantDataBase::setInteger (int value)
{
throw std::runtime_error ("conversion of integer to variant not possible");
}
void ESM::VariantDataBase::setFloat (float value)
{
throw std::runtime_error ("conversion of float to variant not possible");
}
ESM::VariantStringData::VariantStringData (const VariantDataBase *data)
{
if (data)
mValue = data->getString (true);
}
ESM::VariantDataBase *ESM::VariantStringData::clone() const
{
return new VariantStringData (*this);
}
std::string ESM::VariantStringData::getString (bool default_) const
{
return mValue;
}
void ESM::VariantStringData::setString (const std::string& value)
{
mValue = value;
}
void ESM::VariantStringData::read (ESMReader& esm, Variant::Format format, VarType type)
{ {
if (type!=VT_String) if (type!=VT_String)
throw std::logic_error ("not a string type"); throw std::logic_error ("not a string type");
@ -84,10 +20,10 @@ void ESM::VariantStringData::read (ESMReader& esm, Variant::Format format, VarTy
esm.fail ("local variables of type string not supported"); esm.fail ("local variables of type string not supported");
// GMST // GMST
mValue = esm.getHString(); out = esm.getHString();
} }
void ESM::VariantStringData::write (ESMWriter& esm, Variant::Format format, VarType type) const void ESM::writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, const std::string& in)
{ {
if (type!=VT_String) if (type!=VT_String)
throw std::logic_error ("not a string type"); throw std::logic_error ("not a string type");
@ -98,49 +34,14 @@ void ESM::VariantStringData::write (ESMWriter& esm, Variant::Format format, VarT
if (format==Variant::Format_Info) if (format==Variant::Format_Info)
throw std::runtime_error ("info variables of type string not supported"); throw std::runtime_error ("info variables of type string not supported");
if (format==Variant::Format_Local)
throw std::runtime_error ("local variables of type string not supported");
// GMST // GMST
esm.writeHNString ("STRV", mValue); esm.writeHNString("STRV", in);
} }
bool ESM::VariantStringData::isEqual (const VariantDataBase& value) const void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, int& out)
{
return dynamic_cast<const VariantStringData&> (value).mValue==mValue;
}
ESM::VariantIntegerData::VariantIntegerData (const VariantDataBase *data) : mValue (0)
{
if (data)
mValue = data->getInteger (true);
}
ESM::VariantDataBase *ESM::VariantIntegerData::clone() const
{
return new VariantIntegerData (*this);
}
int ESM::VariantIntegerData::getInteger (bool default_) const
{
return mValue;
}
float ESM::VariantIntegerData::getFloat (bool default_) const
{
return static_cast<float>(mValue);
}
void ESM::VariantIntegerData::setInteger (int value)
{
mValue = value;
}
void ESM::VariantIntegerData::setFloat (float value)
{
mValue = static_cast<int> (value);
}
void ESM::VariantIntegerData::read (ESMReader& esm, Variant::Format format, VarType type)
{ {
if (type!=VT_Short && type!=VT_Long && type!=VT_Int) if (type!=VT_Short && type!=VT_Long && type!=VT_Int)
throw std::logic_error ("not an integer type"); throw std::logic_error ("not an integer type");
@ -153,12 +54,12 @@ void ESM::VariantIntegerData::read (ESMReader& esm, Variant::Format format, VarT
if (type==VT_Short) if (type==VT_Short)
{ {
if (value!=value) if (value!=value)
mValue = 0; // nan out = 0; // nan
else else
mValue = static_cast<short> (value); out = static_cast<short> (value);
} }
else if (type==VT_Long) else if (type==VT_Long)
mValue = static_cast<int> (value); out = static_cast<int> (value);
else else
esm.fail ("unsupported global variable integer type"); esm.fail ("unsupported global variable integer type");
} }
@ -173,7 +74,7 @@ void ESM::VariantIntegerData::read (ESMReader& esm, Variant::Format format, VarT
esm.fail (stream.str()); esm.fail (stream.str());
} }
esm.getHT (mValue); esm.getHT(out);
} }
else if (format==Variant::Format_Local) else if (format==Variant::Format_Local)
{ {
@ -181,18 +82,18 @@ void ESM::VariantIntegerData::read (ESMReader& esm, Variant::Format format, VarT
{ {
short value; short value;
esm.getHT(value); esm.getHT(value);
mValue = value; out = value;
} }
else if (type==VT_Int) else if (type==VT_Int)
{ {
esm.getHT(mValue); esm.getHT(out);
} }
else else
esm.fail("unsupported local variable integer type"); esm.fail("unsupported local variable integer type");
} }
} }
void ESM::VariantIntegerData::write (ESMWriter& esm, Variant::Format format, VarType type) const void ESM::writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, int in)
{ {
if (type!=VT_Short && type!=VT_Long && type!=VT_Int) if (type!=VT_Short && type!=VT_Long && type!=VT_Int)
throw std::logic_error ("not an integer type"); throw std::logic_error ("not an integer type");
@ -201,7 +102,7 @@ void ESM::VariantIntegerData::write (ESMWriter& esm, Variant::Format format, Var
{ {
if (type==VT_Short || type==VT_Long) if (type==VT_Short || type==VT_Long)
{ {
float value = static_cast<float>(mValue); float value = static_cast<float>(in);
esm.writeHNString ("FNAM", type==VT_Short ? "s" : "l"); esm.writeHNString ("FNAM", type==VT_Short ? "s" : "l");
esm.writeHNT ("FLTV", value); esm.writeHNT ("FLTV", value);
} }
@ -219,72 +120,35 @@ void ESM::VariantIntegerData::write (ESMWriter& esm, Variant::Format format, Var
throw std::runtime_error (stream.str()); throw std::runtime_error (stream.str());
} }
esm.writeHNT ("INTV", mValue); esm.writeHNT("INTV", in);
} }
else if (format==Variant::Format_Local) else if (format==Variant::Format_Local)
{ {
if (type==VT_Short) if (type==VT_Short)
esm.writeHNT ("STTV", (short)mValue); esm.writeHNT("STTV", static_cast<short>(in));
else if (type == VT_Int) else if (type == VT_Int)
esm.writeHNT ("INTV", mValue); esm.writeHNT("INTV", in);
else else
throw std::runtime_error("unsupported local variable integer type"); throw std::runtime_error("unsupported local variable integer type");
} }
} }
bool ESM::VariantIntegerData::isEqual (const VariantDataBase& value) const void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, float& out)
{
return dynamic_cast<const VariantIntegerData&> (value).mValue==mValue;
}
ESM::VariantFloatData::VariantFloatData (const VariantDataBase *data) : mValue (0)
{
if (data)
mValue = data->getFloat (true);
}
ESM::VariantDataBase *ESM::VariantFloatData::clone() const
{
return new VariantFloatData (*this);
}
int ESM::VariantFloatData::getInteger (bool default_) const
{
return static_cast<int> (mValue);
}
float ESM::VariantFloatData::getFloat (bool default_) const
{
return mValue;
}
void ESM::VariantFloatData::setInteger (int value)
{
mValue = static_cast<float>(value);
}
void ESM::VariantFloatData::setFloat (float value)
{
mValue = value;
}
void ESM::VariantFloatData::read (ESMReader& esm, Variant::Format format, VarType type)
{ {
if (type!=VT_Float) if (type!=VT_Float)
throw std::logic_error ("not a float type"); throw std::logic_error ("not a float type");
if (format==Variant::Format_Global) if (format==Variant::Format_Global)
{ {
esm.getHNT (mValue, "FLTV"); esm.getHNT(out, "FLTV");
} }
else if (format==Variant::Format_Gmst || format==Variant::Format_Info || format==Variant::Format_Local) else if (format==Variant::Format_Gmst || format==Variant::Format_Info || format==Variant::Format_Local)
{ {
esm.getHT (mValue); esm.getHT(out);
} }
} }
void ESM::VariantFloatData::write (ESMWriter& esm, Variant::Format format, VarType type) const void ESM::writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, float in)
{ {
if (type!=VT_Float) if (type!=VT_Float)
throw std::logic_error ("not a float type"); throw std::logic_error ("not a float type");
@ -292,15 +156,10 @@ void ESM::VariantFloatData::write (ESMWriter& esm, Variant::Format format, VarTy
if (format==Variant::Format_Global) if (format==Variant::Format_Global)
{ {
esm.writeHNString ("FNAM", "f"); esm.writeHNString ("FNAM", "f");
esm.writeHNT ("FLTV", mValue); esm.writeHNT("FLTV", in);
} }
else if (format==Variant::Format_Gmst || format==Variant::Format_Info || format==Variant::Format_Local) else if (format==Variant::Format_Gmst || format==Variant::Format_Info || format==Variant::Format_Local)
{ {
esm.writeHNT ("FLTV", mValue); esm.writeHNT("FLTV", in);
} }
} }
bool ESM::VariantFloatData::isEqual (const VariantDataBase& value) const
{
return dynamic_cast<const VariantFloatData&> (value).mValue==mValue;
}

View file

@ -2,177 +2,58 @@
#define OPENMW_ESM_VARIANTIMP_H #define OPENMW_ESM_VARIANTIMP_H
#include <string> #include <string>
#include <functional>
#include "variant.hpp" #include "variant.hpp"
namespace ESM namespace ESM
{ {
class VariantDataBase void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, std::string& value);
void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, float& value);
void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, int& value);
void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, const std::string& value);
void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, float value);
void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, int value);
struct ReadESMVariantValue
{ {
public: std::reference_wrapper<ESMReader> mReader;
Variant::Format mFormat;
VarType mType;
virtual ~VariantDataBase(); ReadESMVariantValue(ESMReader& reader, Variant::Format format, VarType type)
: mReader(reader), mFormat(format), mType(type) {}
virtual VariantDataBase *clone() const = 0; void operator()(std::monostate) const {}
virtual std::string getString (bool default_ = false) const;
///< Will throw an exception, if value can not be represented as a string.
///
/// \note Numeric values are not converted to strings.
///
/// \param default_ Return a default value instead of throwing an exception.
///
/// Default-implementation: throw an exception.
virtual int getInteger (bool default_ = false) const;
///< Will throw an exception, if value can not be represented as an integer (implicit
/// casting of float values is permitted).
///
/// \param default_ Return a default value instead of throwing an exception.
///
/// Default-implementation: throw an exception.
virtual float getFloat (bool default_ = false) const;
///< Will throw an exception, if value can not be represented as a float value.
///
/// \param default_ Return a default value instead of throwing an exception.
///
/// Default-implementation: throw an exception.
virtual void setString (const std::string& value);
///< Will throw an exception, if type is not compatible with string.
///
/// Default-implementation: throw an exception.
virtual void setInteger (int value);
///< Will throw an exception, if type is not compatible with integer.
///
/// Default-implementation: throw an exception.
virtual void setFloat (float value);
///< Will throw an exception, if type is not compatible with float.
///
/// Default-implementation: throw an exception.
virtual void read (ESMReader& esm, Variant::Format format, VarType type) = 0;
///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail
virtual void write (ESMWriter& esm, Variant::Format format, VarType type) const = 0;
///< If \a type is not supported by \a format, an exception is thrown.
virtual bool isEqual (const VariantDataBase& value) const = 0;
///< If the (C++) type of \a value does not match the type of *this, an exception is thrown.
template <typename T>
void operator()(T& value) const
{
readESMVariantValue(mReader.get(), mFormat, mType, value);
}
}; };
class VariantStringData : public VariantDataBase struct WriteESMVariantValue
{ {
std::string mValue; std::reference_wrapper<ESMWriter> mWriter;
Variant::Format mFormat;
VarType mType;
public: WriteESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type)
: mWriter(writer), mFormat(format), mType(type) {}
VariantStringData (const VariantDataBase *data = nullptr); void operator()(std::monostate) const {}
///< Calling the constructor with an incompatible data type will result in a silent
/// default initialisation.
VariantDataBase *clone() const override; template <typename T>
void operator()(const T& value) const
std::string getString (bool default_ = false) const override; {
///< Will throw an exception, if value can not be represented as a string. writeESMVariantValue(mWriter.get(), mFormat, mType, value);
/// }
/// \note Numeric values are not converted to strings.
///
/// \param default_ Return a default value instead of throwing an exception.
void setString (const std::string& value) override;
///< Will throw an exception, if type is not compatible with string.
void read (ESMReader& esm, Variant::Format format, VarType type) override;
///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail
void write (ESMWriter& esm, Variant::Format format, VarType type) const override;
///< If \a type is not supported by \a format, an exception is thrown.
bool isEqual (const VariantDataBase& value) const override;
///< If the (C++) type of \a value does not match the type of *this, an exception is thrown.
};
class VariantIntegerData : public VariantDataBase
{
int mValue;
public:
VariantIntegerData (const VariantDataBase *data = nullptr);
///< Calling the constructor with an incompatible data type will result in a silent
/// default initialisation.
VariantDataBase *clone() const override;
int getInteger (bool default_ = false) const override;
///< Will throw an exception, if value can not be represented as an integer (implicit
/// casting of float values is permitted).
///
/// \param default_ Return a default value instead of throwing an exception.
float getFloat (bool default_ = false) const override;
///< Will throw an exception, if value can not be represented as a float value.
///
/// \param default_ Return a default value instead of throwing an exception.
void setInteger (int value) override;
///< Will throw an exception, if type is not compatible with integer.
void setFloat (float value) override;
///< Will throw an exception, if type is not compatible with float.
void read (ESMReader& esm, Variant::Format format, VarType type) override;
///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail
void write (ESMWriter& esm, Variant::Format format, VarType type) const override;
///< If \a type is not supported by \a format, an exception is thrown.
bool isEqual (const VariantDataBase& value) const override;
///< If the (C++) type of \a value does not match the type of *this, an exception is thrown.
};
class VariantFloatData : public VariantDataBase
{
float mValue;
public:
VariantFloatData (const VariantDataBase *data = nullptr);
///< Calling the constructor with an incompatible data type will result in a silent
/// default initialisation.
VariantDataBase *clone() const override;
int getInteger (bool default_ = false) const override;
///< Will throw an exception, if value can not be represented as an integer (implicit
/// casting of float values is permitted).
///
/// \param default_ Return a default value instead of throwing an exception.
float getFloat (bool default_ = false) const override;
///< Will throw an exception, if value can not be represented as a float value.
///
/// \param default_ Return a default value instead of throwing an exception.
void setInteger (int value) override;
///< Will throw an exception, if type is not compatible with integer.
void setFloat (float value) override;
///< Will throw an exception, if type is not compatible with float.
void read (ESMReader& esm, Variant::Format format, VarType type) override;
///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail
void write (ESMWriter& esm, Variant::Format format, VarType type) const override;
///< If \a type is not supported by \a format, an exception is thrown.
bool isEqual (const VariantDataBase& value) const override;
///< If the (C++) type of \a value does not match the type of *this, an exception is thrown.
}; };
} }

View file

@ -569,7 +569,8 @@ namespace Gui
resolution = std::min(960, std::max(48, resolution)); resolution = std::min(960, std::max(48, resolution));
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
resolution *= uiScale; if (uiScale > 0.f)
resolution *= uiScale;
MyGUI::xml::ElementPtr resolutionNode = resourceNode->createChild("Property"); MyGUI::xml::ElementPtr resolutionNode = resourceNode->createChild("Property");
resolutionNode->addAttribute("key", "Resolution"); resolutionNode->addAttribute("key", "Resolution");

View file

@ -13,6 +13,7 @@ namespace Misc
explicit FrameRateLimiter(std::chrono::duration<Rep, Ratio> maxFrameDuration, explicit FrameRateLimiter(std::chrono::duration<Rep, Ratio> maxFrameDuration,
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now())
: mMaxFrameDuration(std::chrono::duration_cast<std::chrono::steady_clock::duration>(maxFrameDuration)) : mMaxFrameDuration(std::chrono::duration_cast<std::chrono::steady_clock::duration>(maxFrameDuration))
, mLastFrameDuration(0)
, mLastMeasurement(now) , mLastMeasurement(now)
{} {}

View file

@ -24,11 +24,6 @@ namespace osg
class Material; class Material;
} }
namespace osgParticle
{
class Emitter;
}
namespace NifOsg namespace NifOsg
{ {

View file

@ -227,6 +227,7 @@ namespace Resource
, mAutoUseSpecularMaps(false) , mAutoUseSpecularMaps(false)
, mApplyLightingToEnvMaps(false) , mApplyLightingToEnvMaps(false)
, mLightingMethod(SceneUtil::LightingMethod::FFP) , mLightingMethod(SceneUtil::LightingMethod::FFP)
, mConvertAlphaTestToAlphaToCoverage(false)
, mInstanceCache(new MultiObjectCache) , mInstanceCache(new MultiObjectCache)
, mSharedStateManager(new SharedStateManager) , mSharedStateManager(new SharedStateManager)
, mImageManager(imageManager) , mImageManager(imageManager)

View file

@ -375,6 +375,7 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr<osgViewer::Viewer> v
pack_evt.y = mMouseY = evt.motion.y; pack_evt.y = mMouseY = evt.motion.y;
pack_evt.xrel = evt.motion.xrel; pack_evt.xrel = evt.motion.xrel;
pack_evt.yrel = evt.motion.yrel; pack_evt.yrel = evt.motion.yrel;
pack_evt.type = SDL_MOUSEMOTION;
if (mFirstMouseMove) if (mFirstMouseMove)
{ {
// first event should be treated as non-relative, since there's no point of reference // first event should be treated as non-relative, since there's no point of reference
@ -387,6 +388,7 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr<osgViewer::Viewer> v
{ {
mMouseZ += pack_evt.zrel = (evt.wheel.y * 120); mMouseZ += pack_evt.zrel = (evt.wheel.y * 120);
pack_evt.z = mMouseZ; pack_evt.z = mMouseZ;
pack_evt.type = SDL_MOUSEWHEEL;
} }
else else
{ {

View file

@ -50,6 +50,7 @@ namespace Shader
, mAutoUseNormalMaps(false) , mAutoUseNormalMaps(false)
, mAutoUseSpecularMaps(false) , mAutoUseSpecularMaps(false)
, mApplyLightingToEnvMaps(false) , mApplyLightingToEnvMaps(false)
, mConvertAlphaTestToAlphaToCoverage(false)
, mTranslucentFramebuffer(false) , mTranslucentFramebuffer(false)
, mShaderManager(shaderManager) , mShaderManager(shaderManager)
, mImageManager(imageManager) , mImageManager(imageManager)

View file

@ -32,7 +32,7 @@ void BsaArchive::listResources(std::map<std::string, File *> &out, char (*normal
{ {
for (std::vector<BsaArchiveFile>::iterator it = mResources.begin(); it != mResources.end(); ++it) for (std::vector<BsaArchiveFile>::iterator it = mResources.begin(); it != mResources.end(); ++it)
{ {
std::string ent = it->mInfo->name; std::string ent = it->mInfo->name();
std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function); std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function);
out[ent] = &*it; out[ent] = &*it;
@ -43,7 +43,7 @@ bool BsaArchive::contains(const std::string& file, char (*normalize_function)(ch
{ {
for (const auto& it : mResources) for (const auto& it : mResources)
{ {
std::string ent = it.mInfo->name; std::string ent = it.mInfo->name();
std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function); std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function);
if(file == ent) if(file == ent)
return true; return true;

View file

@ -0,0 +1,19 @@
#############
Custom Models
#############
Custom models can be imported into OpenMW using a variety of formats. Below is a quick overview of supported formats, followed by separate articles with further look at the pipelines.
* **COLLADA** has no license restrictions and is suitable for modding as well as standalone games based on the OpenMW engine. It supports static and animated models. While it doesn't yet work in all parts of the engine, work is being done to resolve the remaining limitations.
* **OSG native** has no license restrictions, but currently supports only static, non-animated models.
* **NIF** is the proprietary format used in the original Morrowind game. It supports static and animated models and everything else the format included in the original game.
.. toctree::
:caption: Table of Contents
:maxdepth: 1
pipeline-blender-collada
pipeline-blender-osgnative
pipeline-blender-nif

View file

@ -0,0 +1,84 @@
##############################
Blender to OpenMW with Collada
##############################
First, let's take a look at the pipeline requirements and how the fundamental properties of a scene translate from Blender to OpenMW.
Requirements
------------
* `OpenMW 0.47 <https://openmw.org/downloads/>`_ or later
* `Blender 2.81 <https://www.blender.org/download/>`_ or later. Latest confirmed, working version is Blender 2.91
* `Better COLLADA Exporter <https://github.com/unelsson/collada-exporter>`_ tuned for OpenMW
* A model you would like to export
In addition, OpenMW needs to be configured to read COLLADA (dae) files instead of the default format (nif). In settings.cfg under [Models] section... TODO
Location
--------
Objects keep their visual location and origin they had in the original scene.
Rotation
--------
* Blenders +Z axis is up axis in OpenMW
* Blenders +Y axis is front axis in OpenMW
* Blenders X axis is left-right axis in OpenMW
Scale
-----
Scale ratio between Blender and OpenMW is 70 to 1. This means 70 blender units translate to 1 m in OpenMW.
However, a scale factor like this is impractical to work with. A better approach is to work with a scale of 1 Blender unit = 1m and apply the 70 scale factor in the Better COLLADA Exporter. The exporter will automatically scale all object, mesh, armature and animation data.
Exporter settings - static models
---------------------------------
Better COLLADA Exporter offers various options which are rather straightforward for static models. The important one is last in the list, to apply a scaling factor of 70 to the whole scene, so 1 blender unit equals 1 m in OpenMW. The following settings should be good for general use.
It's also very important to have "export selected" box checked, as otherwise the exporter may just fail with an error message. It's also important to have the correct window open, and the models selected before exporting.
Animated models to OpenMW
-------------------------
Animated models are those where a hierarchy of bones, known as armature, deforms the mesh and makes things move. Besides the topics covered above, the following requirements apply.
Armature
--------
* For animated models, a single armature per COLLADA file is advised to avoid any potential problems.
* There needs to be a single top-most bone in the armatures hierarchy, where both the deformation and control bones fall under it.
* Not all bones need to be exported. By disabing the bones “Deform” property and using the corresponding option in the exporter, it is possible to export only the bones needed for animation.
Animations
----------
Every action in Blender is exported as its own animation clip in COLLADA. Actions you don't wish to export need to have "-noexp" added to their name, with the corresponding option enabled in the exporter.
Due to current limitations of the format / exporter, the keyframes of any action must not overlap the keyframes of any other action. Thus in practice, the keyframes for each action need to be manually offset to their unique range on the timeline.
An animated .dae file needs a corresponding animation definition file, or textkeys, for OpenMW to understand. Textkeys are set in .txt file with the same name as the model. E.g. OpenMWDude.dae -> OpenMWDude.txt , each line having a textkey and a double number for timesignature. E.g. idle: start 0.03333333333333333.
Root Motion
-----------
OpenMW can read the movement of the root (top-most) bone and use it to move objects in the game world. For this to work, the root bone must be animated to move through space. The root bone must, in its default pose, be alligned with the world.
Exporter Settings
-----------------
For animated models, use the following exporter settings. Before export, select all objects you wish to include in the exported file. TODO

View file

@ -0,0 +1,17 @@
##########################
Blender to OpenMW with NIF
##########################
There is a lot of information available around the Internet on how to work with NIF files. We recommend you refer to https://www.niftools.org/ for more information.
For Blender specifically, you will need the following requirements.
Requirements
------------
* `OpenMW <https://openmw.org/downloads/>`_
* `Blender 2.8+ <https://www.blender.org/download/>`_
* Either `Niftools addon <https://github.com/niftools/blender_niftools_addonr>`_
* Or `Morrowind Blender Plugin <https://blender-morrowind.readthedocs.io/en/latest/index.html>`_
* A model you would like to export

View file

@ -1,17 +1,9 @@
################## #################################
Native Mesh Format Blender to OpenMW with OSG native
################## #################################
This article explains how to export a model from Blender to OpenMW using the OSG model format. This article explains how to export a model from Blender to OpenMW using the OSG model format. It supports only basic, static models.
Starting with OpenMW version 0.38 we can utilize the OSG native model format. For more details on the format, refer to `this forum post <https://forum.openmw.org/viewtopic.php?f=20&t=2949&p=35514#p35514>`_.
The OSG model format doesn't yet support all the features that NIF's support,
but works for basic models. For more details on the format, refer to
`this forum post <https://forum.openmw.org/viewtopic.php?f=20&t=2949&p=35514#p35514>`_.
Previously, NIF files were the only way to get models into the game.
Unfortunately, the NIF format is proprietary, bloated,
and the available exporters are not in great shape.
For example, the Blender NIF exporter currently only works with the very old Blender 2.49.
Prerequisites Prerequisites
############# #############
@ -103,12 +95,5 @@ Using shaders/normal maps
######################### #########################
See :ref:`OSG Native Files` See :ref:`OSG Native Files`
Conclusion
##########
These are the basics of getting a textured, static model from Blender into the game.
In the future, we will want a way to add texture animations,
skeletal animations, separate collision shapes,
and some other features that are currently only available via NIF files.
We will likely add these features to the native OSG format after OpenMW 1.0.

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