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:
commit
582f7b52cf
111 changed files with 1756 additions and 1205 deletions
|
@ -30,6 +30,29 @@ stages:
|
|||
paths:
|
||||
- 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:
|
||||
extends: .Debian
|
||||
cache:
|
||||
|
@ -94,6 +117,7 @@ Debian_Clang_tests:
|
|||
BUILD_TESTS_ONLY: 1
|
||||
|
||||
MacOS:
|
||||
image: macos-11-xcode-12
|
||||
tags:
|
||||
- macos
|
||||
stage: build
|
||||
|
@ -104,13 +128,17 @@ MacOS:
|
|||
- rm -fr build/* # remove anything in the build directory
|
||||
- CI/before_install.osx.sh
|
||||
- CI/before_script.osx.sh
|
||||
- cd build; make -j2 package
|
||||
- cd build; make -j $(sysctl -n hw.logicalcpu) package
|
||||
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done
|
||||
artifacts:
|
||||
paths:
|
||||
- build/OpenMW-*.dmg
|
||||
- "build/**/*.log"
|
||||
|
||||
macOS10.15_Xcode11:
|
||||
extends: MacOS
|
||||
image: macos-10.15-xcode-11
|
||||
|
||||
variables: &engine-targets
|
||||
targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard"
|
||||
package: "Engine"
|
||||
|
|
|
@ -10,7 +10,7 @@ addons:
|
|||
- sourceline: 'ppa:openmw/openmw'
|
||||
packages: [
|
||||
# Dev
|
||||
build-essential, cmake, clang-tools, ccache,
|
||||
build-essential, cmake, clang-tools-9, ccache,
|
||||
# Boost
|
||||
libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev,
|
||||
# FFmpeg
|
||||
|
@ -37,8 +37,8 @@ matrix:
|
|||
os: linux
|
||||
dist: focal
|
||||
env:
|
||||
- MATRIX_EVAL="CC=clang && CXX=clang++"
|
||||
- ANALYZE="scan-build --force-analyze-debug-code --use-cc clang --use-c++ clang++"
|
||||
- MATRIX_EVAL="CC=clang-9 && CXX=clang++-9"
|
||||
- ANALYZE="scan-build-9 --force-analyze-debug-code --use-cc clang-9 --use-c++ clang++-9"
|
||||
compiler: clang
|
||||
|
||||
before_install:
|
||||
|
@ -66,11 +66,13 @@ deploy:
|
|||
repo: OpenMW/openmw
|
||||
notifications:
|
||||
email:
|
||||
if: repository_slug = OpenMW/openmw AND branch = master
|
||||
recipients:
|
||||
- corrmage+travis-ci@gmail.com
|
||||
on_success: change
|
||||
on_failure: always
|
||||
irc:
|
||||
if: repository_slug = OpenMW/openmw AND branch = master
|
||||
channels:
|
||||
- "chat.freenode.net#openmw"
|
||||
on_success: change
|
||||
|
|
|
@ -185,6 +185,7 @@ Programmers
|
|||
sergoz
|
||||
ShadowRadiance
|
||||
Siimacore
|
||||
Simon Meulenbeek (simonmb)
|
||||
sir_herrbatka
|
||||
smbas
|
||||
Sophie Kirschner (pineapplemachine)
|
||||
|
@ -198,6 +199,7 @@ Programmers
|
|||
Sylvain Thesnieres (Garvek)
|
||||
t6
|
||||
terrorfisch
|
||||
Tess (tescoShoppah)
|
||||
thegriglat
|
||||
Thomas Luppi (Digmaster)
|
||||
tlmullis
|
||||
|
|
|
@ -115,6 +115,8 @@
|
|||
Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs
|
||||
Bug #5912: ImprovedBound mod doesn't work
|
||||
Bug #5914: BM: The Swimmer can't reach destination
|
||||
Bug #5923: Clicking on empty spaces between journal entries might show random topics
|
||||
Bug #5934: AddItem command doesn't accept negative values
|
||||
Feature #390: 3rd person look "over the shoulder"
|
||||
Feature #832: OpenMW-CS: Handle deleted references
|
||||
Feature #1536: Show more information about level on menu
|
||||
|
@ -122,6 +124,7 @@
|
|||
Feature #2404: Levelled List can not be placed into a container
|
||||
Feature #2686: Timestamps in openmw.log
|
||||
Feature #3171: OpenMW-CS: Instance drag selection
|
||||
Feature #3983: Wizard: Add link to buy Morrowind
|
||||
Feature #4894: Consider actors as obstacles for pathfinding
|
||||
Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing
|
||||
Feature #4977: Use the "default icon.tga" when an item's icon is not found
|
||||
|
@ -133,6 +136,7 @@
|
|||
Feature #5456: Basic collada animation support
|
||||
Feature #5457: Realistic diagonal movement
|
||||
Feature #5486: Fixes trainers to choose their training skills based on their base skill points
|
||||
Feature #5511: Add in game option to toggle HRTF support in OpenMW
|
||||
Feature #5519: Code Patch tab in launcher
|
||||
Feature #5524: Resume failed script execution after reload
|
||||
Feature #5545: Option to allow stealing from an unconscious NPC during combat
|
||||
|
@ -147,6 +151,7 @@
|
|||
Feature #5730: Add graphic herbalism option to the launcher and documents
|
||||
Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used.
|
||||
Feature #5813: Instanced groundcover support
|
||||
Feature #5814: Bsatool should be able to create BSA archives, not only to extract it
|
||||
Feature #5828: Support more than 8 lights
|
||||
Feature #5910: Fall back to delta time when physics can't keep up
|
||||
Task #5480: Drop Qt4 support
|
||||
|
|
|
@ -16,7 +16,7 @@ cmake \
|
|||
-D CMAKE_CXX_FLAGS="-stdlib=libc++" \
|
||||
-D CMAKE_C_FLAGS_RELEASE="-g -O0" \
|
||||
-D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \
|
||||
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.12" \
|
||||
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.14" \
|
||||
-D CMAKE_BUILD_TYPE=RELEASE \
|
||||
-D OPENMW_OSX_DEPLOYMENT=TRUE \
|
||||
-D BUILD_OPENMW=TRUE \
|
||||
|
|
|
@ -20,6 +20,7 @@ struct Arguments
|
|||
std::string mode;
|
||||
std::string filename;
|
||||
std::string extractfile;
|
||||
std::string addfile;
|
||||
std::string outdir;
|
||||
|
||||
bool longformat;
|
||||
|
@ -36,6 +37,10 @@ bool parseOptions (int argc, char** argv, Arguments &info)
|
|||
" Extract a file from the input archive.\n\n"
|
||||
" bsatool extractall archivefile [output_directory]\n"
|
||||
" Extract all files from the input archive.\n\n"
|
||||
" bsatool add [-a] archivefile file_to_add\n"
|
||||
" Add a file to the input archive.\n\n"
|
||||
" bsatool create [-c] archivefile\n"
|
||||
" Create an archive.\n\n"
|
||||
"Allowed options");
|
||||
|
||||
desc.add_options()
|
||||
|
@ -95,7 +100,7 @@ bool parseOptions (int argc, char** argv, Arguments &info)
|
|||
}
|
||||
|
||||
info.mode = variables["mode"].as<std::string>();
|
||||
if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall"))
|
||||
if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall" || info.mode == "add" || info.mode == "create"))
|
||||
{
|
||||
std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n"
|
||||
<< desc << std::endl;
|
||||
|
@ -126,6 +131,17 @@ bool parseOptions (int argc, char** argv, Arguments &info)
|
|||
if (variables["input-file"].as< std::vector<std::string> >().size() > 2)
|
||||
info.outdir = variables["input-file"].as< std::vector<std::string> >()[2];
|
||||
}
|
||||
else if (info.mode == "add")
|
||||
{
|
||||
if (variables["input-file"].as< std::vector<std::string> >().size() < 1)
|
||||
{
|
||||
std::cout << "\nERROR: file to add unspecified\n\n"
|
||||
<< desc << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
|
||||
info.addfile = variables["input-file"].as< std::vector<std::string> >()[1];
|
||||
}
|
||||
else if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
|
||||
info.outdir = variables["input-file"].as< std::vector<std::string> >()[1];
|
||||
|
||||
|
@ -138,6 +154,7 @@ bool parseOptions (int argc, char** argv, Arguments &info)
|
|||
int list(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
|
||||
int extract(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
|
||||
int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
|
||||
int add(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
|
@ -157,6 +174,12 @@ int main(int argc, char** argv)
|
|||
else
|
||||
bsa = std::make_unique<Bsa::BSAFile>(Bsa::BSAFile());
|
||||
|
||||
if (info.mode == "create")
|
||||
{
|
||||
bsa->open(info.filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bsa->open(info.filename);
|
||||
|
||||
if (info.mode == "list")
|
||||
|
@ -165,6 +188,8 @@ int main(int argc, char** argv)
|
|||
return extract(bsa, info);
|
||||
else if (info.mode == "extractall")
|
||||
return extractAll(bsa, info);
|
||||
else if (info.mode == "add")
|
||||
return add(bsa, info);
|
||||
else
|
||||
{
|
||||
std::cout << "Unsupported mode. That is not supposed to happen." << std::endl;
|
||||
|
@ -188,13 +213,13 @@ int list(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
|
|||
{
|
||||
// Long format
|
||||
std::ios::fmtflags f(std::cout.flags());
|
||||
std::cout << std::setw(50) << std::left << file.name;
|
||||
std::cout << std::setw(50) << std::left << file.name();
|
||||
std::cout << std::setw(8) << std::left << std::dec << file.fileSize;
|
||||
std::cout << "@ 0x" << std::hex << file.offset << std::endl;
|
||||
std::cout.flags(f);
|
||||
}
|
||||
else
|
||||
std::cout << file.name << std::endl;
|
||||
std::cout << file.name() << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -253,7 +278,7 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
|
|||
{
|
||||
for (const auto &file : bsa->getList())
|
||||
{
|
||||
std::string extractPath(file.name);
|
||||
std::string extractPath(file.name());
|
||||
Misc::StringUtils::replaceAll(extractPath, "\\", "/");
|
||||
|
||||
// Get the target path (the path the file will be extracted to)
|
||||
|
@ -272,7 +297,7 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
|
|||
|
||||
// Get a stream for the file to extract
|
||||
// (inefficient because getFile iter on the list again)
|
||||
Files::IStreamPtr data = bsa->getFile(file.name);
|
||||
Files::IStreamPtr data = bsa->getFile(file.name());
|
||||
bfs::ofstream out(target, std::ios::binary);
|
||||
|
||||
// Write the file to disk
|
||||
|
@ -283,3 +308,11 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int add(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
|
||||
{
|
||||
boost::filesystem::fstream stream(info.addfile, std::ios_base::binary | std::ios_base::out | std::ios_base::in);
|
||||
bsa->addFile(info.addfile, stream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ set(LAUNCHER
|
|||
utils/profilescombobox.cpp
|
||||
utils/textinputdialog.cpp
|
||||
utils/lineedit.cpp
|
||||
utils/openalutil.cpp
|
||||
|
||||
${CMAKE_SOURCE_DIR}/files/windows/launcher.rc
|
||||
)
|
||||
|
@ -31,6 +32,7 @@ set(LAUNCHER_HEADER
|
|||
utils/profilescombobox.hpp
|
||||
utils/textinputdialog.hpp
|
||||
utils/lineedit.hpp
|
||||
utils/openalutil.hpp
|
||||
)
|
||||
|
||||
# Headers that must be pre-processed
|
||||
|
@ -47,6 +49,7 @@ set(LAUNCHER_HEADER_MOC
|
|||
utils/textinputdialog.hpp
|
||||
utils/profilescombobox.hpp
|
||||
utils/lineedit.hpp
|
||||
utils/openalutil.hpp
|
||||
|
||||
)
|
||||
|
||||
|
@ -95,6 +98,7 @@ endif (WIN32)
|
|||
|
||||
target_link_libraries(openmw-launcher
|
||||
${SDL2_LIBRARY_ONLY}
|
||||
${OPENAL_LIBRARY}
|
||||
components
|
||||
)
|
||||
|
||||
|
|
|
@ -7,11 +7,14 @@
|
|||
#include <QFileDialog>
|
||||
#include <QCompleter>
|
||||
#include <QProxyStyle>
|
||||
#include <QString>
|
||||
#include <components/contentselector/view/contentselector.hpp>
|
||||
#include <components/contentselector/model/esmfile.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "utils/openalutil.hpp"
|
||||
|
||||
Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
|
||||
Settings::Manager &engineSettings, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
|
@ -21,7 +24,17 @@ Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
|
|||
setObjectName ("AdvancedPage");
|
||||
setupUi(this);
|
||||
|
||||
for(const char * name : Launcher::enumerateOpenALDevices())
|
||||
{
|
||||
audioDeviceSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name));
|
||||
}
|
||||
for(const char * name : Launcher::enumerateOpenALDevicesHrtf())
|
||||
{
|
||||
hrtfProfileSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name));
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
|
||||
mCellNameCompleter.setModel(&mCellNameCompleterModel);
|
||||
startDefaultCharacterAtField->setCompleter(&mCellNameCompleter);
|
||||
}
|
||||
|
@ -135,6 +148,34 @@ bool Launcher::AdvancedPage::loadSettings()
|
|||
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
|
||||
{
|
||||
loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
|
||||
|
@ -260,6 +301,33 @@ void Launcher::AdvancedPage::saveSettings()
|
|||
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
|
||||
{
|
||||
saveSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
QString getAspect(int x, int y)
|
||||
{
|
||||
int gcd = std::gcd (x, y);
|
||||
if (gcd == 0)
|
||||
return QString();
|
||||
|
||||
int xaspect = x / gcd;
|
||||
int yaspect = y / gcd;
|
||||
// special case: 8 : 5 is usually referred to as 16:10
|
||||
|
@ -298,9 +301,9 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
|
|||
return result;
|
||||
}
|
||||
|
||||
QString aspect = getAspect(mode.w, mode.h);
|
||||
QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(mode.h);
|
||||
|
||||
QString aspect = getAspect(mode.w, mode.h);
|
||||
if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) {
|
||||
resolution.append(tr("\t(Wide ") + aspect + ")");
|
||||
|
||||
|
|
55
apps/launcher/utils/openalutil.cpp
Normal file
55
apps/launcher/utils/openalutil.cpp
Normal 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;
|
||||
}
|
7
apps/launcher/utils/openalutil.hpp
Normal file
7
apps/launcher/utils/openalutil.hpp
Normal file
|
@ -0,0 +1,7 @@
|
|||
#include <vector>
|
||||
|
||||
namespace Launcher
|
||||
{
|
||||
std::vector<const char *> enumerateOpenALDevices();
|
||||
std::vector<const char *> enumerateOpenALDevicesHrtf();
|
||||
}
|
|
@ -56,7 +56,9 @@ void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData
|
|||
|
||||
|
||||
CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns)
|
||||
: InventoryColumns (columns) {}
|
||||
: InventoryColumns (columns)
|
||||
, mEffects(nullptr)
|
||||
{}
|
||||
|
||||
CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns)
|
||||
: InventoryRefIdAdapter<ESM::Ingredient> (UniversalId::Type_Ingredient, columns),
|
||||
|
@ -585,7 +587,13 @@ void CSMWorld::DoorRefIdAdapter::setData (const RefIdColumn *column, RefIdData&
|
|||
}
|
||||
|
||||
CSMWorld::LightColumns::LightColumns (const InventoryColumns& columns)
|
||||
: InventoryColumns (columns) {}
|
||||
: InventoryColumns (columns)
|
||||
, mTime(nullptr)
|
||||
, mRadius(nullptr)
|
||||
, mColor(nullptr)
|
||||
, mSound(nullptr)
|
||||
, mEmitterType(nullptr)
|
||||
{}
|
||||
|
||||
CSMWorld::LightRefIdAdapter::LightRefIdAdapter (const LightColumns& columns)
|
||||
: InventoryRefIdAdapter<ESM::Light> (UniversalId::Type_Light, columns), mColumns (columns)
|
||||
|
@ -1454,7 +1462,15 @@ int CSMWorld::CreatureMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *co
|
|||
}
|
||||
|
||||
CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns)
|
||||
: EnchantableColumns (columns) {}
|
||||
: EnchantableColumns (columns)
|
||||
, mType(nullptr)
|
||||
, mHealth(nullptr)
|
||||
, mSpeed(nullptr)
|
||||
, mReach(nullptr)
|
||||
, mChop{nullptr}
|
||||
, mSlash{nullptr}
|
||||
, mThrust{nullptr}
|
||||
{}
|
||||
|
||||
CSMWorld::WeaponRefIdAdapter::WeaponRefIdAdapter (const WeaponColumns& columns)
|
||||
: EnchantableRefIdAdapter<ESM::Weapon> (UniversalId::Type_Weapon, columns), mColumns (columns)
|
||||
|
|
|
@ -178,7 +178,11 @@ namespace CSMWorld
|
|||
const RefIdColumn *mName;
|
||||
const RefIdColumn *mScript;
|
||||
|
||||
NameColumns (const ModelColumns& base) : ModelColumns (base) {}
|
||||
NameColumns (const ModelColumns& base)
|
||||
: ModelColumns (base)
|
||||
, mName(nullptr)
|
||||
, mScript(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
/// \brief Adapter for IDs with names (all but levelled lists and statics)
|
||||
|
@ -247,7 +251,12 @@ namespace CSMWorld
|
|||
const RefIdColumn *mWeight;
|
||||
const RefIdColumn *mValue;
|
||||
|
||||
InventoryColumns (const NameColumns& base) : NameColumns (base) {}
|
||||
InventoryColumns (const NameColumns& base)
|
||||
: NameColumns (base)
|
||||
, mIcon(nullptr)
|
||||
, mWeight(nullptr)
|
||||
, mValue(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
/// \brief Adapter for IDs that can go into an inventory
|
||||
|
@ -405,7 +414,11 @@ namespace CSMWorld
|
|||
const RefIdColumn *mEnchantment;
|
||||
const RefIdColumn *mEnchantmentPoints;
|
||||
|
||||
EnchantableColumns (const InventoryColumns& base) : InventoryColumns (base) {}
|
||||
EnchantableColumns (const InventoryColumns& base)
|
||||
: InventoryColumns (base)
|
||||
, mEnchantment(nullptr)
|
||||
, mEnchantmentPoints(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
/// \brief Adapter for enchantable IDs
|
||||
|
@ -474,7 +487,11 @@ namespace CSMWorld
|
|||
const RefIdColumn *mQuality;
|
||||
const RefIdColumn *mUses;
|
||||
|
||||
ToolColumns (const InventoryColumns& base) : InventoryColumns (base) {}
|
||||
ToolColumns (const InventoryColumns& base)
|
||||
: InventoryColumns (base)
|
||||
, mQuality(nullptr)
|
||||
, mUses(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
/// \brief Adapter for tools with limited uses IDs (lockpick, repair, probes)
|
||||
|
@ -549,7 +566,17 @@ namespace CSMWorld
|
|||
const RefIdColumn *mAiPackages;
|
||||
std::map<const RefIdColumn *, unsigned int> mServices;
|
||||
|
||||
ActorColumns (const NameColumns& base) : NameColumns (base) {}
|
||||
ActorColumns (const NameColumns& base)
|
||||
: NameColumns (base)
|
||||
, mHello(nullptr)
|
||||
, mFlee(nullptr)
|
||||
, mFight(nullptr)
|
||||
, mAlarm(nullptr)
|
||||
, mInventory(nullptr)
|
||||
, mSpells(nullptr)
|
||||
, mDestinations(nullptr)
|
||||
, mAiPackages(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
/// \brief Adapter for actor IDs (handles common AI functionality)
|
||||
|
@ -2054,7 +2081,11 @@ namespace CSMWorld
|
|||
const RefIdColumn *mLevList;
|
||||
const RefIdColumn *mNestedListLevList;
|
||||
|
||||
LevListColumns (const BaseColumns& base) : BaseColumns (base) {}
|
||||
LevListColumns (const BaseColumns& base)
|
||||
: BaseColumns (base)
|
||||
, mLevList(nullptr)
|
||||
, mNestedListLevList(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
template<typename RecordT>
|
||||
|
|
|
@ -33,26 +33,6 @@ namespace Compiler
|
|||
class Context;
|
||||
}
|
||||
|
||||
namespace MWScript
|
||||
{
|
||||
class ScriptManager;
|
||||
}
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
class SoundManager;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class World;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace Files
|
||||
{
|
||||
struct ConfigurationManager;
|
||||
|
|
|
@ -32,7 +32,6 @@ namespace MyGUI
|
|||
|
||||
namespace ESM
|
||||
{
|
||||
struct Class;
|
||||
class ESMReader;
|
||||
class ESMWriter;
|
||||
struct CellId;
|
||||
|
|
|
@ -281,13 +281,13 @@ namespace MWBase
|
|||
virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
|
||||
virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0;
|
||||
|
||||
virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool moveToActive=false) = 0;
|
||||
virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false) = 0;
|
||||
///< @return an updated Ptr in case the Ptr's cell changes
|
||||
|
||||
virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0;
|
||||
///< @return an updated Ptr
|
||||
|
||||
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec) = 0;
|
||||
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive) = 0;
|
||||
///< @return an updated Ptr
|
||||
|
||||
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
|
||||
|
@ -607,7 +607,7 @@ namespace MWBase
|
|||
|
||||
/// Return a vector aiming the actor's weapon towards a target.
|
||||
/// @note The length of the vector is the distance between actor and target.
|
||||
virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0;
|
||||
virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target, bool isRangedCombat) = 0;
|
||||
|
||||
/// Return the distance between actor's weapon and target's collision box.
|
||||
virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0;
|
||||
|
|
|
@ -46,11 +46,6 @@ namespace MWClass
|
|||
mStore.readState(inventory);
|
||||
}
|
||||
|
||||
MWWorld::CustomData *ContainerCustomData::clone() const
|
||||
{
|
||||
return new ContainerCustomData (*this);
|
||||
}
|
||||
|
||||
ContainerCustomData& ContainerCustomData::asContainerCustomData()
|
||||
{
|
||||
return *this;
|
||||
|
@ -72,7 +67,7 @@ namespace MWClass
|
|||
MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
|
||||
|
||||
// store
|
||||
ptr.getRefData().setCustomData (std::make_unique<ContainerCustomData>(*ref->mBase, ptr.getCell()).release());
|
||||
ptr.getRefData().setCustomData (std::make_unique<ContainerCustomData>(*ref->mBase, ptr.getCell()));
|
||||
|
||||
MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell());
|
||||
}
|
||||
|
@ -317,7 +312,7 @@ namespace MWClass
|
|||
return;
|
||||
|
||||
const ESM::ContainerState& containerState = state.asContainerState();
|
||||
ptr.getRefData().setCustomData(std::make_unique<ContainerCustomData>(containerState.mInventory).release());
|
||||
ptr.getRefData().setCustomData(std::make_unique<ContainerCustomData>(containerState.mInventory));
|
||||
}
|
||||
|
||||
void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
|
||||
|
|
|
@ -13,15 +13,13 @@ namespace ESM
|
|||
|
||||
namespace MWClass
|
||||
{
|
||||
class ContainerCustomData : public MWWorld::CustomData
|
||||
class ContainerCustomData : public MWWorld::TypedCustomData<ContainerCustomData>
|
||||
{
|
||||
MWWorld::ContainerStore mStore;
|
||||
public:
|
||||
ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell);
|
||||
ContainerCustomData(const ESM::InventoryState& inventory);
|
||||
|
||||
MWWorld::CustomData *clone() const override;
|
||||
|
||||
ContainerCustomData& asContainerCustomData() override;
|
||||
const ContainerCustomData& asContainerCustomData() const override;
|
||||
|
||||
|
|
|
@ -51,14 +51,16 @@ namespace
|
|||
namespace MWClass
|
||||
{
|
||||
|
||||
class CreatureCustomData : public MWWorld::CustomData
|
||||
class CreatureCustomData : public MWWorld::TypedCustomData<CreatureCustomData>
|
||||
{
|
||||
public:
|
||||
MWMechanics::CreatureStats mCreatureStats;
|
||||
MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures
|
||||
std::unique_ptr<MWWorld::ContainerStore> mContainerStore; // may be InventoryStore for some creatures
|
||||
MWMechanics::Movement mMovement;
|
||||
|
||||
MWWorld::CustomData *clone() const override;
|
||||
CreatureCustomData() = default;
|
||||
CreatureCustomData(const CreatureCustomData& other);
|
||||
CreatureCustomData(CreatureCustomData&& other) noexcept = default;
|
||||
|
||||
CreatureCustomData& asCreatureCustomData() override
|
||||
{
|
||||
|
@ -68,16 +70,13 @@ namespace MWClass
|
|||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
CreatureCustomData() : mContainerStore(nullptr) {}
|
||||
virtual ~CreatureCustomData() { delete mContainerStore; }
|
||||
};
|
||||
|
||||
MWWorld::CustomData *CreatureCustomData::clone() const
|
||||
CreatureCustomData::CreatureCustomData(const CreatureCustomData& other)
|
||||
: mCreatureStats(other.mCreatureStats),
|
||||
mContainerStore(other.mContainerStore->clone()),
|
||||
mMovement(other.mMovement)
|
||||
{
|
||||
CreatureCustomData* cloned = new CreatureCustomData (*this);
|
||||
cloned->mContainerStore = mContainerStore->clone();
|
||||
return cloned;
|
||||
}
|
||||
|
||||
const Creature::GMST& Creature::getGmst()
|
||||
|
@ -148,16 +147,16 @@ namespace MWClass
|
|||
// inventory
|
||||
bool hasInventory = hasInventoryStore(ptr);
|
||||
if (hasInventory)
|
||||
data->mContainerStore = new MWWorld::InventoryStore();
|
||||
data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
|
||||
else
|
||||
data->mContainerStore = new MWWorld::ContainerStore();
|
||||
data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
|
||||
|
||||
data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold);
|
||||
|
||||
data->mCreatureStats.setNeedRecalcDynamicStats(false);
|
||||
|
||||
// store
|
||||
ptr.getRefData().setCustomData(data.release());
|
||||
ptr.getRefData().setCustomData(std::move(data));
|
||||
|
||||
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
|
||||
|
||||
|
@ -758,11 +757,11 @@ namespace MWClass
|
|||
std::unique_ptr<CreatureCustomData> data (new CreatureCustomData);
|
||||
|
||||
if (hasInventoryStore(ptr))
|
||||
data->mContainerStore = new MWWorld::InventoryStore();
|
||||
data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
|
||||
else
|
||||
data->mContainerStore = new MWWorld::ContainerStore();
|
||||
data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
|
||||
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
ptr.getRefData().setCustomData (std::move(data));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -10,15 +10,13 @@
|
|||
|
||||
namespace MWClass
|
||||
{
|
||||
class CreatureLevListCustomData : public MWWorld::CustomData
|
||||
class CreatureLevListCustomData : public MWWorld::TypedCustomData<CreatureLevListCustomData>
|
||||
{
|
||||
public:
|
||||
// actorId of the creature we spawned
|
||||
int mSpawnActorId;
|
||||
bool mSpawn; // Should a new creature be spawned?
|
||||
|
||||
MWWorld::CustomData *clone() const override;
|
||||
|
||||
CreatureLevListCustomData& asCreatureLevListCustomData() override
|
||||
{
|
||||
return *this;
|
||||
|
@ -29,11 +27,6 @@ namespace MWClass
|
|||
}
|
||||
};
|
||||
|
||||
MWWorld::CustomData *CreatureLevListCustomData::clone() const
|
||||
{
|
||||
return new CreatureLevListCustomData (*this);
|
||||
}
|
||||
|
||||
std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const
|
||||
{
|
||||
return "";
|
||||
|
@ -138,11 +131,11 @@ namespace MWClass
|
|||
{
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
std::unique_ptr<CreatureLevListCustomData> data (new CreatureLevListCustomData);
|
||||
std::unique_ptr<CreatureLevListCustomData> data = std::make_unique<CreatureLevListCustomData>();
|
||||
data->mSpawnActorId = -1;
|
||||
data->mSpawn = true;
|
||||
|
||||
ptr.getRefData().setCustomData(data.release());
|
||||
ptr.getRefData().setCustomData(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,13 +31,11 @@
|
|||
|
||||
namespace MWClass
|
||||
{
|
||||
class DoorCustomData : public MWWorld::CustomData
|
||||
class DoorCustomData : public MWWorld::TypedCustomData<DoorCustomData>
|
||||
{
|
||||
public:
|
||||
MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle;
|
||||
|
||||
MWWorld::CustomData *clone() const override;
|
||||
|
||||
DoorCustomData& asDoorCustomData() override
|
||||
{
|
||||
return *this;
|
||||
|
@ -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
|
||||
{
|
||||
if (!model.empty())
|
||||
|
@ -327,8 +320,7 @@ namespace MWClass
|
|||
{
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
std::unique_ptr<DoorCustomData> data(new DoorCustomData);
|
||||
ptr.getRefData().setCustomData(data.release());
|
||||
ptr.getRefData().setCustomData(std::make_unique<DoorCustomData>());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -246,15 +246,13 @@ namespace
|
|||
namespace MWClass
|
||||
{
|
||||
|
||||
class NpcCustomData : public MWWorld::CustomData
|
||||
class NpcCustomData : public MWWorld::TypedCustomData<NpcCustomData>
|
||||
{
|
||||
public:
|
||||
MWMechanics::NpcStats mNpcStats;
|
||||
MWMechanics::Movement mMovement;
|
||||
MWWorld::InventoryStore mInventoryStore;
|
||||
|
||||
MWWorld::CustomData *clone() const override;
|
||||
|
||||
NpcCustomData& asNpcCustomData() override
|
||||
{
|
||||
return *this;
|
||||
|
@ -265,11 +263,6 @@ namespace MWClass
|
|||
}
|
||||
};
|
||||
|
||||
MWWorld::CustomData *NpcCustomData::clone() const
|
||||
{
|
||||
return new NpcCustomData (*this);
|
||||
}
|
||||
|
||||
const Npc::GMST& Npc::getGmst()
|
||||
{
|
||||
static GMST gmst;
|
||||
|
@ -397,7 +390,7 @@ namespace MWClass
|
|||
data->mNpcStats.setGoldPool(gold);
|
||||
|
||||
// store
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
ptr.getRefData().setCustomData(std::move(data));
|
||||
|
||||
getInventoryStore(ptr).autoEquip(ptr);
|
||||
}
|
||||
|
@ -1302,8 +1295,7 @@ namespace MWClass
|
|||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
// Create a CustomData, but don't fill it from ESM records (not needed)
|
||||
std::unique_ptr<NpcCustomData> data (new NpcCustomData);
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
ptr.getRefData().setCustomData(std::make_unique<NpcCustomData>());
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "bookpage.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "MyGUI_RenderItem.h"
|
||||
#include "MyGUI_RenderManager.h"
|
||||
#include "MyGUI_TextureUtility.h"
|
||||
|
@ -894,6 +896,27 @@ protected:
|
|||
return mIsPageReset || (mPage != page);
|
||||
}
|
||||
|
||||
std::optional<MyGUI::IntPoint> getAdjustedPos(int left, int top, bool move = false)
|
||||
{
|
||||
if (!mBook)
|
||||
return {};
|
||||
|
||||
if (mPage >= mBook->mPages.size())
|
||||
return {};
|
||||
|
||||
MyGUI::IntPoint pos (left, top);
|
||||
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
|
||||
// work around inconsistency in MyGUI where the mouse press coordinates aren't
|
||||
// transformed by the current Layer (even though mouse *move* events are).
|
||||
if(!move)
|
||||
pos = mNode->getLayer()->getPosition(left, top);
|
||||
#endif
|
||||
pos.left -= mCroppedParent->getAbsoluteLeft ();
|
||||
pos.top -= mCroppedParent->getAbsoluteTop ();
|
||||
pos.top += mViewTop;
|
||||
return pos;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
typedef TypesetBookImpl::StyleImpl Style;
|
||||
|
@ -952,16 +975,10 @@ public:
|
|||
|
||||
void onMouseMove (int left, int top)
|
||||
{
|
||||
if (!mBook)
|
||||
return;
|
||||
|
||||
if (mPage >= mBook->mPages.size())
|
||||
return;
|
||||
|
||||
left -= mCroppedParent->getAbsoluteLeft ();
|
||||
top -= mCroppedParent->getAbsoluteTop ();
|
||||
|
||||
Style * hit = mBook->hitTestWithMargin (left, mViewTop + top);
|
||||
Style * hit = nullptr;
|
||||
if(auto pos = getAdjustedPos(left, top, true))
|
||||
if(pos->top <= mViewBottom)
|
||||
hit = mBook->hitTestWithMargin (pos->left, pos->top);
|
||||
|
||||
if (mLastDown == MyGUI::MouseButton::None)
|
||||
{
|
||||
|
@ -991,24 +1008,11 @@ public:
|
|||
|
||||
void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id)
|
||||
{
|
||||
if (!mBook)
|
||||
return;
|
||||
auto pos = getAdjustedPos(left, top);
|
||||
|
||||
if (mPage >= mBook->mPages.size())
|
||||
return;
|
||||
|
||||
// work around inconsistency in MyGUI where the mouse press coordinates aren't
|
||||
// transformed by the current Layer (even though mouse *move* events are).
|
||||
MyGUI::IntPoint pos (left, top);
|
||||
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
|
||||
pos = mNode->getLayer()->getPosition(left, top);
|
||||
#endif
|
||||
pos.left -= mCroppedParent->getAbsoluteLeft ();
|
||||
pos.top -= mCroppedParent->getAbsoluteTop ();
|
||||
|
||||
if (mLastDown == MyGUI::MouseButton::None)
|
||||
if (pos && mLastDown == MyGUI::MouseButton::None)
|
||||
{
|
||||
mFocusItem = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top);
|
||||
mFocusItem = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr;
|
||||
mItemActive = true;
|
||||
|
||||
dirtyFocusItem ();
|
||||
|
@ -1019,25 +1023,11 @@ public:
|
|||
|
||||
void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id)
|
||||
{
|
||||
if (!mBook)
|
||||
return;
|
||||
auto pos = getAdjustedPos(left, top);
|
||||
|
||||
if (mPage >= mBook->mPages.size())
|
||||
return;
|
||||
|
||||
// work around inconsistency in MyGUI where the mouse release coordinates aren't
|
||||
// transformed by the current Layer (even though mouse *move* events are).
|
||||
MyGUI::IntPoint pos (left, top);
|
||||
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
|
||||
pos = mNode->getLayer()->getPosition(left, top);
|
||||
#endif
|
||||
|
||||
pos.left -= mCroppedParent->getAbsoluteLeft ();
|
||||
pos.top -= mCroppedParent->getAbsoluteTop ();
|
||||
|
||||
if (mLastDown == id)
|
||||
if (pos && mLastDown == id)
|
||||
{
|
||||
Style * item = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top);
|
||||
Style * item = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr;
|
||||
|
||||
bool clicked = mFocusItem == item;
|
||||
|
||||
|
|
|
@ -6,11 +6,6 @@
|
|||
|
||||
#include "itemmodel.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Environment;
|
||||
}
|
||||
|
||||
namespace MyGUI
|
||||
{
|
||||
class Gui;
|
||||
|
@ -19,7 +14,6 @@ namespace MyGUI
|
|||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
class ContainerWindow;
|
||||
class ItemView;
|
||||
class SortFilterItemModel;
|
||||
|
|
|
@ -15,11 +15,6 @@ namespace Gui
|
|||
class MWList;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class ResponseCallback;
|
||||
|
|
|
@ -71,7 +71,7 @@ namespace MWGui
|
|||
, mUpdateTimer(0.f)
|
||||
{
|
||||
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
||||
if (uiScale > 1.0)
|
||||
if (uiScale > 0.f)
|
||||
mScaleFactor = uiScale;
|
||||
|
||||
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture()));
|
||||
|
|
|
@ -7,11 +7,6 @@
|
|||
#include <MyGUI_RenderManager.h>
|
||||
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
class RaceSelectionPreview;
|
||||
|
|
|
@ -11,11 +11,6 @@ namespace ESM
|
|||
struct Spell;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class ReviewDialog : public WindowModal
|
||||
|
|
|
@ -65,6 +65,9 @@ namespace
|
|||
std::string getAspect (int x, int y)
|
||||
{
|
||||
int gcd = std::gcd (x, y);
|
||||
if (gcd == 0)
|
||||
return std::string();
|
||||
|
||||
int xaspect = x / gcd;
|
||||
int yaspect = y / gcd;
|
||||
// special case: 8 : 5 is usually referred to as 16:10
|
||||
|
@ -282,8 +285,10 @@ namespace MWGui
|
|||
std::sort(resolutions.begin(), resolutions.end(), sortResolutions);
|
||||
for (std::pair<int, int>& resolution : resolutions)
|
||||
{
|
||||
std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second)
|
||||
+ " (" + getAspect(resolution.first, resolution.second) + ")";
|
||||
std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second);
|
||||
std::string aspect = getAspect(resolution.first, resolution.second);
|
||||
if (!aspect.empty())
|
||||
str = str + " (" + aspect + ")";
|
||||
|
||||
if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE)
|
||||
mResolutionList->addItem(str);
|
||||
|
|
|
@ -3,11 +3,6 @@
|
|||
|
||||
#include "windowbase.hpp"
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class SettingsWindow : public WindowBase
|
||||
|
|
|
@ -15,11 +15,6 @@ namespace MyGUI
|
|||
class Widget;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class SpellBuyingWindow : public ReferenceInterface, public WindowBase
|
||||
|
|
|
@ -393,7 +393,8 @@ namespace MWGui
|
|||
MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||
int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
|
||||
|
||||
if (MyGUI::utility::parseInt(mPriceLabel->getCaption()) > playerGold)
|
||||
int price = MyGUI::utility::parseInt(mPriceLabel->getCaption());
|
||||
if (price > playerGold)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}");
|
||||
return;
|
||||
|
@ -401,8 +402,6 @@ namespace MWGui
|
|||
|
||||
mSpell.mName = mNameEdit->getCaption();
|
||||
|
||||
int price = MyGUI::utility::parseInt(mPriceLabel->getCaption());
|
||||
|
||||
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player);
|
||||
|
||||
// add gold to NPC trading gold pool
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
|
||||
class StatsWindow : public WindowPinnableBase, public NoDrop, public StatsListener
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -3,11 +3,6 @@
|
|||
|
||||
#include "windowbase.hpp"
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class TextInputDialog : public WindowModal
|
||||
|
|
|
@ -58,12 +58,12 @@ namespace MWGui
|
|||
}
|
||||
}
|
||||
|
||||
int TimeAdvancer::getHours()
|
||||
int TimeAdvancer::getHours() const
|
||||
{
|
||||
return mHours;
|
||||
}
|
||||
|
||||
bool TimeAdvancer::isRunning()
|
||||
bool TimeAdvancer::isRunning() const
|
||||
{
|
||||
return mRunning;
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ namespace MWGui
|
|||
void stop();
|
||||
void onFrame(float dt);
|
||||
|
||||
int getHours();
|
||||
bool isRunning();
|
||||
int getHours() const;
|
||||
bool isRunning() const;
|
||||
|
||||
// signals
|
||||
typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
|
||||
|
|
|
@ -11,12 +11,6 @@ namespace MyGUI
|
|||
class Widget;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class TravelWindow : public ReferenceInterface, public WindowBase
|
||||
|
|
|
@ -3,11 +3,6 @@
|
|||
|
||||
#include "layout.hpp"
|
||||
|
||||
namespace MWBase
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
|
@ -15,7 +10,6 @@ namespace MWWorld
|
|||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
class DragAndDrop;
|
||||
|
||||
class WindowBase: public Layout
|
||||
|
|
|
@ -114,7 +114,6 @@ namespace MWGui
|
|||
class TrainingWindow;
|
||||
class SpellIcons;
|
||||
class MerchantRepair;
|
||||
class Repair;
|
||||
class SoulgemDialog;
|
||||
class Recharge;
|
||||
class CompanionWindow;
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
|
||||
class WindowPinnableBase: public WindowBase
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace MWInput
|
|||
}
|
||||
|
||||
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
||||
if (uiScale != 0.f)
|
||||
if (uiScale > 0.f)
|
||||
mInvUiScalingFactor = 1.f / uiScale;
|
||||
|
||||
float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input");
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace MWInput
|
|||
, mGuiCursorEnabled(true)
|
||||
{
|
||||
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
||||
if (uiScale != 0.f)
|
||||
if (uiScale > 0.f)
|
||||
mInvUiScalingFactor = 1.f / uiScale;
|
||||
|
||||
int w,h;
|
||||
|
|
|
@ -45,13 +45,13 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterCont
|
|||
return true; //Door is no longer opening
|
||||
|
||||
ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door
|
||||
float x = pos.pos[0] - tPos.pos[0];
|
||||
float y = pos.pos[1] - tPos.pos[1];
|
||||
float x = pos.pos[1] - tPos.pos[1];
|
||||
float y = pos.pos[0] - tPos.pos[0];
|
||||
|
||||
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
||||
|
||||
// Turn away from the door and move when turn completed
|
||||
if (zTurn(actor, std::atan2(x,y) + getAdjustedAngle(), osg::DegreesToRadians(5.f)))
|
||||
if (zTurn(actor, std::atan2(y,x) + getAdjustedAngle(), osg::DegreesToRadians(5.f)))
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
|
||||
else
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||
|
|
|
@ -228,7 +228,6 @@ namespace MWMechanics
|
|||
const osg::Vec3f vActorPos(pos.asVec3());
|
||||
const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3());
|
||||
|
||||
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
|
||||
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
|
||||
|
||||
storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS);
|
||||
|
@ -236,13 +235,14 @@ namespace MWMechanics
|
|||
if (isRangedCombat)
|
||||
{
|
||||
// rotate actor taking into account target movement direction and projectile speed
|
||||
vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
|
||||
osg::Vec3f vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
|
||||
|
||||
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
||||
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, false);
|
||||
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
||||
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3();
|
||||
osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
|
||||
osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, true);
|
||||
float distToTarget = vDirToTarget.length();
|
||||
|
||||
osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos;
|
||||
|
|
|
@ -2955,7 +2955,7 @@ void CharacterController::updateHeadTracking(float duration)
|
|||
}
|
||||
else
|
||||
// no head node to look at, fall back to look at center of collision box
|
||||
direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget);
|
||||
direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget, false);
|
||||
}
|
||||
direction.normalize();
|
||||
|
||||
|
|
|
@ -681,7 +681,7 @@ namespace MWMechanics
|
|||
// Deviating from Morrowind here: it doesn't increase disposition on marginal wins,
|
||||
// which seems to be a bug (MCP fixes it too).
|
||||
// Original logic: x = 0, y = -iPerMinChange
|
||||
x = -iPerMinChange;
|
||||
x = iPerMinChange;
|
||||
y = x; // This goes unused.
|
||||
}
|
||||
else
|
||||
|
|
|
@ -179,6 +179,7 @@ bool Actor::setPosition(const osg::Vec3f& position)
|
|||
if (mSkipSimulation)
|
||||
return false;
|
||||
bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
|
||||
updateWorldPosition();
|
||||
applyOffsetChange();
|
||||
mPreviousPosition = mPosition;
|
||||
mPosition = position;
|
||||
|
|
|
@ -317,7 +317,7 @@ namespace MWPhysics
|
|||
|
||||
// init
|
||||
for (auto& data : actorsData)
|
||||
data.updatePosition();
|
||||
data.updatePosition(mCollisionWorld);
|
||||
mPrevStepCount = numSteps;
|
||||
mRemainingSteps = numSteps;
|
||||
mTimeAccum = timeAccum;
|
||||
|
|
|
@ -60,6 +60,22 @@
|
|||
#include "movementsolver.hpp"
|
||||
#include "mtphysics.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool canMoveToWaterSurface(const MWPhysics::Actor* physicActor, const float waterlevel, btCollisionWorld* world)
|
||||
{
|
||||
if (!physicActor)
|
||||
return false;
|
||||
const float halfZ = physicActor->getHalfExtents().z();
|
||||
const osg::Vec3f actorPosition = physicActor->getPosition();
|
||||
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
|
||||
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
|
||||
MWPhysics::ActorTracer tracer;
|
||||
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world);
|
||||
return (tracer.mFraction >= 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
|
||||
|
@ -347,16 +363,7 @@ namespace MWPhysics
|
|||
|
||||
bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel)
|
||||
{
|
||||
const Actor* physicActor = getActor(actor);
|
||||
if (!physicActor)
|
||||
return false;
|
||||
const float halfZ = physicActor->getHalfExtents().z();
|
||||
const osg::Vec3f actorPosition = physicActor->getPosition();
|
||||
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
|
||||
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
|
||||
ActorTracer tracer;
|
||||
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, mCollisionWorld.get());
|
||||
return (tracer.mFraction >= 1.0f);
|
||||
return ::canMoveToWaterSurface(getActor(actor), waterlevel, mCollisionWorld.get());
|
||||
}
|
||||
|
||||
osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const
|
||||
|
@ -772,16 +779,10 @@ namespace MWPhysics
|
|||
const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects();
|
||||
|
||||
bool waterCollision = false;
|
||||
bool moveToWaterSurface = false;
|
||||
if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude())
|
||||
{
|
||||
if (!world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3())))
|
||||
if (physicActor->getCollisionMode() || !world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3())))
|
||||
waterCollision = true;
|
||||
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(character, waterlevel))
|
||||
{
|
||||
moveToWaterSurface = true;
|
||||
waterCollision = true;
|
||||
}
|
||||
}
|
||||
|
||||
physicActor->setCanWaterWalk(waterCollision);
|
||||
|
@ -794,7 +795,7 @@ namespace MWPhysics
|
|||
if (!willSimulate)
|
||||
standingOn = physicActor->getStandingOnPtr();
|
||||
|
||||
actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel);
|
||||
actorsFrameData.emplace_back(std::move(physicActor), standingOn, waterCollision, movement, slowFall, waterlevel);
|
||||
}
|
||||
mMovementQueue.clear();
|
||||
return actorsFrameData;
|
||||
|
@ -937,9 +938,9 @@ namespace MWPhysics
|
|||
}
|
||||
|
||||
ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn,
|
||||
bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel)
|
||||
bool waterCollision, osg::Vec3f movement, float slowFall, float waterlevel)
|
||||
: mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn),
|
||||
mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface),
|
||||
mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision),
|
||||
mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos()
|
||||
{
|
||||
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
|
@ -953,7 +954,7 @@ namespace MWPhysics
|
|||
mWasOnGround = actor->getOnGround();
|
||||
}
|
||||
|
||||
void ActorFrameData::updatePosition()
|
||||
void ActorFrameData::updatePosition(btCollisionWorld* world)
|
||||
{
|
||||
mActorRaw->updateWorldPosition();
|
||||
// If physics runs "fast enough", position are interpolated without simulation
|
||||
|
@ -961,10 +962,10 @@ namespace MWPhysics
|
|||
// regardless of simulation speed.
|
||||
mActorRaw->applyOffsetChange();
|
||||
mPosition = mActorRaw->getPosition();
|
||||
if (mMoveToWaterSurface)
|
||||
if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world))
|
||||
{
|
||||
mPosition.z() = mWaterlevel;
|
||||
MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z());
|
||||
MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z(), false);
|
||||
}
|
||||
mOldHeight = mPosition.z();
|
||||
mRefpos = mActorRaw->getPtr().getRefData().getPosition();
|
||||
|
|
|
@ -79,7 +79,7 @@ namespace MWPhysics
|
|||
struct ActorFrameData
|
||||
{
|
||||
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
|
||||
void updatePosition();
|
||||
void updatePosition(btCollisionWorld* world);
|
||||
std::weak_ptr<Actor> mActor;
|
||||
Actor* mActorRaw;
|
||||
MWWorld::Ptr mStandingOn;
|
||||
|
@ -90,7 +90,7 @@ namespace MWPhysics
|
|||
bool mDidJump;
|
||||
bool mFloatToSurface;
|
||||
bool mNeedLand;
|
||||
bool mMoveToWaterSurface;
|
||||
bool mWaterCollision;
|
||||
float mWaterlevel;
|
||||
float mSlowFall;
|
||||
float mOldHeight;
|
||||
|
|
|
@ -93,7 +93,7 @@ LocalMap::LocalMap(osg::Group* root)
|
|||
{
|
||||
// Increase map resolution, if use UI scaling
|
||||
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
||||
if (uiScale > 1.0)
|
||||
if (uiScale > 0.f)
|
||||
mMapResolution *= uiScale;
|
||||
|
||||
SceneUtil::FindByNameVisitor find("Scene Root");
|
||||
|
|
|
@ -63,7 +63,7 @@ namespace MWRender
|
|||
{
|
||||
std::set<ESM::RefNum> mDisabled;
|
||||
std::set<ESM::RefNum> mBlacklist;
|
||||
bool operator==(const RefTracker&other) { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; }
|
||||
bool operator==(const RefTracker&other) const { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; }
|
||||
};
|
||||
RefTracker mRefTracker;
|
||||
RefTracker mRefTrackerNew;
|
||||
|
|
|
@ -855,6 +855,7 @@ namespace MWRender
|
|||
RenderingManager::RayResult result;
|
||||
result.mHit = false;
|
||||
result.mHitRefnum.mContentFile = -1;
|
||||
result.mHitRefnum.mIndex = -1;
|
||||
result.mRatio = 0;
|
||||
if (intersector->containsIntersections())
|
||||
{
|
||||
|
|
|
@ -45,6 +45,9 @@
|
|||
#include <components/sceneutil/visitor.hpp>
|
||||
#include <components/sceneutil/shadow.hpp>
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#include <components/nifosg/particle.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
@ -1164,7 +1167,7 @@ void SkyManager::create()
|
|||
{
|
||||
assert(!mCreated);
|
||||
|
||||
mAtmosphereDay = mSceneManager->getInstance("meshes/sky_atmosphere.nif", mEarlyRenderBinRoot);
|
||||
mAtmosphereDay = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot);
|
||||
ModVertexAlphaVisitor modAtmosphere(0);
|
||||
mAtmosphereDay->accept(modAtmosphere);
|
||||
|
||||
|
@ -1176,10 +1179,10 @@ void SkyManager::create()
|
|||
mEarlyRenderBinRoot->addChild(mAtmosphereNightNode);
|
||||
|
||||
osg::ref_ptr<osg::Node> atmosphereNight;
|
||||
if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif"))
|
||||
atmosphereNight = mSceneManager->getInstance("meshes/sky_night_02.nif", mAtmosphereNightNode);
|
||||
if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models")))
|
||||
atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight02", "Models"), mAtmosphereNightNode);
|
||||
else
|
||||
atmosphereNight = mSceneManager->getInstance("meshes/sky_night_01.nif", mAtmosphereNightNode);
|
||||
atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight01", "Models"), mAtmosphereNightNode);
|
||||
atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
|
||||
ModVertexAlphaVisitor modStars(2);
|
||||
atmosphereNight->accept(modStars);
|
||||
|
@ -1193,14 +1196,14 @@ void SkyManager::create()
|
|||
|
||||
mCloudNode = new osg::PositionAttitudeTransform;
|
||||
mEarlyRenderBinRoot->addChild(mCloudNode);
|
||||
mCloudMesh = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode);
|
||||
mCloudMesh = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode);
|
||||
ModVertexAlphaVisitor modClouds(1);
|
||||
mCloudMesh->accept(modClouds);
|
||||
mCloudUpdater = new CloudUpdater;
|
||||
mCloudUpdater->setOpacity(1.f);
|
||||
mCloudMesh->addUpdateCallback(mCloudUpdater);
|
||||
|
||||
mCloudMesh2 = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode);
|
||||
mCloudMesh2 = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode);
|
||||
mCloudMesh2->accept(modClouds);
|
||||
mCloudUpdater2 = new CloudUpdater;
|
||||
mCloudUpdater2->setOpacity(0.f);
|
||||
|
@ -1597,7 +1600,7 @@ void SkyManager::update(float duration)
|
|||
if (mParticleNode)
|
||||
{
|
||||
// Morrowind deliberately rotates the blizzard mesh, so so should we.
|
||||
if (mCurrentParticleEffect == "meshes\\blizzard.nif")
|
||||
if (mCurrentParticleEffect == Settings::Manager::getString("weatherblizzard", "Models"))
|
||||
quat.makeRotate(osg::Vec3f(-1,0,0), mStormDirection);
|
||||
mParticleNode->setAttitude(quat);
|
||||
}
|
||||
|
@ -1897,16 +1900,16 @@ void SkyManager::setWaterHeight(float height)
|
|||
|
||||
void SkyManager::listAssetsToPreload(std::vector<std::string>& models, std::vector<std::string>& textures)
|
||||
{
|
||||
models.emplace_back("meshes/sky_atmosphere.nif");
|
||||
if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif"))
|
||||
models.emplace_back("meshes/sky_night_02.nif");
|
||||
models.emplace_back("meshes/sky_night_01.nif");
|
||||
models.emplace_back("meshes/sky_clouds_01.nif");
|
||||
models.emplace_back(Settings::Manager::getString("skyatmosphere", "Models"));
|
||||
if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models")))
|
||||
models.emplace_back(Settings::Manager::getString("skynight02", "Models"));
|
||||
models.emplace_back(Settings::Manager::getString("skynight01", "Models"));
|
||||
models.emplace_back(Settings::Manager::getString("skyclouds", "Models"));
|
||||
|
||||
models.emplace_back("meshes\\ashcloud.nif");
|
||||
models.emplace_back("meshes\\blightcloud.nif");
|
||||
models.emplace_back("meshes\\snow.nif");
|
||||
models.emplace_back("meshes\\blizzard.nif");
|
||||
models.emplace_back(Settings::Manager::getString("weatherashcloud", "Models"));
|
||||
models.emplace_back(Settings::Manager::getString("weatherblightcloud", "Models"));
|
||||
models.emplace_back(Settings::Manager::getString("weathersnow", "Models"));
|
||||
models.emplace_back(Settings::Manager::getString("weatherblizzard", "Models"));
|
||||
|
||||
textures.emplace_back("textures/tx_mooncircle_full_s.dds");
|
||||
textures.emplace_back("textures/tx_mooncircle_full_m.dds");
|
||||
|
|
|
@ -93,7 +93,7 @@ namespace MWScript
|
|||
runtime.pop();
|
||||
|
||||
if (count<0)
|
||||
throw std::runtime_error ("second argument for AddItem must be non-negative");
|
||||
count = static_cast<uint16_t>(count);
|
||||
|
||||
// no-op
|
||||
if (count == 0)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
ESM::GlobalScript script;
|
||||
script.load (reader);
|
||||
|
||||
if (script.mTargetRef.hasContentFile())
|
||||
{
|
||||
auto iter = contentFileMap.find(script.mTargetRef.mContentFile);
|
||||
if (iter != contentFileMap.end())
|
||||
script.mTargetRef.mContentFile = iter->second;
|
||||
}
|
||||
|
||||
auto iter = mScripts.find (script.mId);
|
||||
|
||||
if (iter==mScripts.end())
|
||||
|
|
|
@ -73,7 +73,7 @@ namespace MWScript
|
|||
|
||||
void write (ESM::ESMWriter& writer, Loading::Listener& progress) const;
|
||||
|
||||
bool readRecord (ESM::ESMReader& reader, uint32_t type);
|
||||
bool readRecord (ESM::ESMReader& reader, uint32_t type, const std::map<int, int>& contentFileMap);
|
||||
///< Records for variables that do not exist are dropped silently.
|
||||
///
|
||||
/// \return Known type?
|
||||
|
|
|
@ -10,16 +10,6 @@
|
|||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
class SoundManager;
|
||||
}
|
||||
|
||||
namespace MWInput
|
||||
{
|
||||
struct MWInputManager;
|
||||
}
|
||||
|
||||
namespace MWScript
|
||||
{
|
||||
class Locals;
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace MWScript
|
|||
std::vector<MWWorld::Ptr> actors;
|
||||
MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors);
|
||||
for (auto& actor : actors)
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff);
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false);
|
||||
}
|
||||
|
||||
template<class R>
|
||||
|
@ -303,7 +303,7 @@ namespace MWScript
|
|||
}
|
||||
|
||||
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos));
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -439,7 +439,7 @@ namespace MWScript
|
|||
}
|
||||
else
|
||||
{
|
||||
ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true);
|
||||
ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true, true);
|
||||
}
|
||||
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(base,ptr);
|
||||
|
||||
|
@ -726,7 +726,7 @@ namespace MWScript
|
|||
// This approach can be used to create elevators.
|
||||
moveStandingActors(ptr, diff);
|
||||
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff));
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -762,7 +762,7 @@ namespace MWScript
|
|||
// This approach can be used to create elevators.
|
||||
moveStandingActors(ptr, diff);
|
||||
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff));
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ namespace MWSound
|
|||
|
||||
public:
|
||||
SoundManager(const VFS::Manager* vfs, bool useSound);
|
||||
virtual ~SoundManager();
|
||||
~SoundManager() override;
|
||||
|
||||
void processChangedSettings(const Settings::CategorySettingVector& settings) override;
|
||||
|
||||
|
|
|
@ -18,14 +18,14 @@ void MWState::QuickSaveManager::visitSave(const Slot *saveSlot)
|
|||
}
|
||||
}
|
||||
|
||||
bool MWState::QuickSaveManager::isOldestSave(const Slot *compare)
|
||||
bool MWState::QuickSaveManager::isOldestSave(const Slot *compare) const
|
||||
{
|
||||
if(mOldestSlotVisited == nullptr)
|
||||
return true;
|
||||
return (compare->mTimeStamp <= mOldestSlotVisited->mTimeStamp);
|
||||
}
|
||||
|
||||
bool MWState::QuickSaveManager::shouldCreateNewSlot()
|
||||
bool MWState::QuickSaveManager::shouldCreateNewSlot() const
|
||||
{
|
||||
return (mSlotsVisited < mMaxSaves);
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ namespace MWState{
|
|||
unsigned int mSlotsVisited;
|
||||
const Slot *mOldestSlotVisited;
|
||||
private:
|
||||
bool shouldCreateNewSlot();
|
||||
bool isOldestSave(const Slot *compare);
|
||||
bool shouldCreateNewSlot() const;
|
||||
bool isOldestSave(const Slot *compare) const;
|
||||
public:
|
||||
QuickSaveManager(std::string &saveName, unsigned int maxSaves);
|
||||
///< A utility class to manage multiple quicksave slots
|
||||
|
|
|
@ -461,7 +461,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
|
|||
|
||||
case ESM::REC_GSCR:
|
||||
|
||||
MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval);
|
||||
MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval, contentFileMap);
|
||||
break;
|
||||
|
||||
case ESM::REC_GMAP:
|
||||
|
|
|
@ -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)
|
||||
: mStore (store), mReader (reader),
|
||||
mIdCache (Settings::Manager::getInt("pointers cache size", "Cells"), std::pair<std::string, CellStore *> ("", (CellStore*)nullptr)),
|
||||
mIdCacheIndex (0)
|
||||
{}
|
||||
{
|
||||
int cacheSize = std::max(Settings::Manager::getInt("pointers cache size", "Cells"), 0);
|
||||
mIdCache = IdCache(cacheSize, std::pair<std::string, CellStore *> ("", (CellStore*)nullptr));
|
||||
}
|
||||
|
||||
MWWorld::CellStore *MWWorld::Cells::getExterior (int x, int y)
|
||||
{
|
||||
|
@ -259,8 +261,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell,
|
|||
MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name)
|
||||
{
|
||||
// First check the cache
|
||||
for (std::vector<std::pair<std::string, CellStore *> >::iterator iter (mIdCache.begin());
|
||||
iter!=mIdCache.end(); ++iter)
|
||||
for (IdCache::iterator iter (mIdCache.begin()); iter!=mIdCache.end(); ++iter)
|
||||
if (iter->first==name && iter->second)
|
||||
{
|
||||
Ptr ptr = getPtr (name, *iter->second);
|
||||
|
|
|
@ -28,11 +28,12 @@ namespace MWWorld
|
|||
/// \brief Cell container
|
||||
class Cells
|
||||
{
|
||||
typedef std::vector<std::pair<std::string, CellStore *> > IdCache;
|
||||
const MWWorld::ESMStore& mStore;
|
||||
std::vector<ESM::ESMReader>& mReader;
|
||||
mutable std::map<std::string, CellStore> mInteriors;
|
||||
mutable std::map<std::pair<int, int>, CellStore> mExteriors;
|
||||
std::vector<std::pair<std::string, CellStore *> > mIdCache;
|
||||
IdCache mIdCache;
|
||||
std::size_t mIdCacheIndex;
|
||||
|
||||
Cells (const Cells&);
|
||||
|
|
|
@ -76,11 +76,25 @@ namespace
|
|||
MWWorld::ResolutionListener::~ResolutionListener()
|
||||
{
|
||||
if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
for(const auto&& ptr : mStore)
|
||||
ptr.getRefData().setCount(0);
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
Log(Debug::Warning) << "Failed to clear temporary container contents of " << mStore.mPtr.get<ESM::Container>()->mBase->mId << ": " << e.what();
|
||||
}
|
||||
mStore.fillNonRandom(mStore.mPtr.get<ESM::Container>()->mBase->mInventory, "", mStore.mSeed);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -153,7 +153,7 @@ namespace MWWorld
|
|||
|
||||
virtual ~ContainerStore();
|
||||
|
||||
virtual ContainerStore* clone() { return new ContainerStore(*this); }
|
||||
virtual std::unique_ptr<ContainerStore> clone() { return std::make_unique<ContainerStore>(*this); }
|
||||
|
||||
ConstContainerStoreIterator cbegin (int mask = Type_All) const;
|
||||
ConstContainerStoreIterator cend() const;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef GAME_MWWORLD_CUSTOMDATA_H
|
||||
#define GAME_MWWORLD_CUSTOMDATA_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
class CreatureCustomData;
|
||||
|
@ -19,7 +21,7 @@ namespace MWWorld
|
|||
|
||||
virtual ~CustomData() {}
|
||||
|
||||
virtual CustomData *clone() const = 0;
|
||||
virtual std::unique_ptr<CustomData> clone() const = 0;
|
||||
|
||||
// Fast version of dynamic_cast<X&>. Needs to be overridden in the respective class.
|
||||
|
||||
|
@ -38,6 +40,15 @@ namespace MWWorld
|
|||
virtual MWClass::CreatureLevListCustomData& asCreatureLevListCustomData();
|
||||
virtual const MWClass::CreatureLevListCustomData& asCreatureLevListCustomData() const;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct TypedCustomData : CustomData
|
||||
{
|
||||
std::unique_ptr<CustomData> clone() const final
|
||||
{
|
||||
return std::make_unique<T>(*static_cast<const T*>(this));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -123,7 +123,7 @@ namespace MWWorld
|
|||
|
||||
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;
|
||||
///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed)
|
||||
|
|
|
@ -40,8 +40,6 @@ namespace MWWorld
|
|||
void RefData::cleanup()
|
||||
{
|
||||
mBaseNode = nullptr;
|
||||
|
||||
delete mCustomData;
|
||||
mCustomData = nullptr;
|
||||
}
|
||||
|
||||
|
@ -223,21 +221,20 @@ namespace MWWorld
|
|||
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
|
||||
delete mCustomData;
|
||||
mCustomData = data;
|
||||
mCustomData = std::move(value);
|
||||
}
|
||||
|
||||
CustomData *RefData::getCustomData()
|
||||
{
|
||||
return mCustomData;
|
||||
return mCustomData.get();
|
||||
}
|
||||
|
||||
const CustomData *RefData::getCustomData() const
|
||||
{
|
||||
return mCustomData;
|
||||
return mCustomData.get();
|
||||
}
|
||||
|
||||
bool RefData::hasChanged() const
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
#include <components/esm/animationstate.hpp>
|
||||
|
||||
#include "../mwscript/locals.hpp"
|
||||
#include "../mwworld/customdata.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace SceneUtil
|
||||
{
|
||||
|
@ -44,7 +46,7 @@ namespace MWWorld
|
|||
|
||||
ESM::AnimationState mAnimationState;
|
||||
|
||||
CustomData *mCustomData;
|
||||
std::unique_ptr<CustomData> mCustomData;
|
||||
|
||||
void copy (const RefData& refData);
|
||||
|
||||
|
@ -68,6 +70,7 @@ namespace MWWorld
|
|||
/// perform these operations).
|
||||
|
||||
RefData (const RefData& refData);
|
||||
RefData (RefData&& other) noexcept = default;
|
||||
|
||||
~RefData();
|
||||
|
||||
|
@ -76,6 +79,7 @@ namespace MWWorld
|
|||
/// perform this operations).
|
||||
|
||||
RefData& operator= (const RefData& refData);
|
||||
RefData& operator= (RefData&& other) noexcept = default;
|
||||
|
||||
/// Return base node (can be a null pointer).
|
||||
SceneUtil::PositionAttitudeTransform* getBaseNode();
|
||||
|
@ -117,7 +121,7 @@ namespace MWWorld
|
|||
void setPosition (const ESM::Position& pos);
|
||||
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
|
||||
/// transferred to this.
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ namespace Loading
|
|||
namespace DetourNavigator
|
||||
{
|
||||
struct Navigator;
|
||||
class Water;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
|
|
|
@ -862,19 +862,6 @@ namespace MWWorld
|
|||
if (reference == getPlayerPtr())
|
||||
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();
|
||||
|
||||
if (reference.getCellRef().getRefNum().hasContentFile())
|
||||
|
@ -1251,7 +1238,7 @@ namespace MWWorld
|
|||
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;
|
||||
positionToIndex(x, y, cellX, cellY);
|
||||
|
@ -1266,21 +1253,14 @@ namespace MWWorld
|
|||
return moveObject(ptr, cell, x, y, z, movePhysics);
|
||||
}
|
||||
|
||||
MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive)
|
||||
{
|
||||
return moveObjectImp(ptr, x, y, z, true, moveToActive);
|
||||
}
|
||||
|
||||
MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec)
|
||||
MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive)
|
||||
{
|
||||
auto* actor = mPhysics->getActor(ptr);
|
||||
if (actor)
|
||||
{
|
||||
actor->adjustPosition(vec);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -1546,7 +1526,7 @@ namespace MWWorld
|
|||
auto* physactor = mPhysics->getActor(actor);
|
||||
assert(physactor);
|
||||
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);
|
||||
assert(physactor);
|
||||
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;
|
||||
}
|
||||
|
||||
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 weaponHalfExtents = mPhysics->getHalfExtents(actor);
|
||||
osg::Vec3f targetPos = target.getRefData().getPosition().asVec3();
|
||||
osg::Vec3f targetHalfExtents = mPhysics->getHalfExtents(target);
|
||||
weaponPos.z() += weaponHalfExtents.z() * 2 * Constants::TorsoHeight;
|
||||
targetPos.z() += targetHalfExtents.z();
|
||||
float heightRatio = isRangedCombat ? 2.f * Constants::TorsoHeight : 1.f;
|
||||
weaponPos.z() += mPhysics->getHalfExtents(actor).z() * heightRatio;
|
||||
osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target);
|
||||
return (targetPos - weaponPos);
|
||||
}
|
||||
|
||||
|
|
|
@ -60,8 +60,6 @@ namespace ToUTF8
|
|||
class Utf8Encoder;
|
||||
}
|
||||
|
||||
struct ContentLoader;
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class Object;
|
||||
|
@ -138,9 +136,6 @@ namespace MWWorld
|
|||
|
||||
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);
|
||||
|
||||
void updateSoundListener();
|
||||
|
@ -376,13 +371,13 @@ namespace MWWorld
|
|||
|
||||
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
|
||||
|
||||
MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override;
|
||||
///< @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
|
||||
|
||||
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.
|
||||
/// @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.
|
||||
float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) override;
|
||||
|
|
|
@ -13,6 +13,7 @@ if (GTEST_FOUND AND GMOCK_FOUND)
|
|||
mwdialogue/test_keywordsearch.cpp
|
||||
|
||||
esm/test_fixed_string.cpp
|
||||
esm/variant.cpp
|
||||
|
||||
misc/test_stringops.cpp
|
||||
misc/test_endianness.cpp
|
||||
|
|
513
apps/openmw_test_suite/esm/variant.cpp
Normal file
513
apps/openmw_test_suite/esm/variant.cpp
Normal 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}
|
||||
));
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
#include "methodselectionpage.hpp"
|
||||
#include "mainwizard.hpp"
|
||||
|
||||
#include <QUrl>
|
||||
#include <QDesktopServices>
|
||||
|
||||
Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) :
|
||||
QWizardPage(parent)
|
||||
{
|
||||
|
@ -11,9 +14,12 @@ Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) :
|
|||
#ifndef OPENMW_USE_UNSHIELD
|
||||
retailDiscRadioButton->setEnabled(false);
|
||||
existingLocationRadioButton->setChecked(true);
|
||||
buyLinkButton->released();
|
||||
#endif
|
||||
|
||||
registerField(QLatin1String("installation.retailDisc"), retailDiscRadioButton);
|
||||
|
||||
connect(buyLinkButton, SIGNAL(released()), this, SLOT(handleBuyButton()));
|
||||
}
|
||||
|
||||
int Wizard::MethodSelectionPage::nextId() const
|
||||
|
@ -24,3 +30,8 @@ int Wizard::MethodSelectionPage::nextId() const
|
|||
return MainWizard::Page_ExistingInstallation;
|
||||
}
|
||||
}
|
||||
|
||||
void Wizard::MethodSelectionPage::handleBuyButton()
|
||||
{
|
||||
QDesktopServices::openUrl(QUrl("https://openmw.org/faq/#do_i_need_morrowind"));
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@ namespace Wizard
|
|||
|
||||
int nextId() const override;
|
||||
|
||||
private slots:
|
||||
void handleBuyButton();
|
||||
|
||||
private:
|
||||
MainWizard *mWizard;
|
||||
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
# Get the Google C++ Mocking Framework.
|
||||
# (This file is almost an copy of the original FindGTest.cmake file,
|
||||
# altered to download and compile GMock and GTest if not found
|
||||
# in GMOCK_ROOT or GTEST_ROOT respectively,
|
||||
# (This file is almost an copy of the original FindGTest.cmake file for GMock,
|
||||
# feel free to use it as it is or modify it for your own needs.)
|
||||
#
|
||||
# Defines the following variables:
|
||||
#
|
||||
# GMOCK_FOUND - Found or got the Google Mocking framework
|
||||
# GTEST_FOUND - Found or got the Google Testing framework
|
||||
# GMOCK_INCLUDE_DIRS - GMock include directory
|
||||
# GTEST_INCLUDE_DIRS - GTest include direcotry
|
||||
#
|
||||
# Also defines the library variables below as normal variables
|
||||
#
|
||||
|
@ -17,14 +13,8 @@
|
|||
# GMOCK_LIBRARIES - libgmock
|
||||
# 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:
|
||||
#
|
||||
# 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_VER - The version of the gmock sources to be downloaded
|
||||
#
|
||||
|
@ -101,48 +91,6 @@
|
|||
#
|
||||
# * 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)
|
||||
find_library(${_name}
|
||||
|
@ -155,38 +103,20 @@ function(_gmock_find_library _name)
|
|||
mark_as_advanced(${_name})
|
||||
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)
|
||||
set(GMOCK_MSVC_SEARCH MD)
|
||||
endif()
|
||||
|
||||
set(_gmock_libpath_suffixes lib)
|
||||
set(_gtest_libpath_suffixes lib)
|
||||
if(MSVC)
|
||||
if(GMOCK_MSVC_SEARCH STREQUAL "MD")
|
||||
list(APPEND _gmock_libpath_suffixes
|
||||
msvc/gmock-md/Debug
|
||||
msvc/gmock-md/Release)
|
||||
list(APPEND _gtest_libpath_suffixes
|
||||
msvc/gtest-md/Debug
|
||||
msvc/gtest-md/Release)
|
||||
elseif(GMOCK_MSVC_SEARCH STREQUAL "MT")
|
||||
list(APPEND _gmock_libpath_suffixes
|
||||
msvc/gmock/Debug
|
||||
msvc/gmock/Release)
|
||||
list(APPEND _gtest_libpath_suffixes
|
||||
msvc/gtest/Debug
|
||||
msvc/gtest/Release)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
@ -197,13 +127,6 @@ find_path(GMOCK_INCLUDE_DIR gmock/gmock.h
|
|||
)
|
||||
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")
|
||||
# The provided /MD project files for Google Mock add -md suffixes to the
|
||||
# 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_MAIN_LIBRARY gmock_main-md gmock_main)
|
||||
_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()
|
||||
_gmock_find_library(GMOCK_LIBRARY gmock)
|
||||
_gmock_find_library(GMOCK_LIBRARY_DEBUG gmockd)
|
||||
_gmock_find_library(GMOCK_MAIN_LIBRARY gmock_main)
|
||||
_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()
|
||||
|
||||
if(NOT TARGET GMock::GMock)
|
||||
|
@ -244,224 +151,17 @@ if(NOT TARGET GMock::Main)
|
|||
endif()
|
||||
|
||||
set(GMOCK_LIBRARY_EXISTS OFF)
|
||||
set(GTEST_LIBRARY_EXISTS OFF)
|
||||
|
||||
if(EXISTS "${GMOCK_LIBRARY}" OR EXISTS "${GMOCK_LIBRARY_DEBUG}" AND GMOCK_INCLUDE_DIR)
|
||||
set(GMOCK_LIBRARY_EXISTS ON)
|
||||
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)
|
||||
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)
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
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
|
||||
INTERFACE_LINK_LIBRARIES "Threads::Threads"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
|
||||
|
@ -477,13 +177,6 @@ if(GMOCK_INCLUDE_DIR)
|
|||
# so just specify it on the link interface.
|
||||
set_property(TARGET GMock::GMock APPEND PROPERTY
|
||||
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()
|
||||
|
||||
|
@ -492,17 +185,6 @@ set_target_properties(GMock::Main PROPERTIES
|
|||
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
|
||||
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)
|
||||
set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR})
|
||||
set(GMOCK_LIBRARIES GMock::GMock)
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
|
||||
using namespace Bsa;
|
||||
|
||||
|
@ -37,6 +38,31 @@ void BSAFile::fail(const std::string &msg)
|
|||
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
|
||||
void BSAFile::readHeader()
|
||||
{
|
||||
|
@ -113,14 +139,17 @@ void BSAFile::readHeader()
|
|||
|
||||
// Read the offset info into a temporary buffer
|
||||
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
|
||||
mStringBuf.resize(dirsize-12*filenum);
|
||||
input.read(&mStringBuf[0], mStringBuf.size());
|
||||
input.read(mStringBuf.data(), mStringBuf.size());
|
||||
|
||||
// Check our position
|
||||
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
|
||||
// relative to this. 12 header bytes + directory + hash table
|
||||
|
@ -129,23 +158,72 @@ void BSAFile::readHeader()
|
|||
|
||||
// Set up the the FileStruct table
|
||||
mFiles.resize(filenum);
|
||||
size_t endOfNameBuffer = 0;
|
||||
for(size_t i=0;i<filenum;i++)
|
||||
{
|
||||
FileStruct &fs = mFiles[i];
|
||||
fs.fileSize = offsets[i*2];
|
||||
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)
|
||||
fail("Archive contains offsets outside itself");
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// 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
|
||||
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)
|
||||
{
|
||||
mFilename = file;
|
||||
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)
|
||||
|
@ -181,3 +274,56 @@ Files::IStreamPtr BSAFile::getFile(const FileStruct *file)
|
|||
{
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -43,20 +43,42 @@ namespace Bsa
|
|||
class BSAFile
|
||||
{
|
||||
public:
|
||||
|
||||
#pragma pack(push)
|
||||
#pragma pack(1)
|
||||
struct Hash
|
||||
{
|
||||
uint32_t low, high;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
/// Represents one file entry in the archive
|
||||
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
|
||||
// beginning of the file, not the offset into the data buffer
|
||||
// (which is what is stored in the archive.)
|
||||
uint32_t fileSize, offset;
|
||||
Hash hash;
|
||||
|
||||
// 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;
|
||||
|
||||
protected:
|
||||
bool mHasChanged = false;
|
||||
|
||||
/// Table of files in this archive
|
||||
FileList mFiles;
|
||||
|
||||
|
@ -72,7 +94,7 @@ protected:
|
|||
/// Case insensitive string comparison
|
||||
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); }
|
||||
};
|
||||
|
||||
|
@ -80,7 +102,7 @@ protected:
|
|||
the files[] vector above. The iltstr ensures that file name
|
||||
checks are case insensitive.
|
||||
*/
|
||||
typedef std::map<const char*, int, iltstr> Lookup;
|
||||
typedef std::map<std::string, int, iltstr> Lookup;
|
||||
Lookup mLookup;
|
||||
|
||||
/// Error handling
|
||||
|
@ -88,9 +110,7 @@ protected:
|
|||
|
||||
/// Read header information from the input source
|
||||
virtual void readHeader();
|
||||
|
||||
/// Read header information from the input source
|
||||
|
||||
virtual void writeHeader();
|
||||
|
||||
/// Get the index of a given file name, or -1 if not found
|
||||
/// @note Thread safe.
|
||||
|
@ -106,11 +126,16 @@ public:
|
|||
: mIsLoaded(false)
|
||||
{ }
|
||||
|
||||
virtual ~BSAFile() = default;
|
||||
virtual ~BSAFile()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
/// Open an archive file.
|
||||
void open(const std::string &file);
|
||||
|
||||
void close();
|
||||
|
||||
/* -----------------------------------
|
||||
* Archive file routines
|
||||
* -----------------------------------
|
||||
|
@ -131,6 +156,8 @@ public:
|
|||
*/
|
||||
virtual Files::IStreamPtr getFile(const FileStruct* file);
|
||||
|
||||
virtual void addFile(const std::string& filename, std::istream& file);
|
||||
|
||||
/// Get a list of all files
|
||||
/// @note Thread safe.
|
||||
const FileList &getList() const
|
||||
|
|
|
@ -226,7 +226,6 @@ void CompressedBSAFile::readHeader()
|
|||
FileStruct fileStruct{};
|
||||
fileStruct.fileSize = file.getSizeWithoutCompressionFlag();
|
||||
fileStruct.offset = file.offset;
|
||||
fileStruct.name = nullptr;
|
||||
mFiles.push_back(fileStruct);
|
||||
|
||||
fullPaths.push_back(folder);
|
||||
|
@ -249,7 +248,7 @@ void CompressedBSAFile::readHeader()
|
|||
}
|
||||
|
||||
//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);
|
||||
|
||||
|
@ -276,7 +275,7 @@ void CompressedBSAFile::readHeader()
|
|||
fullPaths.at(fileIndex).c_str() + stringLength + 1u,
|
||||
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;
|
||||
mStringBuffOffset += stringLength + 1u;
|
||||
|
@ -320,13 +319,19 @@ CompressedBSAFile::FileRecord CompressedBSAFile::getFileRecord(const std::string
|
|||
|
||||
Files::IStreamPtr CompressedBSAFile::getFile(const FileStruct* file)
|
||||
{
|
||||
FileRecord fileRec = getFileRecord(file->name);
|
||||
FileRecord fileRec = getFileRecord(file->name());
|
||||
if (!fileRec.isValid()) {
|
||||
fail("File not found: " + std::string(file->name));
|
||||
fail("File not found: " + std::string(file->name()));
|
||||
}
|
||||
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)
|
||||
{
|
||||
FileRecord fileRec = getFileRecord(file);
|
||||
|
@ -376,8 +381,10 @@ Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord)
|
|||
LZ4F_decompressionContext_t context = nullptr;
|
||||
LZ4F_createDecompressionContext(&context, LZ4F_VERSION);
|
||||
LZ4F_decompressOptions_t options = {};
|
||||
LZ4F_decompress(context, memoryStreamPtr->getRawData(), &uncompressedSize, buffer.get(), &size, &options);
|
||||
LZ4F_errorCode_t errorCode = LZ4F_freeDecompressionContext(context);
|
||||
LZ4F_errorCode_t errorCode = LZ4F_decompress(context, memoryStreamPtr->getRawData(), &uncompressedSize, buffer.get(), &size, &options);
|
||||
if (LZ4F_isError(errorCode))
|
||||
fail("LZ4 decompression error (file " + mFilename + "): " + LZ4F_getErrorName(errorCode));
|
||||
errorCode = LZ4F_freeDecompressionContext(context);
|
||||
if (LZ4F_isError(errorCode))
|
||||
fail("LZ4 decompression error (file " + mFilename + "): " + LZ4F_getErrorName(errorCode));
|
||||
}
|
||||
|
@ -430,10 +437,10 @@ void CompressedBSAFile::convertCompressedSizesToUncompressed()
|
|||
{
|
||||
for (auto & mFile : mFiles)
|
||||
{
|
||||
const FileRecord& fileRecord = getFileRecord(mFile.name);
|
||||
const FileRecord& fileRecord = getFileRecord(mFile.name());
|
||||
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))
|
||||
|
|
|
@ -94,7 +94,7 @@ namespace Bsa
|
|||
|
||||
Files::IStreamPtr getFile(const char* filePath) override;
|
||||
Files::IStreamPtr getFile(const FileStruct* fileStruct) override;
|
||||
|
||||
void addFile(const std::string& filename, std::istream& file) override;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -146,11 +146,10 @@ static void gdb_info(pid_t pid)
|
|||
/*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
strcpy(respfile, "/tmp/gdb-respfile-XXXXXX");
|
||||
// coverity[secure_temp]
|
||||
if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != nullptr)
|
||||
{
|
||||
fprintf(f, "attach %d\n"
|
||||
|
|
|
@ -42,6 +42,7 @@ void ESM::Header::load (ESMReader &esm)
|
|||
MasterData m;
|
||||
m.name = esm.getHString();
|
||||
m.size = esm.getHNLong ("DATA");
|
||||
m.index = -1;
|
||||
mMaster.push_back (m);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,106 +14,53 @@ namespace
|
|||
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 STTV = ESM::FourCC<'S','T','T','V'>::value;
|
||||
}
|
||||
|
||||
ESM::Variant::Variant() : mType (VT_None), mData (nullptr) {}
|
||||
|
||||
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)
|
||||
template <typename T, bool orDefault = false>
|
||||
struct GetValue
|
||||
{
|
||||
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;
|
||||
mData = newData;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ESM::Variant& ESM::Variant::operator= (Variant&& variant)
|
||||
{
|
||||
if (&variant!=this)
|
||||
template <typename V>
|
||||
T operator()(const V&) const
|
||||
{
|
||||
delete mData;
|
||||
|
||||
mType = variant.mType;
|
||||
mData = variant.mData;
|
||||
|
||||
variant.mData = nullptr;
|
||||
if constexpr (orDefault)
|
||||
return T {};
|
||||
else
|
||||
throw std::runtime_error("cannot convert variant");
|
||||
}
|
||||
};
|
||||
|
||||
return *this;
|
||||
}
|
||||
template <typename T>
|
||||
struct SetValue
|
||||
{
|
||||
T mValue;
|
||||
|
||||
ESM::Variant::Variant (const Variant& variant)
|
||||
: mType (variant.mType), mData (variant.mData ? variant.mData->clone() : nullptr)
|
||||
{}
|
||||
explicit SetValue(T value) : mValue(value) {}
|
||||
|
||||
ESM::Variant::Variant(Variant&& variant)
|
||||
: mType (variant.mType), mData (variant.mData)
|
||||
{
|
||||
variant.mData = nullptr;
|
||||
}
|
||||
void operator()(int& value) const { value = static_cast<int>(mValue); }
|
||||
|
||||
ESM::VarType ESM::Variant::getType() const
|
||||
{
|
||||
return mType;
|
||||
void operator()(float& value) const { value = static_cast<float>(mValue); }
|
||||
|
||||
template <typename V>
|
||||
void operator()(V&) const { throw std::runtime_error("cannot convert variant"); }
|
||||
};
|
||||
}
|
||||
|
||||
std::string ESM::Variant::getString() const
|
||||
{
|
||||
if (!mData)
|
||||
throw std::runtime_error ("can not convert empty variant to string");
|
||||
|
||||
return mData->getString();
|
||||
return std::get<std::string>(mData);
|
||||
}
|
||||
|
||||
int ESM::Variant::getInteger() const
|
||||
{
|
||||
if (!mData)
|
||||
throw std::runtime_error ("can not convert empty variant to integer");
|
||||
|
||||
return mData->getInteger();
|
||||
return std::visit(GetValue<int>{}, mData);
|
||||
}
|
||||
|
||||
float ESM::Variant::getFloat() const
|
||||
{
|
||||
if (!mData)
|
||||
throw std::runtime_error ("can not convert empty variant to float");
|
||||
|
||||
return mData->getFloat();
|
||||
return std::visit(GetValue<float>{}, mData);
|
||||
}
|
||||
|
||||
void ESM::Variant::read (ESMReader& esm, Format format)
|
||||
|
@ -202,9 +149,7 @@ void ESM::Variant::read (ESMReader& esm, Format format)
|
|||
|
||||
setType (type);
|
||||
|
||||
// data
|
||||
if (mData)
|
||||
mData->read (esm, format, mType);
|
||||
std::visit(ReadESMVariantValue {esm, format, mType}, mData);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
else
|
||||
mData->write (esm, format, mType);
|
||||
std::visit(WriteESMVariantValue {esm, format, mType}, mData);
|
||||
}
|
||||
|
||||
void ESM::Variant::write (std::ostream& stream) const
|
||||
|
@ -246,27 +191,27 @@ void ESM::Variant::write (std::ostream& stream) const
|
|||
|
||||
case VT_Short:
|
||||
|
||||
stream << "variant short: " << mData->getInteger();
|
||||
stream << "variant short: " << std::get<int>(mData);
|
||||
break;
|
||||
|
||||
case VT_Int:
|
||||
|
||||
stream << "variant int: " << mData->getInteger();
|
||||
stream << "variant int: " << std::get<int>(mData);
|
||||
break;
|
||||
|
||||
case VT_Long:
|
||||
|
||||
stream << "variant long: " << mData->getInteger();
|
||||
stream << "variant long: " << std::get<int>(mData);
|
||||
break;
|
||||
|
||||
case VT_Float:
|
||||
|
||||
stream << "variant float: " << mData->getFloat();
|
||||
stream << "variant float: " << std::get<float>(mData);
|
||||
break;
|
||||
|
||||
case VT_String:
|
||||
|
||||
stream << "variant string: \"" << mData->getString() << "\"";
|
||||
stream << "variant string: \"" << std::get<std::string>(mData) << "\"";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -275,74 +220,50 @@ void ESM::Variant::setType (VarType type)
|
|||
{
|
||||
if (type!=mType)
|
||||
{
|
||||
VariantDataBase *newData = nullptr;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case VT_Unknown:
|
||||
case VT_None:
|
||||
|
||||
break; // no data
|
||||
mData = std::monostate {};
|
||||
break;
|
||||
|
||||
case VT_Short:
|
||||
case VT_Int:
|
||||
case VT_Long:
|
||||
|
||||
newData = new VariantIntegerData (mData);
|
||||
mData = std::visit(GetValue<int, true>{}, mData);
|
||||
break;
|
||||
|
||||
case VT_Float:
|
||||
|
||||
newData = new VariantFloatData (mData);
|
||||
mData = std::visit(GetValue<float, true>{}, mData);
|
||||
break;
|
||||
|
||||
case VT_String:
|
||||
|
||||
newData = new VariantStringData (mData);
|
||||
mData = std::string {};
|
||||
break;
|
||||
}
|
||||
|
||||
delete mData;
|
||||
mData = newData;
|
||||
mType = type;
|
||||
}
|
||||
}
|
||||
|
||||
void ESM::Variant::setString (const std::string& value)
|
||||
{
|
||||
if (!mData)
|
||||
throw std::runtime_error ("can not assign string to empty variant");
|
||||
std::get<std::string>(mData) = value;
|
||||
}
|
||||
|
||||
mData->setString (value);
|
||||
void ESM::Variant::setString (std::string&& value)
|
||||
{
|
||||
std::get<std::string>(mData) = std::move(value);
|
||||
}
|
||||
|
||||
void ESM::Variant::setInteger (int value)
|
||||
{
|
||||
if (!mData)
|
||||
throw std::runtime_error ("can not assign integer to empty variant");
|
||||
|
||||
mData->setInteger (value);
|
||||
std::visit(SetValue(value), mData);
|
||||
}
|
||||
|
||||
void ESM::Variant::setFloat (float value)
|
||||
{
|
||||
if (!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::visit(SetValue(value), mData);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <string>
|
||||
#include <iosfwd>
|
||||
#include <variant>
|
||||
#include <tuple>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
@ -20,12 +22,10 @@ namespace ESM
|
|||
VT_String
|
||||
};
|
||||
|
||||
class VariantDataBase;
|
||||
|
||||
class Variant
|
||||
{
|
||||
VarType mType;
|
||||
VariantDataBase *mData;
|
||||
std::variant<std::monostate, int, float, std::string> mData;
|
||||
|
||||
public:
|
||||
|
||||
|
@ -37,21 +37,17 @@ namespace ESM
|
|||
Format_Local // local script variables in save game files
|
||||
};
|
||||
|
||||
Variant();
|
||||
Variant() : mType (VT_None), mData (std::monostate{}) {}
|
||||
|
||||
Variant (const std::string& value);
|
||||
Variant (int value);
|
||||
Variant (float value);
|
||||
explicit Variant(const std::string& value) : mType(VT_String), mData(value) {}
|
||||
|
||||
~Variant();
|
||||
explicit Variant(std::string&& value) : mType(VT_String), mData(std::move(value)) {}
|
||||
|
||||
Variant& operator= (const Variant& variant);
|
||||
Variant& operator= (Variant && variant);
|
||||
explicit Variant(int value) : mType(VT_Long), mData(value) {}
|
||||
|
||||
Variant (const Variant& variant);
|
||||
Variant (Variant&& variant);
|
||||
explicit Variant(float value) : mType(VT_Float), mData(value) {}
|
||||
|
||||
VarType getType() const;
|
||||
VarType getType() const { return mType; }
|
||||
|
||||
std::string getString() const;
|
||||
///< 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);
|
||||
///< 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);
|
||||
///< Will throw an exception, if type is not compatible with integer.
|
||||
|
||||
void setFloat (float value);
|
||||
///< 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);
|
||||
|
||||
bool operator== (const Variant& left, const Variant& right);
|
||||
bool operator!= (const Variant& left, const Variant& right);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -5,71 +5,7 @@
|
|||
#include "esmreader.hpp"
|
||||
#include "esmwriter.hpp"
|
||||
|
||||
ESM::VariantDataBase::~VariantDataBase() {}
|
||||
|
||||
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)
|
||||
void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, std::string& out)
|
||||
{
|
||||
if (type!=VT_String)
|
||||
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");
|
||||
|
||||
// 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)
|
||||
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)
|
||||
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
|
||||
esm.writeHNString ("STRV", mValue);
|
||||
esm.writeHNString("STRV", in);
|
||||
}
|
||||
|
||||
bool ESM::VariantStringData::isEqual (const VariantDataBase& value) const
|
||||
{
|
||||
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)
|
||||
void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, int& out)
|
||||
{
|
||||
if (type!=VT_Short && type!=VT_Long && type!=VT_Int)
|
||||
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 (value!=value)
|
||||
mValue = 0; // nan
|
||||
out = 0; // nan
|
||||
else
|
||||
mValue = static_cast<short> (value);
|
||||
out = static_cast<short> (value);
|
||||
}
|
||||
else if (type==VT_Long)
|
||||
mValue = static_cast<int> (value);
|
||||
out = static_cast<int> (value);
|
||||
else
|
||||
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.getHT (mValue);
|
||||
esm.getHT(out);
|
||||
}
|
||||
else if (format==Variant::Format_Local)
|
||||
{
|
||||
|
@ -181,18 +82,18 @@ void ESM::VariantIntegerData::read (ESMReader& esm, Variant::Format format, VarT
|
|||
{
|
||||
short value;
|
||||
esm.getHT(value);
|
||||
mValue = value;
|
||||
out = value;
|
||||
}
|
||||
else if (type==VT_Int)
|
||||
{
|
||||
esm.getHT(mValue);
|
||||
esm.getHT(out);
|
||||
}
|
||||
else
|
||||
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)
|
||||
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)
|
||||
{
|
||||
float value = static_cast<float>(mValue);
|
||||
float value = static_cast<float>(in);
|
||||
esm.writeHNString ("FNAM", type==VT_Short ? "s" : "l");
|
||||
esm.writeHNT ("FLTV", value);
|
||||
}
|
||||
|
@ -219,72 +120,35 @@ void ESM::VariantIntegerData::write (ESMWriter& esm, Variant::Format format, Var
|
|||
throw std::runtime_error (stream.str());
|
||||
}
|
||||
|
||||
esm.writeHNT ("INTV", mValue);
|
||||
esm.writeHNT("INTV", in);
|
||||
}
|
||||
else if (format==Variant::Format_Local)
|
||||
{
|
||||
if (type==VT_Short)
|
||||
esm.writeHNT ("STTV", (short)mValue);
|
||||
esm.writeHNT("STTV", static_cast<short>(in));
|
||||
else if (type == VT_Int)
|
||||
esm.writeHNT ("INTV", mValue);
|
||||
esm.writeHNT("INTV", in);
|
||||
else
|
||||
throw std::runtime_error("unsupported local variable integer type");
|
||||
}
|
||||
}
|
||||
|
||||
bool ESM::VariantIntegerData::isEqual (const VariantDataBase& value) const
|
||||
{
|
||||
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)
|
||||
void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, float& out)
|
||||
{
|
||||
if (type!=VT_Float)
|
||||
throw std::logic_error ("not a float type");
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
esm.writeHNT ("FLTV", mValue);
|
||||
esm.writeHNT("FLTV", in);
|
||||
}
|
||||
}
|
||||
|
||||
bool ESM::VariantFloatData::isEqual (const VariantDataBase& value) const
|
||||
{
|
||||
return dynamic_cast<const VariantFloatData&> (value).mValue==mValue;
|
||||
}
|
||||
|
|
|
@ -2,177 +2,58 @@
|
|||
#define OPENMW_ESM_VARIANTIMP_H
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include "variant.hpp"
|
||||
|
||||
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;
|
||||
|
||||
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.
|
||||
void operator()(std::monostate) const {}
|
||||
|
||||
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);
|
||||
///< Calling the constructor with an incompatible data type will result in a silent
|
||||
/// default initialisation.
|
||||
void operator()(std::monostate) const {}
|
||||
|
||||
VariantDataBase *clone() const override;
|
||||
|
||||
std::string getString (bool default_ = false) const override;
|
||||
///< 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.
|
||||
|
||||
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
|
||||
template <typename T>
|
||||
void operator()(const T& value) const
|
||||
{
|
||||
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.
|
||||
writeESMVariantValue(mWriter.get(), mFormat, mType, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -569,6 +569,7 @@ namespace Gui
|
|||
resolution = std::min(960, std::max(48, resolution));
|
||||
|
||||
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
||||
if (uiScale > 0.f)
|
||||
resolution *= uiScale;
|
||||
|
||||
MyGUI::xml::ElementPtr resolutionNode = resourceNode->createChild("Property");
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace Misc
|
|||
explicit FrameRateLimiter(std::chrono::duration<Rep, Ratio> maxFrameDuration,
|
||||
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now())
|
||||
: mMaxFrameDuration(std::chrono::duration_cast<std::chrono::steady_clock::duration>(maxFrameDuration))
|
||||
, mLastFrameDuration(0)
|
||||
, mLastMeasurement(now)
|
||||
{}
|
||||
|
||||
|
|
|
@ -24,11 +24,6 @@ namespace osg
|
|||
class Material;
|
||||
}
|
||||
|
||||
namespace osgParticle
|
||||
{
|
||||
class Emitter;
|
||||
}
|
||||
|
||||
namespace NifOsg
|
||||
{
|
||||
|
||||
|
|
|
@ -227,6 +227,7 @@ namespace Resource
|
|||
, mAutoUseSpecularMaps(false)
|
||||
, mApplyLightingToEnvMaps(false)
|
||||
, mLightingMethod(SceneUtil::LightingMethod::FFP)
|
||||
, mConvertAlphaTestToAlphaToCoverage(false)
|
||||
, mInstanceCache(new MultiObjectCache)
|
||||
, mSharedStateManager(new SharedStateManager)
|
||||
, mImageManager(imageManager)
|
||||
|
|
|
@ -375,6 +375,7 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr<osgViewer::Viewer> v
|
|||
pack_evt.y = mMouseY = evt.motion.y;
|
||||
pack_evt.xrel = evt.motion.xrel;
|
||||
pack_evt.yrel = evt.motion.yrel;
|
||||
pack_evt.type = SDL_MOUSEMOTION;
|
||||
if (mFirstMouseMove)
|
||||
{
|
||||
// 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);
|
||||
pack_evt.z = mMouseZ;
|
||||
pack_evt.type = SDL_MOUSEWHEEL;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -50,6 +50,7 @@ namespace Shader
|
|||
, mAutoUseNormalMaps(false)
|
||||
, mAutoUseSpecularMaps(false)
|
||||
, mApplyLightingToEnvMaps(false)
|
||||
, mConvertAlphaTestToAlphaToCoverage(false)
|
||||
, mTranslucentFramebuffer(false)
|
||||
, mShaderManager(shaderManager)
|
||||
, mImageManager(imageManager)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
std::string ent = it->mInfo->name;
|
||||
std::string ent = it->mInfo->name();
|
||||
std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function);
|
||||
|
||||
out[ent] = &*it;
|
||||
|
@ -43,7 +43,7 @@ bool BsaArchive::contains(const std::string& file, char (*normalize_function)(ch
|
|||
{
|
||||
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);
|
||||
if(file == ent)
|
||||
return true;
|
||||
|
|
19
docs/source/reference/modding/custom-models/index.rst
Normal file
19
docs/source/reference/modding/custom-models/index.rst
Normal 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
|
|
@ -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
|
||||
--------
|
||||
|
||||
* Blender’s +Z axis is up axis in OpenMW
|
||||
* Blender’s +Y axis is front axis in OpenMW
|
||||
* Blender’s 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 armature’s hierarchy, where both the deformation and control bones fall under it.
|
||||
* Not all bones need to be exported. By disabing the bone’s “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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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.
|
||||
Starting with OpenMW version 0.38 we can utilize the OSG native model format.
|
||||
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.
|
||||
This article explains how to export a model from Blender to OpenMW using the OSG model format. It supports only basic, static 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>`_.
|
||||
|
||||
Prerequisites
|
||||
#############
|
||||
|
@ -104,11 +96,4 @@ Using shaders/normal maps
|
|||
|
||||
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
Loading…
Reference in a new issue