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

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

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

View file

@ -30,6 +30,29 @@ stages:
paths:
- 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"

View file

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

View file

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

View file

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

View file

@ -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 \

View file

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

View file

@ -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
)

View file

@ -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");
@ -259,6 +300,33 @@ void Launcher::AdvancedPage::saveSettings()
static std::array<std::string, 3> lightingMethodMap = {"legacy", "shaders compatibility", "shaders"};
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
{

View file

@ -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 + ")");

View file

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

View file

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

View file

@ -56,7 +56,9 @@ void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData
CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns)
: 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)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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()));

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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");

View file

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

View file

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

View file

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

View file

@ -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();

View file

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

View file

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

View file

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

View file

@ -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();

View file

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

View file

@ -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");

View file

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

View file

@ -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())
{

View file

@ -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");

View file

@ -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)

View file

@ -235,13 +235,20 @@ namespace MWScript
}
}
bool GlobalScripts::readRecord (ESM::ESMReader& reader, uint32_t type)
bool GlobalScripts::readRecord (ESM::ESMReader& reader, uint32_t type, const std::map<int, int>& contentFileMap)
{
if (type==ESM::REC_GSCR)
{
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())

View file

@ -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?

View file

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

View file

@ -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>
@ -290,11 +290,11 @@ namespace MWScript
float terrainHeight = -std::numeric_limits<float>::max();
if (ptr.getCell()->isExterior())
terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(curPos);
if (pos < terrainHeight)
pos = terrainHeight;
}
newPos[2] = pos;
}
else
@ -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));
}
};

View file

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

View file

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

View file

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

View file

@ -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:

View file

@ -132,9 +132,11 @@ void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, CellStore& cell) const
MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& reader)
: 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);

View file

@ -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&);

View file

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

View file

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

View file

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

View file

@ -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)

View file

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

View file

@ -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.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,9 @@
#include "methodselectionpage.hpp"
#include "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"));
}

View file

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

View file

@ -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)

View file

@ -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;
readHeader();
if(boost::filesystem::exists(file))
readHeader();
else
{
{ boost::filesystem::fstream(mFilename, std::ios::binary | std::ios::out); }
writeHeader();
}
}
/// Close the archive, write the updated headers to the file
void Bsa::BSAFile::close()
{
if (!mHasChanged)
return;
writeHeader();
}
Files::IStreamPtr BSAFile::getFile(const char *file)
@ -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();
}

View file

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

View file

@ -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))

View file

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

View file

@ -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"

View file

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

View file

@ -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;
}
template <typename V>
T operator()(const V&) const
{
if constexpr (orDefault)
return T {};
else
throw std::runtime_error("cannot convert variant");
}
};
return *this;
}
ESM::Variant& ESM::Variant::operator= (Variant&& variant)
{
if (&variant!=this)
template <typename T>
struct SetValue
{
delete mData;
T mValue;
mType = variant.mType;
mData = variant.mData;
explicit SetValue(T value) : mValue(value) {}
variant.mData = nullptr;
}
void operator()(int& value) const { value = static_cast<int>(mValue); }
return *this;
}
void operator()(float& value) const { value = static_cast<float>(mValue); }
ESM::Variant::Variant (const Variant& variant)
: mType (variant.mType), mData (variant.mData ? variant.mData->clone() : nullptr)
{}
ESM::Variant::Variant(Variant&& variant)
: mType (variant.mType), mData (variant.mData)
{
variant.mData = nullptr;
}
ESM::VarType ESM::Variant::getType() const
{
return mType;
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);
}

View file

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

View file

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

View file

@ -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
{
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.
template <typename T>
void operator()(const T& value) const
{
writeESMVariantValue(mWriter.get(), mFormat, mType, value);
}
};
}

View file

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

View file

@ -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)
{}

View file

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

View file

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

View file

@ -375,6 +375,7 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr<osgViewer::Viewer> v
pack_evt.y = mMouseY = evt.motion.y;
pack_evt.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
{

View file

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

View file

@ -32,7 +32,7 @@ void BsaArchive::listResources(std::map<std::string, File *> &out, char (*normal
{
for (std::vector<BsaArchiveFile>::iterator it = mResources.begin(); it != mResources.end(); ++it)
{
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;

View file

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

View file

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

View file

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

View file

@ -1,17 +1,9 @@
##################
Native Mesh Format
##################
#################################
Blender to OpenMW with OSG native
#################################
This article explains how to export a model from Blender to OpenMW using the OSG model format.
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
#############
@ -103,12 +95,5 @@ 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