Merge branch 'master' into menuscripts

ini_importer_tests
uramer 12 months ago
commit d1268acf95

1
.gitignore vendored

@ -28,6 +28,7 @@ Doxygen
.idea
cmake-build-*
files/windows/*.aps
.cache/clangd
## qt-creator
CMakeLists.txt.user*
.vs

@ -142,7 +142,7 @@ Ubuntu_GCC:
variables:
CC: gcc
CXX: g++
CCACHE_SIZE: 4G
CCACHE_SIZE: 3G
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
timeout: 2h
@ -193,7 +193,7 @@ Ubuntu_GCC_Debug:
variables:
CC: gcc
CXX: g++
CCACHE_SIZE: 4G
CCACHE_SIZE: 3G
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -O0
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.

@ -8,10 +8,17 @@
Bug #4204: Dead slaughterfish doesn't float to water surface after loading saved game
Bug #4207: RestoreHealth/Fatigue spells have a huge priority even if a success chance is near 0
Bug #4382: Sound output device does not change when it should
Bug #4508: Can't stack enchantment buffs from different instances of the same self-cast generic magic apparel
Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely
Bug #4683: Disposition decrease when player commits crime is not implemented properly
Bug #4742: Actors with wander never stop walking after Loopgroup Walkforward
Bug #4743: PlayGroup doesn't play non-looping animations correctly
Bug #4754: Stack of ammunition cannot be equipped partially
Bug #4816: GetWeaponDrawn returns 1 before weapon is attached
Bug #4822: Non-weapon equipment and body parts can't inherit time from parent animation
Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses
Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation
Bug #5066: Quirks with starting and stopping scripted animations
Bug #5129: Stuttering animation on Centurion Archer
Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place
Bug #5371: Keyframe animation tracks are used for any file that begins with an X
@ -22,6 +29,7 @@
Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load
Bug #6025: Subrecords cannot overlap records
Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex
Bug #6190: Unintuitive sun specularity time of day dependence
Bug #6222: global map cell size can crash openmw if set to too high a value
Bug #6313: Followers with high Fight can turn hostile
Bug #6427: Enemy health bar disappears before damaging effect ends
@ -30,6 +38,8 @@
Bug #6657: Distant terrain tiles become black when using FWIW mod
Bug #6661: Saved games that have no preview screenshot cause issues or crashes
Bug #6716: mwscript comparison operator handling is too restrictive
Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW
Bug #6758: Main menu background video can be stopped by opening the options menu
Bug #6807: Ultimate Galleon is not working properly
Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands
Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack
@ -63,32 +73,63 @@
Bug #7229: Error marker loading failure is not handled
Bug #7243: Supporting loading external files from VFS from esm files
Bug #7284: "Your weapon has no effect." message doesn't always show when the player character attempts to attack
Bug #7292: Weather settings for disabling or enabling snow and rain ripples don't work
Bug #7298: Water ripples from projectiles sometimes are not spawned
Bug #7307: Alchemy "Magic Effect" search string does not match on tool tip for effects related to attributes
Bug #7309: Sunlight scattering is visible in inappropriate situations
Bug #7322: Shadows don't cover groundcover depending on the view angle and perspective with compute scene bounds = primitives
Bug #7354: Disabling post processing in-game causes a crash
Bug #7364: Post processing is not reflected in savegame previews
Bug #7380: NiZBufferProperty issue
Bug #7413: Generated wilderness cells don't spawn fish
Bug #7415: Unbreakable lock discrepancies
Bug #7416: Modpccrimelevel is different from vanilla
Bug #7428: AutoCalc flag is not used to calculate enchantment costs
Bug #7450: Evading obstacles does not work for actors missing certain animations
Bug #7459: Icons get stacked on the cursor when picking up multiple items simultaneously
Bug #7472: Crash when enchanting last projectiles
Bug #7475: Equipping a constant effect item doesn't update the magic menu
Bug #7502: Data directories dialog (0.48.0) forces adding subdirectory instead of intended directory
Bug #7505: Distant terrain does not support sample size greater than cell size
Bug #7553: Faction reaction loading is incorrect
Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading
Bug #7573: Drain Fatigue can't bring fatigue below zero by default
Bug #7585: Difference in interior lighting between OpenMW with legacy lighting method enabled and vanilla Morrowind
Bug #7603: Scripts menu size is not updated properly
Bug #7604: Goblins Grunt becomes idle once injured
Bug #7609: ForceGreeting should not open dialogue for werewolves
Bug #7611: Beast races' idle animations slide after turning or jumping in place
Bug #7619: Long map notes may get cut off
Bug #7630: Charm can be cast on creatures
Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing
Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation
Bug #7637: Actors can sometimes move while playing scripted animations
Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat
Bug #7641: loopgroup loops the animation one time too many for actors
Bug #7642: Items in repair and recharge menus aren't sorted alphabetically
Bug #7643: Can't enchant items with constant effect on self magic effects for non-player character
Bug #7646: Follower voices pain sounds when attacked with magic
Bug #7647: NPC walk cycle bugs after greeting player
Bug #7654: Tooltips for enchantments with invalid effects cause crashes
Bug #7660: Some inconsistencies regarding Invisibility breaking
Bug #7661: Player followers should stop attacking newly recruited actors
Bug #7665: Alchemy menu is missing the ability to deselect and choose different qualities of an apparatus
Bug #7675: Successful lock spell doesn't produce a sound
Bug #7676: Incorrect magic effect order in alchemy
Bug #7679: Scene luminance value flashes when toggling shaders
Bug #7685: Corky sometimes doesn't follow Llovyn Andus
Bug #7712: Casting doesn't support spells and enchantments with no effects
Bug #7723: Assaulting vampires and werewolves shouldn't be a crime
Bug #7724: Guards don't help vs werewolves
Bug #7742: Governing attribute training limit should use the modified attribute
Bug #7758: Water walking is not taken into account to compute path cost on the water
Feature #2566: Handle NAM9 records for manual cell references
Feature #3537: Shader-based water ripples
Feature #5173: Support for NiFogProperty
Feature #5492: Let rain and snow collide with statics
Feature #6149: Dehardcode Lua API_REVISION
Feature #6152: Playing music via lua scripts
Feature #6188: Specular lighting from point light sources
Feature #6447: Add LOD support to Object Paging
Feature #6491: Add support for Qt6
Feature #6556: Lua API for sounds
@ -113,12 +154,17 @@
Feature #7477: NegativeLight Magic Effect flag
Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field
Feature #7546: Start the game on Fredas
Feature #7554: Controller binding for tab for menu navigation
Feature #7568: Uninterruptable scripted music
Feature #7608: Make the missing dependencies warning when loading a savegame more helpful
Feature #7618: Show the player character's health in the save details
Feature #7625: Add some missing console error outputs
Feature #7634: Support NiParticleBomb
Feature #7652: Sort inactive post processing shaders list properly
Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore
Feature #7709: Improve resolution selection in Launcher
Task #5896: Do not use deprecated MyGUI properties
Task #6624: Drop support for saves made prior to 0.45
Task #7113: Move from std::atoi to std::from_char
Task #7117: Replace boost::scoped_array with std::vector
Task #7151: Do not use std::strerror to get errno error message

@ -1,22 +1,18 @@
#!/bin/sh -ex
export HOMEBREW_NO_EMOJI=1
export HOMEBREW_NO_INSTALL_CLEANUP=1
export HOMEBREW_AUTOREMOVE=1
brew uninstall --ignore-dependencies python@3.8 || true
brew uninstall --ignore-dependencies python@3.9 || true
brew uninstall --ignore-dependencies qt@6 || true
brew uninstall --ignore-dependencies jpeg || true
# workaround for gitlab's pre-installed brew
# purge large and unnecessary packages that get in our way and have caused issues
brew uninstall ruby php openjdk node postgresql maven curl || true
brew tap --repair
brew update --quiet
# Some of these tools can come from places other than brew, so check before installing
brew reinstall xquartz fontconfig freetype harfbuzz brotli
# Fix: can't open file: @loader_path/libbrotlicommon.1.dylib (No such file or directory)
BREW_LIB_PATH="$(brew --prefix)/lib"
install_name_tool -change "@loader_path/libbrotlicommon.1.dylib" "${BREW_LIB_PATH}/libbrotlicommon.1.dylib" ${BREW_LIB_PATH}/libbrotlidec.1.dylib
install_name_tool -change "@loader_path/libbrotlicommon.1.dylib" "${BREW_LIB_PATH}/libbrotlicommon.1.dylib" ${BREW_LIB_PATH}/libbrotlienc.1.dylib
brew install curl xquartz gd fontconfig freetype harfbuzz brotli
command -v ccache >/dev/null 2>&1 || brew install ccache
command -v cmake >/dev/null 2>&1 || brew install cmake

@ -902,8 +902,6 @@ printf "Qt ${QT_VER}... "
fi
cd $QT_SDK
add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
-DCMAKE_PREFIX_PATH="$QT_SDK"
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
if [ $CONFIGURATION == "Debug" ]; then
DLLSUFFIX="d"
@ -930,7 +928,7 @@ printf "SDL 2.24.0... "
rm -rf SDL2-2.24.0
eval 7z x -y SDL2-devel-2.24.0-VC.zip $STRIP
fi
export SDL2DIR="$(real_pwd)/SDL2-2.24.0"
SDL2DIR="$(real_pwd)/SDL2-2.24.0"
for config in ${CONFIGURATIONS[@]}; do
add_runtime_dlls $config "$(pwd)/SDL2-2.24.0/lib/x${ARCHSUFFIX}/SDL2.dll"
done
@ -1025,6 +1023,8 @@ printf "zlib 1.2.11... "
echo Done.
}
add_cmake_opts -DCMAKE_PREFIX_PATH="\"${QT_SDK};${SDL2DIR}\""
echo
cd $DEPS_INSTALL/..
echo

@ -19,6 +19,7 @@ apps/openmw_test_suite/lua/test_serialization.cpp
apps/openmw_test_suite/lua/test_storage.cpp
apps/openmw_test_suite/lua/test_ui_content.cpp
apps/openmw_test_suite/lua/test_utilpackage.cpp
apps/openmw_test_suite/lua/test_inputactions.cpp
apps/openmw_test_suite/misc/test_endianness.cpp
apps/openmw_test_suite/misc/test_resourcehelpers.cpp
apps/openmw_test_suite/misc/test_stringops.cpp

@ -86,7 +86,7 @@ declare -rA GROUPED_DEPS=(
libswresample3
libswscale5
libtinyxml2.6.2v5
libyaml-cpp0.7
libyaml-cpp0.8
python3-pip
xvfb
"
@ -125,3 +125,4 @@ add-apt-repository -y ppa:openmw/openmw
add-apt-repository -y ppa:openmw/openmw-daily
add-apt-repository -y ppa:openmw/staging
apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null
apt list --installed

@ -1,4 +1,4 @@
set -e
#!/bin/bash -e
docs/source/install_luadocumentor_in_docker.sh
PATH=$PATH:~/luarocks/bin

@ -71,7 +71,8 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 49)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 50)
set(OPENMW_LUA_API_REVISION 51)
set(OPENMW_POSTPROCESSING_API_REVISION 1)
set(OPENMW_VERSION_COMMITHASH "")
set(OPENMW_VERSION_TAGHASH "")
@ -113,7 +114,6 @@ include(WholeArchive)
configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_BINARY_DIR}/docs/mainpage.hpp")
option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE)
option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE)
option(QT_STATIC "Link static build of Qt into the binaries" FALSE)
option(OPENMW_USE_SYSTEM_BULLET "Use system provided bullet physics library" ON)
@ -179,6 +179,8 @@ if (MSVC)
# there should be no relevant downsides to having it on:
# https://docs.microsoft.com/en-us/cpp/build/reference/bigobj-increase-number-of-sections-in-dot-obj-file
add_compile_options(/bigobj)
add_compile_options(/Zc:__cplusplus)
endif()
# Set up common paths
@ -480,7 +482,6 @@ set(SOL_CONFIG_DIR ${OpenMW_SOURCE_DIR}/extern/sol_config)
include_directories(
BEFORE SYSTEM
"."
${SDL2_INCLUDE_DIR}
${Boost_INCLUDE_DIR}
${MyGUI_INCLUDE_DIRS}
${OPENAL_INCLUDE_DIR}
@ -492,7 +493,7 @@ include_directories(
${ICU_INCLUDE_DIRS}
)
link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${COLLADA_DOM_LIBRARY_DIRS})
link_directories(${Boost_LIBRARY_DIRS} ${COLLADA_DOM_LIBRARY_DIRS})
if(MYGUI_STATIC)
add_definitions(-DMYGUI_STATIC)

@ -5,6 +5,7 @@
#include <components/esm3/loadland.hpp>
#include <algorithm>
#include <iterator>
#include <random>
namespace
@ -24,29 +25,25 @@ namespace
PreparedNavMeshData mValue;
};
template <typename Random>
osg::Vec2i generateVec2i(int max, Random& random)
osg::Vec2i generateVec2i(int max, auto& random)
{
std::uniform_int_distribution<int> distribution(0, max);
return osg::Vec2i(distribution(random), distribution(random));
}
template <typename Random>
osg::Vec3f generateAgentHalfExtents(float min, float max, Random& random)
osg::Vec3f generateAgentHalfExtents(float min, float max, auto& random)
{
std::uniform_int_distribution<int> distribution(min, max);
return osg::Vec3f(distribution(random), distribution(random), distribution(random));
}
template <typename OutputIterator, typename Random>
void generateVertices(OutputIterator out, std::size_t number, Random& random)
void generateVertices(std::output_iterator<int> auto out, std::size_t number, auto& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
std::generate_n(out, 3 * (number - number % 3), [&] { return distribution(random); });
}
template <typename OutputIterator, typename Random>
void generateIndices(OutputIterator out, int max, std::size_t number, Random& random)
void generateIndices(std::output_iterator<int> auto out, int max, std::size_t number, auto& random)
{
std::uniform_int_distribution<int> distribution(0, max);
std::generate_n(out, number - number % 3, [&] { return distribution(random); });
@ -70,21 +67,18 @@ namespace
return AreaType_null;
}
template <typename Random>
AreaType generateAreaType(Random& random)
AreaType generateAreaType(auto& random)
{
std::uniform_int_distribution<int> distribution(0, 4);
return toAreaType(distribution(random));
}
template <typename OutputIterator, typename Random>
void generateAreaTypes(OutputIterator out, std::size_t triangles, Random& random)
void generateAreaTypes(std::output_iterator<AreaType> auto out, std::size_t triangles, auto& random)
{
std::generate_n(out, triangles, [&] { return generateAreaType(random); });
}
template <typename OutputIterator, typename Random>
void generateWater(OutputIterator out, std::size_t count, Random& random)
void generateWater(std::output_iterator<CellWater> auto out, std::size_t count, auto& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
std::generate_n(out, count, [&] {
@ -92,8 +86,7 @@ namespace
});
}
template <class Random>
Mesh generateMesh(std::size_t triangles, Random& random)
Mesh generateMesh(std::size_t triangles, auto& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
std::vector<float> vertices;
@ -109,8 +102,7 @@ namespace
return Mesh(std::move(indices), std::move(vertices), std::move(areaTypes));
}
template <class Random>
Heightfield generateHeightfield(Random& random)
Heightfield generateHeightfield(auto& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
Heightfield result;
@ -127,8 +119,7 @@ namespace
return result;
}
template <class Random>
FlatHeightfield generateFlatHeightfield(Random& random)
FlatHeightfield generateFlatHeightfield(auto& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
FlatHeightfield result;
@ -138,8 +129,7 @@ namespace
return result;
}
template <class Random>
Key generateKey(std::size_t triangles, Random& random)
Key generateKey(std::size_t triangles, auto& random)
{
const CollisionShapeType agentShapeType = CollisionShapeType::Aabb;
const osg::Vec3f agentHalfExtents = generateAgentHalfExtents(0.5, 1.5, random);
@ -158,14 +148,12 @@ namespace
constexpr std::size_t trianglesPerTile = 239;
template <typename OutputIterator, typename Random>
void generateKeys(OutputIterator out, std::size_t count, Random& random)
void generateKeys(std::output_iterator<Key> auto out, std::size_t count, auto& random)
{
std::generate_n(out, count, [&] { return generateKey(trianglesPerTile, random); });
}
template <typename OutputIterator, typename Random>
void fillCache(OutputIterator out, Random& random, NavMeshTilesCache& cache)
void fillCache(std::output_iterator<Key> auto out, auto& random, NavMeshTilesCache& cache)
{
std::size_t size = cache.getStats().mNavMeshCacheSize;

@ -194,7 +194,8 @@ int extract(std::unique_ptr<File>& bsa, Arguments& info)
// Get a stream for the file to extract
for (auto it = bsa->getList().rbegin(); it != bsa->getList().rend(); ++it)
{
if (Misc::StringUtils::ciEqual(Misc::StringUtils::stringToU8String(it->name()), archivePath))
auto streamPath = Misc::StringUtils::stringToU8String(it->name());
if (Misc::StringUtils::ciEqual(streamPath, archivePath) || Misc::StringUtils::ciEqual(streamPath, extractPath))
{
stream = bsa->getFile(&*it);
break;

@ -145,12 +145,12 @@ namespace
config.filterOutNonExistingPaths(dataDirs);
const auto resDir = variables["resources"].as<Files::MaybeQuotedPath>();
const auto& resDir = variables["resources"].as<Files::MaybeQuotedPath>();
Log(Debug::Info) << Version::getOpenmwVersionDescription();
dataDirs.insert(dataDirs.begin(), resDir / "vfs");
const auto fileCollections = Files::Collections(dataDirs);
const auto archives = variables["fallback-archive"].as<StringsVector>();
const auto contentFiles = variables["content"].as<StringsVector>();
const Files::Collections fileCollections(dataDirs);
const auto& archives = variables["fallback-archive"].as<StringsVector>();
const auto& contentFiles = variables["content"].as<StringsVector>();
Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap);

@ -156,7 +156,7 @@ Allowed options)");
return false;
}*/
const auto inputFiles = variables["input-file"].as<Files::MaybeQuotedPathContainer>();
const auto& inputFiles = variables["input-file"].as<Files::MaybeQuotedPathContainer>();
info.filename = inputFiles[0].u8string(); // This call to u8string is redundant, but required to build on
// MSVC 14.26 due to implementation bugs.
if (inputFiles.size() > 1)
@ -265,7 +265,7 @@ namespace
std::cout << " Faction rank: " << ref.mFactionRank << '\n';
std::cout << " Enchantment charge: " << ref.mEnchantmentCharge << '\n';
std::cout << " Uses/health: " << ref.mChargeInt << '\n';
std::cout << " Gold value: " << ref.mGoldValue << '\n';
std::cout << " Count: " << ref.mCount << '\n';
std::cout << " Blocked: " << static_cast<int>(ref.mReferenceBlocked) << '\n';
std::cout << " Deleted: " << deleted << '\n';
if (!ref.mKey.empty())
@ -341,7 +341,7 @@ namespace
{
std::cout << "Author: " << esm.getAuthor() << '\n'
<< "Description: " << esm.getDesc() << '\n'
<< "File format version: " << esm.getFVer() << '\n';
<< "File format version: " << esm.esmVersionF() << '\n';
std::vector<ESM::Header::MasterData> masterData = esm.getGameFiles();
if (!masterData.empty())
{
@ -508,7 +508,7 @@ namespace
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(info.encoding));
esm.setEncoder(&encoder);
esm.setHeader(data.mHeader);
esm.setVersion(ESM::VER_13);
esm.setVersion(ESM::VER_130);
esm.setRecordCount(recordCount);
std::fstream save(info.outname, std::fstream::out | std::fstream::binary);

@ -1084,14 +1084,8 @@ namespace EsmTool
std::cout << " Rank: " << (int)mData.mNpdt.mRank << std::endl;
std::cout << " Attributes:" << std::endl;
std::cout << " Strength: " << (int)mData.mNpdt.mStrength << std::endl;
std::cout << " Intelligence: " << (int)mData.mNpdt.mIntelligence << std::endl;
std::cout << " Willpower: " << (int)mData.mNpdt.mWillpower << std::endl;
std::cout << " Agility: " << (int)mData.mNpdt.mAgility << std::endl;
std::cout << " Speed: " << (int)mData.mNpdt.mSpeed << std::endl;
std::cout << " Endurance: " << (int)mData.mNpdt.mEndurance << std::endl;
std::cout << " Personality: " << (int)mData.mNpdt.mPersonality << std::endl;
std::cout << " Luck: " << (int)mData.mNpdt.mLuck << std::endl;
for (size_t i = 0; i != mData.mNpdt.mAttributes.size(); i++)
std::cout << " " << attributeLabel(i) << ": " << int(mData.mNpdt.mAttributes[i]) << std::endl;
std::cout << " Skills:" << std::endl;
for (size_t i = 0; i != mData.mNpdt.mSkills.size(); i++)
@ -1169,19 +1163,23 @@ namespace EsmTool
std::cout << " Description: " << mData.mDescription << std::endl;
std::cout << " Flags: " << raceFlags(mData.mData.mFlags) << std::endl;
for (int i = 0; i < 2; ++i)
std::cout << " Male:" << std::endl;
for (int j = 0; j < ESM::Attribute::Length; ++j)
{
bool male = i == 0;
std::cout << (male ? " Male:" : " Female:") << std::endl;
for (int j = 0; j < ESM::Attribute::Length; ++j)
std::cout << " " << ESM::Attribute::indexToRefId(j) << ": "
<< mData.mData.mAttributeValues[j].getValue(male) << std::endl;
ESM::RefId id = ESM::Attribute::indexToRefId(j);
std::cout << " " << id << ": " << mData.mData.getAttribute(id, true) << std::endl;
}
std::cout << " Height: " << mData.mData.mMaleHeight << std::endl;
std::cout << " Weight: " << mData.mData.mMaleWeight << std::endl;
std::cout << " Height: " << mData.mData.mHeight.getValue(male) << std::endl;
std::cout << " Weight: " << mData.mData.mWeight.getValue(male) << std::endl;
std::cout << " Female:" << std::endl;
for (int j = 0; j < ESM::Attribute::Length; ++j)
{
ESM::RefId id = ESM::Attribute::indexToRefId(j);
std::cout << " " << id << ": " << mData.mData.getAttribute(id, false) << std::endl;
}
std::cout << " Height: " << mData.mData.mFemaleHeight << std::endl;
std::cout << " Weight: " << mData.mData.mFemaleWeight << std::endl;
for (const auto& bonus : mData.mData.mBonus)
// Not all races have 7 skills.
@ -1204,7 +1202,8 @@ namespace EsmTool
std::array<std::string_view, 10> weathers
= { "Clear", "Cloudy", "Fog", "Overcast", "Rain", "Thunder", "Ash", "Blight", "Snow", "Blizzard" };
for (size_t i = 0; i < weathers.size(); ++i)
std::cout << " " << weathers[i] << ": " << mData.mData.mProbabilities[i] << std::endl;
std::cout << " " << weathers[i] << ": " << static_cast<unsigned>(mData.mData.mProbabilities[i])
<< std::endl;
std::cout << " Map Color: " << mData.mMapColor << std::endl;
if (!mData.mSleepList.empty())
std::cout << " Sleep List: " << mData.mSleepList << std::endl;

@ -1,6 +1,7 @@
#include "converter.hpp"
#include <algorithm>
#include <cstdint>
#include <stdexcept>
#include <osgDB/WriteFile>
@ -33,7 +34,7 @@ namespace
objstate.mPosition = cellref.mPos;
objstate.mRef.mRefNum = cellref.mRefNum;
if (cellref.mDeleted)
objstate.mCount = 0;
objstate.mRef.mCount = 0;
convertSCRI(cellref.mActorData.mSCRI, objstate.mLocals);
objstate.mHasLocals = !objstate.mLocals.mVariables.empty();
@ -90,14 +91,14 @@ namespace ESSImport
struct MAPH
{
unsigned int size;
unsigned int value;
uint32_t size;
uint32_t value;
};
void ConvertFMAP::read(ESM::ESMReader& esm)
{
MAPH maph;
esm.getHNTSized<8>(maph, "MAPH");
esm.getHNT("MAPH", maph.size, maph.value);
std::vector<char> data;
esm.getSubNameIs("MAPD");
esm.getSubHeader();
@ -278,7 +279,7 @@ namespace ESSImport
while (esm.isNextSub("MPCD"))
{
float notepos[3];
esm.getHTSized<3 * sizeof(float)>(notepos);
esm.getHT(notepos);
// Markers seem to be arranged in a 32*32 grid, notepos has grid-indices.
// This seems to be the reason markers can't be placed everywhere in interior cells,

@ -9,15 +9,14 @@ namespace ESSImport
void convertInventory(const Inventory& inventory, ESM::InventoryState& state)
{
int index = 0;
uint32_t index = 0;
for (const auto& item : inventory.mItems)
{
ESM::ObjectState objstate;
objstate.blank();
objstate.mRef = item;
objstate.mRef.mRefID = ESM::RefId::stringRefId(item.mId);
objstate.mCount = std::abs(item.mCount); // restocking items have negative count in the savefile
// openmw handles them differently, so no need to set any flags
objstate.mRef.mCount = item.mCount;
state.mItems.push_back(objstate);
if (item.mRelativeEquipmentSlot != -1)
// Note we should really write the absolute slot here, which we do not know about

@ -1,6 +1,7 @@
#ifndef OPENMW_ESSIMPORT_ACDT_H
#define OPENMW_ESSIMPORT_ACDT_H
#include <cstdint>
#include <string>
#include "importscri.hpp"
@ -25,14 +26,12 @@ namespace ESSImport
};
/// Actor data, shared by (at least) REFR and CellRef
#pragma pack(push)
#pragma pack(1)
struct ACDT
{
// Note, not stored at *all*:
// - Level changes are lost on reload, except for the player (there it's in the NPC record).
unsigned char mUnknown[12];
unsigned int mFlags;
uint32_t mFlags;
float mBreathMeter; // Seconds left before drowning
unsigned char mUnknown2[20];
float mDynamic[3][2];
@ -41,7 +40,7 @@ namespace ESSImport
float mMagicEffects[27]; // Effect attributes:
// https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attributes
unsigned char mUnknown4[4];
unsigned int mGoldPool;
uint32_t mGoldPool;
unsigned char mCountDown; // seen the same value as in ACSC.mCorpseClearCountdown, maybe
// this one is for respawning?
unsigned char mUnknown5[3];
@ -60,7 +59,6 @@ namespace ESSImport
unsigned char mUnknown[3];
float mTime;
};
#pragma pack(pop)
struct ActorData
{

@ -1,6 +1,7 @@
#include "importcellref.hpp"
#include <components/esm3/esmreader.hpp>
#include <cstdint>
namespace ESSImport
{
@ -44,19 +45,14 @@ namespace ESSImport
bool isDeleted = false;
ESM::CellRef::loadData(esm, isDeleted);
mActorData.mHasACDT = false;
if (esm.isNextSub("ACDT"))
{
mActorData.mHasACDT = true;
esm.getHTSized<264>(mActorData.mACDT);
}
mActorData.mHasACDT
= esm.getHNOT("ACDT", mActorData.mACDT.mUnknown, mActorData.mACDT.mFlags, mActorData.mACDT.mBreathMeter,
mActorData.mACDT.mUnknown2, mActorData.mACDT.mDynamic, mActorData.mACDT.mUnknown3,
mActorData.mACDT.mAttributes, mActorData.mACDT.mMagicEffects, mActorData.mACDT.mUnknown4,
mActorData.mACDT.mGoldPool, mActorData.mACDT.mCountDown, mActorData.mACDT.mUnknown5);
mActorData.mHasACSC = false;
if (esm.isNextSub("ACSC"))
{
mActorData.mHasACSC = true;
esm.getHTSized<112>(mActorData.mACSC);
}
mActorData.mHasACSC = esm.getHNOT("ACSC", mActorData.mACSC.mUnknown1, mActorData.mACSC.mFlags,
mActorData.mACSC.mUnknown2, mActorData.mACSC.mCorpseClearCountdown, mActorData.mACSC.mUnknown3);
if (esm.isNextSub("ACSL"))
esm.skipHSubSize(112);
@ -122,23 +118,17 @@ namespace ESSImport
}
// FIXME: not all actors have this, add flag
if (esm.isNextSub("CHRD")) // npc only
esm.getHExact(mActorData.mSkills, 27 * 2 * sizeof(int));
esm.getHNOT("CHRD", mActorData.mSkills); // npc only
if (esm.isNextSub("CRED")) // creature only
esm.getHExact(mActorData.mCombatStats, 3 * 2 * sizeof(int));
esm.getHNOT("CRED", mActorData.mCombatStats); // creature only
mActorData.mSCRI.load(esm);
if (esm.isNextSub("ND3D"))
esm.skipHSub();
mActorData.mHasANIS = false;
if (esm.isNextSub("ANIS"))
{
mActorData.mHasANIS = true;
esm.getHTSized<8>(mActorData.mANIS);
}
mActorData.mHasANIS
= esm.getHNOT("ANIS", mActorData.mANIS.mGroupIndex, mActorData.mANIS.mUnknown, mActorData.mANIS.mTime);
if (esm.isNextSub("LVCR"))
{
@ -155,13 +145,13 @@ namespace ESSImport
// DATA should occur for all references, except levelled creature spawners
// I've seen DATA *twice* on a creature record, and with the exact same content too! weird
// alarmvoi0000.ess
esm.getHNOTSized<24>(mPos, "DATA");
esm.getHNOTSized<24>(mPos, "DATA");
for (int i = 0; i < 2; ++i)
esm.getHNOT("DATA", mPos.pos, mPos.rot);
mDeleted = 0;
if (esm.isNextSub("DELE"))
{
unsigned int deleted;
uint32_t deleted;
esm.getHT(deleted);
mDeleted = ((deleted >> 24) & 0x2) != 0; // the other 3 bytes seem to be uninitialized garbage
}

@ -1,6 +1,7 @@
#include "importcntc.hpp"
#include <components/esm3/esmreader.hpp>
#include <cstdint>
namespace ESSImport
{

@ -14,7 +14,7 @@ namespace ESSImport
/// Changed container contents
struct CNTC
{
int mIndex;
int32_t mIndex;
Inventory mInventory;

@ -3,6 +3,7 @@
#include "importinventory.hpp"
#include <components/esm3/aipackage.hpp>
#include <cstdint>
namespace ESM
{
@ -15,7 +16,7 @@ namespace ESSImport
/// Creature changes
struct CREC
{
int mIndex;
int32_t mIndex;
Inventory mInventory;
ESM::AIPackageList mAiPackages;

@ -8,11 +8,11 @@ namespace ESSImport
void DIAL::load(ESM::ESMReader& esm)
{
// See ESM::Dialogue::Type enum, not sure why we would need this here though
int type = 0;
int32_t type = 0;
esm.getHNOT(type, "DATA");
// Deleted dialogue in a savefile. No clue what this means...
int deleted = 0;
int32_t deleted = 0;
esm.getHNOT(deleted, "DELE");
mIndex = 0;

@ -1,5 +1,8 @@
#ifndef OPENMW_ESSIMPORT_IMPORTDIAL_H
#define OPENMW_ESSIMPORT_IMPORTDIAL_H
#include <cstdint>
namespace ESM
{
class ESMReader;
@ -10,7 +13,7 @@ namespace ESSImport
struct DIAL
{
int mIndex; // Journal index
int32_t mIndex; // Journal index
void load(ESM::ESMReader& esm);
};

@ -9,17 +9,17 @@ namespace ESSImport
{
esm.getSubNameIs("GMDT");
esm.getSubHeader();
if (esm.getSubSize() == 92)
{
esm.getExact(&mGMDT, 92);
mGMDT.mSecundaPhase = 0;
}
else if (esm.getSubSize() == 96)
{
esm.getTSized<96>(mGMDT);
}
else
esm.fail("unexpected subrecord size for GAME.GMDT");
bool hasSecundaPhase = esm.getSubSize() == 96;
esm.getT(mGMDT.mCellName);
esm.getT(mGMDT.mFogColour);
esm.getT(mGMDT.mFogDensity);
esm.getT(mGMDT.mCurrentWeather);
esm.getT(mGMDT.mNextWeather);
esm.getT(mGMDT.mWeatherTransition);
esm.getT(mGMDT.mTimeOfNextTransition);
esm.getT(mGMDT.mMasserPhase);
if (hasSecundaPhase)
esm.getT(mGMDT.mSecundaPhase);
mGMDT.mWeatherTransition &= (0x000000ff);
mGMDT.mSecundaPhase &= (0x000000ff);

@ -1,6 +1,8 @@
#ifndef OPENMW_ESSIMPORT_GAME_H
#define OPENMW_ESSIMPORT_GAME_H
#include <cstdint>
namespace ESM
{
class ESMReader;
@ -15,12 +17,12 @@ namespace ESSImport
struct GMDT
{
char mCellName[64]{};
int mFogColour{ 0 };
int32_t mFogColour{ 0 };
float mFogDensity{ 0.f };
int mCurrentWeather{ 0 }, mNextWeather{ 0 };
int mWeatherTransition{ 0 }; // 0-100 transition between weathers, top 3 bytes may be garbage
int32_t mCurrentWeather{ 0 }, mNextWeather{ 0 };
int32_t mWeatherTransition{ 0 }; // 0-100 transition between weathers, top 3 bytes may be garbage
float mTimeOfNextTransition{ 0.f }; // weather changes when gamehour == timeOfNextTransition
int mMasserPhase{ 0 }, mSecundaPhase{ 0 }; // top 3 bytes may be garbage
int32_t mMasserPhase{ 0 }, mSecundaPhase{ 0 }; // top 3 bytes may be garbage
};
GMDT mGMDT;

@ -12,7 +12,7 @@ namespace ESSImport
while (esm.isNextSub("NPCO"))
{
ContItem contItem;
esm.getHTSized<36>(contItem);
esm.getHT(contItem.mCount, contItem.mItem.mData);
InventoryItem item;
item.mId = contItem.mItem.toString();
@ -28,7 +28,7 @@ namespace ESSImport
bool newStack = esm.isNextSub("XIDX");
if (newStack)
{
unsigned int idx;
uint32_t idx;
esm.getHT(idx);
separateStacks = true;
item.mCount = 1;
@ -40,7 +40,7 @@ namespace ESSImport
bool isDeleted = false;
item.ESM::CellRef::loadData(esm, isDeleted);
int charge = -1;
int32_t charge = -1;
esm.getHNOT(charge, "XHLT");
item.mChargeInt = charge;
@ -60,7 +60,7 @@ namespace ESSImport
// this is currently not handled properly.
esm.getSubHeader();
int itemIndex; // index of the item in the NPCO list
int32_t itemIndex; // index of the item in the NPCO list
esm.getT(itemIndex);
if (itemIndex < 0 || itemIndex >= int(mItems.size()))
@ -68,7 +68,7 @@ namespace ESSImport
// appears to be a relative index for only the *possible* slots this item can be equipped in,
// i.e. 0 most of the time
int slotIndex;
int32_t slotIndex;
esm.getT(slotIndex);
mItems[itemIndex].mRelativeEquipmentSlot = slotIndex;

@ -1,6 +1,7 @@
#ifndef OPENMW_ESSIMPORT_IMPORTINVENTORY_H
#define OPENMW_ESSIMPORT_IMPORTINVENTORY_H
#include <cstdint>
#include <string>
#include <vector>
@ -19,7 +20,7 @@ namespace ESSImport
struct ContItem
{
int mCount;
int32_t mCount;
ESM::NAME32 mItem;
};
@ -28,8 +29,8 @@ namespace ESSImport
struct InventoryItem : public ESM::CellRef
{
std::string mId;
int mCount;
int mRelativeEquipmentSlot;
int32_t mCount;
int32_t mRelativeEquipmentSlot;
SCRI mSCRI;
};
std::vector<InventoryItem> mItems;

@ -10,7 +10,7 @@ namespace ESSImport
while (esm.isNextSub("KNAM"))
{
std::string refId = esm.getHString();
int count;
int32_t count;
esm.getHNT(count, "CNAM");
mKillCounter[refId] = count;
}

@ -1,6 +1,7 @@
#ifndef OPENMW_ESSIMPORT_KLST_H
#define OPENMW_ESSIMPORT_KLST_H
#include <cstdint>
#include <map>
#include <string>
@ -18,9 +19,9 @@ namespace ESSImport
void load(ESM::ESMReader& esm);
/// RefId, kill count
std::map<std::string, int> mKillCounter;
std::map<std::string, int32_t> mKillCounter;
int mWerewolfKills;
int32_t mWerewolfKills;
};
}

@ -7,7 +7,7 @@ namespace ESSImport
void NPCC::load(ESM::ESMReader& esm)
{
esm.getHNTSized<8>(mNPDT, "NPDT");
esm.getHNT("NPDT", mNPDT.mDisposition, mNPDT.unknown, mNPDT.mReputation, mNPDT.unknown2, mNPDT.mIndex);
while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F")
|| esm.isNextSub("AI_A"))

@ -2,6 +2,7 @@
#define OPENMW_ESSIMPORT_NPCC_H
#include <components/esm3/aipackage.hpp>
#include <cstdint>
#include "importinventory.hpp"
@ -21,7 +22,7 @@ namespace ESSImport
unsigned char unknown;
unsigned char mReputation;
unsigned char unknown2;
int mIndex;
int32_t mIndex;
} mNPDT;
Inventory mInventory;

@ -19,7 +19,12 @@ namespace ESSImport
mMNAM = esm.getHString();
}
esm.getHNTSized<212>(mPNAM, "PNAM");
esm.getHNT("PNAM", mPNAM.mPlayerFlags, mPNAM.mLevelProgress, mPNAM.mSkillProgress, mPNAM.mSkillIncreases,
mPNAM.mTelekinesisRangeBonus, mPNAM.mVisionBonus, mPNAM.mDetectKeyMagnitude,
mPNAM.mDetectEnchantmentMagnitude, mPNAM.mDetectAnimalMagnitude, mPNAM.mMarkLocation.mX,
mPNAM.mMarkLocation.mY, mPNAM.mMarkLocation.mZ, mPNAM.mMarkLocation.mRotZ, mPNAM.mMarkLocation.mCellX,
mPNAM.mMarkLocation.mCellY, mPNAM.mUnknown3, mPNAM.mVerticalRotation.mData, mPNAM.mSpecIncreases,
mPNAM.mUnknown4);
if (esm.isNextSub("SNAM"))
esm.skipHSub();
@ -50,12 +55,7 @@ namespace ESSImport
if (esm.isNextSub("NAM3"))
esm.skipHSub();
mHasENAM = false;
if (esm.isNextSub("ENAM"))
{
mHasENAM = true;
esm.getHTSized<8>(mENAM);
}
mHasENAM = esm.getHNOT("ENAM", mENAM.mCellX, mENAM.mCellY);
if (esm.isNextSub("LNAM"))
esm.skipHSub();
@ -63,16 +63,12 @@ namespace ESSImport
while (esm.isNextSub("FNAM"))
{
FNAM fnam;
esm.getHTSized<44>(fnam);
esm.getHT(
fnam.mRank, fnam.mUnknown1, fnam.mReputation, fnam.mFlags, fnam.mUnknown2, fnam.mFactionName.mData);
mFactions.push_back(fnam);
}
mHasAADT = false;
if (esm.isNextSub("AADT")) // Attack animation data?
{
mHasAADT = true;
esm.getHTSized<44>(mAADT);
}
mHasAADT = esm.getHNOT("AADT", mAADT.animGroupIndex, mAADT.mUnknown5); // Attack animation data?
if (esm.isNextSub("KNAM"))
esm.skipHSub(); // assigned Quick Keys, I think

@ -1,6 +1,7 @@
#ifndef OPENMW_ESSIMPORT_PLAYER_H
#define OPENMW_ESSIMPORT_PLAYER_H
#include <cstdint>
#include <string>
#include <vector>
@ -17,7 +18,7 @@ namespace ESSImport
/// Other player data
struct PCDT
{
int mBounty;
int32_t mBounty;
std::string mBirthsign;
std::vector<std::string> mKnownDialogueTopics;
@ -41,13 +42,11 @@ namespace ESSImport
PlayerFlags_LevitationDisabled = 0x80000
};
#pragma pack(push)
#pragma pack(1)
struct FNAM
{
unsigned char mRank;
unsigned char mUnknown1[3];
int mReputation;
int32_t mReputation;
unsigned char mFlags; // 0x1: unknown, 0x2: expelled
unsigned char mUnknown2[3];
ESM::NAME32 mFactionName;
@ -59,7 +58,7 @@ namespace ESSImport
{
float mX, mY, mZ; // worldspace position
float mRotZ; // Z angle in radians
int mCellX, mCellY; // grid coordinates; for interior cells this is always (0, 0)
int32_t mCellX, mCellY; // grid coordinates; for interior cells this is always (0, 0)
};
struct Rotation
@ -67,15 +66,15 @@ namespace ESSImport
float mData[3][3];
};
int mPlayerFlags; // controls, camera and draw state
unsigned int mLevelProgress;
int32_t mPlayerFlags; // controls, camera and draw state
uint32_t mLevelProgress;
float mSkillProgress[27]; // skill progress, non-uniform scaled
unsigned char mSkillIncreases[8]; // number of skill increases for each attribute
int mTelekinesisRangeBonus; // in units; seems redundant
int32_t mTelekinesisRangeBonus; // in units; seems redundant
float mVisionBonus; // range: <0.0, 1.0>; affected by light spells and Get/Mod/SetPCVisionBonus
int mDetectKeyMagnitude; // seems redundant
int mDetectEnchantmentMagnitude; // seems redundant
int mDetectAnimalMagnitude; // seems redundant
int32_t mDetectKeyMagnitude; // seems redundant
int32_t mDetectEnchantmentMagnitude; // seems redundant
int32_t mDetectAnimalMagnitude; // seems redundant
MarkLocation mMarkLocation;
unsigned char mUnknown3[4];
Rotation mVerticalRotation;
@ -85,16 +84,15 @@ namespace ESSImport
struct ENAM
{
int mCellX;
int mCellY;
int32_t mCellX;
int32_t mCellY;
};
struct AADT // 44 bytes
{
int animGroupIndex; // See convertANIS() for the mapping.
int32_t animGroupIndex; // See convertANIS() for the mapping.
unsigned char mUnknown5[40];
};
#pragma pack(pop)
std::vector<FNAM> mFactions;
PNAM mPNAM;

@ -10,7 +10,9 @@ namespace ESSImport
while (esm.isNextSub("PNAM"))
{
PNAM pnam;
esm.getHTSized<184>(pnam);
esm.getHT(pnam.mAttackStrength, pnam.mSpeed, pnam.mUnknown, pnam.mFlightTime, pnam.mSplmIndex,
pnam.mUnknown2, pnam.mVelocity.mValues, pnam.mPosition.mValues, pnam.mUnknown3, pnam.mActorId.mData,
pnam.mArrowId.mData, pnam.mBowId.mData);
mProjectiles.push_back(pnam);
}
}

@ -3,6 +3,7 @@
#include <components/esm/esmcommon.hpp>
#include <components/esm/util.hpp>
#include <cstdint>
#include <vector>
namespace ESM
@ -16,15 +17,13 @@ namespace ESSImport
struct PROJ
{
#pragma pack(push)
#pragma pack(1)
struct PNAM // 184 bytes
{
float mAttackStrength;
float mSpeed;
unsigned char mUnknown[4 * 2];
float mFlightTime;
int mSplmIndex; // reference to a SPLM record (0 for ballistic projectiles)
int32_t mSplmIndex; // reference to a SPLM record (0 for ballistic projectiles)
unsigned char mUnknown2[4];
ESM::Vector3 mVelocity;
ESM::Vector3 mPosition;
@ -35,7 +34,6 @@ namespace ESSImport
bool isMagic() const { return mSplmIndex != 0; }
};
#pragma pack(pop)
std::vector<PNAM> mProjectiles;

@ -7,7 +7,8 @@ namespace ESSImport
void SCPT::load(ESM::ESMReader& esm)
{
esm.getHNTSized<52>(mSCHD, "SCHD");
esm.getHNT("SCHD", mSCHD.mName.mData, mSCHD.mData.mNumShorts, mSCHD.mData.mNumLongs, mSCHD.mData.mNumFloats,
mSCHD.mData.mScriptDataSize, mSCHD.mData.mStringTableSize);
mSCRI.load(esm);

@ -3,6 +3,8 @@
#include "importscri.hpp"
#include <cstdint>
#include <components/esm/esmcommon.hpp>
#include <components/esm3/loadscpt.hpp>
@ -29,7 +31,7 @@ namespace ESSImport
SCRI mSCRI;
bool mRunning;
int mRefNum; // Targeted reference, -1: no reference
int32_t mRefNum; // Targeted reference, -1: no reference
void load(ESM::ESMReader& esm);
};

@ -9,7 +9,7 @@ namespace ESSImport
{
mScript = esm.getHNOString("SCRI");
int numShorts = 0, numLongs = 0, numFloats = 0;
int32_t numShorts = 0, numLongs = 0, numFloats = 0;
if (esm.isNextSub("SLCS"))
{
esm.getSubHeader();
@ -23,7 +23,7 @@ namespace ESSImport
esm.getSubHeader();
for (int i = 0; i < numShorts; ++i)
{
short val;
int16_t val;
esm.getT(val);
mShorts.push_back(val);
}
@ -35,7 +35,7 @@ namespace ESSImport
esm.getSubHeader();
for (int i = 0; i < numLongs; ++i)
{
int val;
int32_t val;
esm.getT(val);
mLongs.push_back(val);
}

@ -3,6 +3,7 @@
#include <components/esm3/variant.hpp>
#include <cstdint>
#include <vector>
namespace ESM

@ -11,13 +11,15 @@ namespace ESSImport
{
ActiveSpell spell;
esm.getHT(spell.mIndex);
esm.getHNTSized<160>(spell.mSPDT, "SPDT");
esm.getHNT("SPDT", spell.mSPDT.mType, spell.mSPDT.mId.mData, spell.mSPDT.mUnknown,
spell.mSPDT.mCasterId.mData, spell.mSPDT.mSourceId.mData, spell.mSPDT.mUnknown2);
spell.mTarget = esm.getHNOString("TNAM");
while (esm.isNextSub("NPDT"))
{
ActiveEffect effect;
esm.getHTSized<56>(effect.mNPDT);
esm.getHT(effect.mNPDT.mAffectedActorId.mData, effect.mNPDT.mUnknown, effect.mNPDT.mMagnitude,
effect.mNPDT.mSecondsActive, effect.mNPDT.mUnknown2);
// Effect-specific subrecords can follow:
// - INAM for disintegration and bound effects

@ -2,6 +2,7 @@
#define OPENMW_ESSIMPORT_IMPORTSPLM_H
#include <components/esm/esmcommon.hpp>
#include <cstdint>
#include <vector>
namespace ESM
@ -15,11 +16,9 @@ namespace ESSImport
struct SPLM
{
#pragma pack(push)
#pragma pack(1)
struct SPDT // 160 bytes
{
int mType; // 1 = spell, 2 = enchantment, 3 = potion
int32_t mType; // 1 = spell, 2 = enchantment, 3 = potion
ESM::NAME32 mId; // base ID of a spell/enchantment/potion
unsigned char mUnknown[4 * 4];
ESM::NAME32 mCasterId;
@ -31,31 +30,29 @@ namespace ESSImport
{
ESM::NAME32 mAffectedActorId;
unsigned char mUnknown[4 * 2];
int mMagnitude;
int32_t mMagnitude;
float mSecondsActive;
unsigned char mUnknown2[4 * 2];
};
struct INAM // 40 bytes
{
int mUnknown;
int32_t mUnknown;
unsigned char mUnknown2;
ESM::FixedString<35> mItemId; // disintegrated item / bound item / item to re-equip after expiration
};
struct CNAM // 36 bytes
{
int mUnknown; // seems to always be 0
int32_t mUnknown; // seems to always be 0
ESM::NAME32 mSummonedOrCommandedActor[32];
};
struct VNAM // 4 bytes
{
int mUnknown;
int32_t mUnknown;
};
#pragma pack(pop)
struct ActiveEffect
{
NPDT mNPDT;
@ -63,7 +60,7 @@ namespace ESSImport
struct ActiveSpell
{
int mIndex;
int32_t mIndex;
SPDT mSPDT;
std::string mTarget;
std::vector<ActiveEffect> mActiveEffects;

@ -42,8 +42,8 @@ Allowed options)");
Files::ConfigurationManager cfgManager(true);
cfgManager.readConfiguration(variables, desc);
const auto essFile = variables["mwsave"].as<Files::MaybeQuotedPath>();
const auto outputFile = variables["output"].as<Files::MaybeQuotedPath>();
const auto& essFile = variables["mwsave"].as<Files::MaybeQuotedPath>();
const auto& outputFile = variables["output"].as<Files::MaybeQuotedPath>();
std::string encoding = variables["encoding"].as<std::string>();
ESSImport::Importer importer(essFile, outputFile, encoding);

@ -35,13 +35,12 @@ set(LAUNCHER_HEADER
# Headers that must be pre-processed
set(LAUNCHER_UI
${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui
${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui
${CMAKE_SOURCE_DIR}/files/ui/mainwindow.ui
${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
${CMAKE_SOURCE_DIR}/files/ui/importpage.ui
${CMAKE_SOURCE_DIR}/files/ui/settingspage.ui
${CMAKE_SOURCE_DIR}/files/ui/directorypicker.ui
${CMAKE_CURRENT_SOURCE_DIR}/ui/datafilespage.ui
${CMAKE_CURRENT_SOURCE_DIR}/ui/graphicspage.ui
${CMAKE_CURRENT_SOURCE_DIR}/ui/mainwindow.ui
${CMAKE_CURRENT_SOURCE_DIR}/ui/importpage.ui
${CMAKE_CURRENT_SOURCE_DIR}/ui/settingspage.ui
${CMAKE_CURRENT_SOURCE_DIR}/ui/directorypicker.ui
)
source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER})
@ -77,7 +76,7 @@ if (WIN32)
endif (WIN32)
target_link_libraries(openmw-launcher
${SDL2_LIBRARY_ONLY}
SDL2::SDL2
${OPENAL_LIBRARY}
components_qt
)

@ -26,7 +26,7 @@
#include <components/files/qtconversion.hpp>
#include <components/misc/strings/conversion.hpp>
#include <components/navmeshtool/protocol.hpp>
#include <components/settings/settings.hpp>
#include <components/settings/values.hpp>
#include <components/vfs/bsaarchive.hpp>
#include "utils/profilescombobox.hpp"
@ -123,7 +123,7 @@ namespace Launcher
int getMaxNavMeshDbFileSizeMiB()
{
return Settings::Manager::getUInt64("max navmeshdb file size", "Navigator") / (1024 * 1024);
return Settings::navigator().mMaxNavmeshdbFileSize / (1024 * 1024);
}
std::optional<QString> findFirstPath(const QStringList& directories, const QString& fileName)
@ -164,11 +164,14 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C
const QString encoding = mGameSettings.value("encoding", "win1252");
mSelector->setEncoding(encoding);
QStringList languages;
languages << tr("English") << tr("French") << tr("German") << tr("Italian") << tr("Polish") << tr("Russian")
<< tr("Spanish");
QVector<std::pair<QString, QString>> languages = { { "English", tr("English") }, { "French", tr("French") },
{ "German", tr("German") }, { "Italian", tr("Italian") }, { "Polish", tr("Polish") },
{ "Russian", tr("Russian") }, { "Spanish", tr("Spanish") } };
mSelector->languageBox()->addItems(languages);
for (auto lang : languages)
{
mSelector->languageBox()->addItem(lang.second, lang.first);
}
mNewProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this);
mCloneProfileDialog = new TextInputDialog(tr("Clone Content List"), tr("Content List name:"), this);
@ -254,9 +257,17 @@ bool Launcher::DataFilesPage::loadSettings()
if (!currentProfile.isEmpty())
addProfile(currentProfile, true);
const int index = mSelector->languageBox()->findText(mLauncherSettings.getLanguage());
if (index != -1)
mSelector->languageBox()->setCurrentIndex(index);
auto language = mLauncherSettings.getLanguage();
for (int i = 0; i < mSelector->languageBox()->count(); ++i)
{
QString languageItem = mSelector->languageBox()->itemData(i).toString();
if (language == languageItem)
{
mSelector->languageBox()->setCurrentIndex(i);
break;
}
}
return true;
}
@ -301,12 +312,14 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
auto row = ui.directoryListWidget->count() - 1;
auto* item = ui.directoryListWidget->item(row);
// Display new content with green background
// Display new content with custom formatting
if (mNewDataDirs.contains(canonicalDirPath))
{
tooltip += "Will be added to the current profile\n";
item->setBackground(Qt::green);
item->setForeground(Qt::black);
QFont font = item->font();
font.setBold(true);
font.setItalic(true);
item->setFont(font);
}
// deactivate data-local and global data directory: they are always included
@ -359,9 +372,8 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
void Launcher::DataFilesPage::saveSettings(const QString& profile)
{
if (const int value = ui.navMeshMaxSizeSpinBox->value(); value != getMaxNavMeshDbFileSizeMiB())
Settings::Manager::setUInt64(
"max navmeshdb file size", "Navigator", static_cast<std::uint64_t>(std::max(0, value)) * 1024 * 1024);
Settings::navigator().mMaxNavmeshdbFileSize.set(
static_cast<std::uint64_t>(std::max(0, ui.navMeshMaxSizeSpinBox->value())) * 1024 * 1024);
QString profileName = profile;
@ -385,7 +397,7 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile)
mLauncherSettings.setContentList(profileName, dirList, selectedArchivePaths(), fileNames);
mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames);
QString language(mSelector->languageBox()->currentText());
QString language(mSelector->languageBox()->currentData().toString());
mLauncherSettings.setLanguage(language);
@ -738,8 +750,11 @@ void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState sel
ui.archiveListWidget->item(row)->setCheckState(selected);
if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ???
{
ui.archiveListWidget->item(row)->setBackground(Qt::green);
ui.archiveListWidget->item(row)->setForeground(Qt::black);
auto item = ui.archiveListWidget->item(row);
QFont font = item->font();
font.setBold(true);
font.setItalic(true);
item->setFont(font);
}
}

@ -2,6 +2,7 @@
#include "sdlinit.hpp"
#include <components/misc/display.hpp>
#include <components/settings/values.hpp>
#include <QMessageBox>
@ -16,22 +17,6 @@
#include <SDL_video.h>
#include <array>
#include <numeric>
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
if (xaspect == 8 && yaspect == 5)
return QString("16:10");
return QString(QString::number(xaspect) + ":" + QString::number(yaspect));
}
Launcher::GraphicsPage::GraphicsPage(QWidget* parent)
: QWidget(parent)
@ -96,32 +81,29 @@ bool Launcher::GraphicsPage::loadSettings()
// Visuals
int vsync = Settings::Manager::getInt("vsync mode", "Video");
if (vsync < 0 || vsync > 2)
vsync = 0;
const int vsync = Settings::video().mVsyncMode;
vSyncComboBox->setCurrentIndex(vsync);
size_t windowMode = static_cast<size_t>(Settings::Manager::getInt("window mode", "Video"));
if (windowMode > static_cast<size_t>(Settings::WindowMode::Windowed))
windowMode = 0;
windowModeComboBox->setCurrentIndex(windowMode);
slotFullScreenChanged(windowMode);
const Settings::WindowMode windowMode = Settings::video().mWindowMode;
windowModeComboBox->setCurrentIndex(static_cast<int>(windowMode));
handleWindowModeChange(windowMode);
if (Settings::Manager::getBool("window border", "Video"))
if (Settings::video().mWindowBorder)
windowBorderCheckBox->setCheckState(Qt::Checked);
// aaValue is the actual value (0, 1, 2, 4, 8, 16)
int aaValue = Settings::Manager::getInt("antialiasing", "Video");
const int aaValue = Settings::video().mAntialiasing;
// aaIndex is the index into the allowed values in the pull down.
int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue));
const int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue));
if (aaIndex != -1)
antiAliasingComboBox->setCurrentIndex(aaIndex);
int width = Settings::Manager::getInt("resolution x", "Video");
int height = Settings::Manager::getInt("resolution y", "Video");
QString resolution = QString::number(width) + QString(" x ") + QString::number(height);
screenComboBox->setCurrentIndex(Settings::Manager::getInt("screen", "Video"));
const int width = Settings::video().mResolutionX;
const int height = Settings::video().mResolutionY;
QString resolution = QString::number(width) + QString(" × ") + QString::number(height);
screenComboBox->setCurrentIndex(Settings::video().mScreen);
int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith);
@ -137,7 +119,7 @@ bool Launcher::GraphicsPage::loadSettings()
customHeightSpinBox->setValue(height);
}
float fpsLimit = Settings::Manager::getFloat("framerate limit", "Video");
const float fpsLimit = Settings::video().mFramerateLimit;
if (fpsLimit != 0)
{
framerateLimitCheckBox->setCheckState(Qt::Checked);
@ -161,32 +143,37 @@ bool Launcher::GraphicsPage::loadSettings()
lightingMethodComboBox->setCurrentIndex(lightingMethod);
// Shadows
if (Settings::Manager::getBool("actor shadows", "Shadows"))
if (Settings::shadows().mActorShadows)
actorShadowsCheckBox->setCheckState(Qt::Checked);
if (Settings::Manager::getBool("player shadows", "Shadows"))
if (Settings::shadows().mPlayerShadows)
playerShadowsCheckBox->setCheckState(Qt::Checked);
if (Settings::Manager::getBool("terrain shadows", "Shadows"))
if (Settings::shadows().mTerrainShadows)
terrainShadowsCheckBox->setCheckState(Qt::Checked);
if (Settings::Manager::getBool("object shadows", "Shadows"))
if (Settings::shadows().mObjectShadows)
objectShadowsCheckBox->setCheckState(Qt::Checked);
if (Settings::Manager::getBool("enable indoor shadows", "Shadows"))
if (Settings::shadows().mEnableIndoorShadows)
indoorShadowsCheckBox->setCheckState(Qt::Checked);
shadowComputeSceneBoundsComboBox->setCurrentIndex(shadowComputeSceneBoundsComboBox->findText(
QString(tr(Settings::Manager::getString("compute scene bounds", "Shadows").c_str()))));
auto boundMethod = Settings::shadows().mComputeSceneBounds.get();
if (boundMethod == "bounds")
shadowComputeSceneBoundsComboBox->setCurrentIndex(0);
else if (boundMethod == "primitives")
shadowComputeSceneBoundsComboBox->setCurrentIndex(1);
else
shadowComputeSceneBoundsComboBox->setCurrentIndex(2);
int shadowDistLimit = Settings::Manager::getInt("maximum shadow map distance", "Shadows");
const int shadowDistLimit = Settings::shadows().mMaximumShadowMapDistance;
if (shadowDistLimit > 0)
{
shadowDistanceCheckBox->setCheckState(Qt::Checked);
shadowDistanceSpinBox->setValue(shadowDistLimit);
}
float shadowFadeStart = Settings::Manager::getFloat("shadow fade start", "Shadows");
const float shadowFadeStart = Settings::shadows().mShadowFadeStart;
if (shadowFadeStart != 0)
fadeStartSpinBox->setValue(shadowFadeStart);
int shadowRes = Settings::Manager::getInt("shadow map resolution", "Shadows");
const int shadowRes = Settings::shadows().mShadowMapResolution;
int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes));
if (shadowResIndex != -1)
shadowResolutionComboBox->setCurrentIndex(shadowResIndex);
@ -198,29 +185,16 @@ void Launcher::GraphicsPage::saveSettings()
{
// Visuals
// Ensure we only set the new settings if they changed. This is to avoid cluttering the
// user settings file (which by definition should only contain settings the user has touched)
int cVSync = vSyncComboBox->currentIndex();
if (cVSync != Settings::Manager::getInt("vsync mode", "Video"))
Settings::Manager::setInt("vsync mode", "Video", cVSync);
int cWindowMode = windowModeComboBox->currentIndex();
if (cWindowMode != Settings::Manager::getInt("window mode", "Video"))
Settings::Manager::setInt("window mode", "Video", cWindowMode);
bool cWindowBorder = windowBorderCheckBox->checkState();
if (cWindowBorder != Settings::Manager::getBool("window border", "Video"))
Settings::Manager::setBool("window border", "Video", cWindowBorder);
int cAAValue = antiAliasingComboBox->currentText().toInt();
if (cAAValue != Settings::Manager::getInt("antialiasing", "Video"))
Settings::Manager::setInt("antialiasing", "Video", cAAValue);
Settings::video().mVsyncMode.set(static_cast<SDLUtil::VSyncMode>(vSyncComboBox->currentIndex()));
Settings::video().mWindowMode.set(static_cast<Settings::WindowMode>(windowModeComboBox->currentIndex()));
Settings::video().mWindowBorder.set(windowBorderCheckBox->checkState() == Qt::Checked);
Settings::video().mAntialiasing.set(antiAliasingComboBox->currentText().toInt());
int cWidth = 0;
int cHeight = 0;
if (standardRadioButton->isChecked())
{
QRegularExpression resolutionRe("^(\\d+) x (\\d+)");
QRegularExpression resolutionRe("^(\\d+) × (\\d+)");
QRegularExpressionMatch match = resolutionRe.match(resolutionComboBox->currentText().simplified());
if (match.hasMatch())
{
@ -234,25 +208,17 @@ void Launcher::GraphicsPage::saveSettings()
cHeight = customHeightSpinBox->value();
}
if (cWidth != Settings::Manager::getInt("resolution x", "Video"))
Settings::Manager::setInt("resolution x", "Video", cWidth);
if (cHeight != Settings::Manager::getInt("resolution y", "Video"))
Settings::Manager::setInt("resolution y", "Video", cHeight);
int cScreen = screenComboBox->currentIndex();
if (cScreen != Settings::Manager::getInt("screen", "Video"))
Settings::Manager::setInt("screen", "Video", cScreen);
Settings::video().mResolutionX.set(cWidth);
Settings::video().mResolutionY.set(cHeight);
Settings::video().mScreen.set(screenComboBox->currentIndex());
if (framerateLimitCheckBox->checkState() != Qt::Unchecked)
{
float cFpsLimit = framerateLimitSpinBox->value();
if (cFpsLimit != Settings::Manager::getFloat("framerate limit", "Video"))
Settings::Manager::setFloat("framerate limit", "Video", cFpsLimit);
Settings::video().mFramerateLimit.set(framerateLimitSpinBox->value());
}
else if (Settings::Manager::getFloat("framerate limit", "Video") != 0)
else if (Settings::video().mFramerateLimit != 0)
{
Settings::Manager::setFloat("framerate limit", "Video", 0);
Settings::video().mFramerateLimit.set(0);
}
// Lighting
@ -264,55 +230,43 @@ void Launcher::GraphicsPage::saveSettings()
Settings::shaders().mLightingMethod.set(lightingMethodMap[lightingMethodComboBox->currentIndex()]);
// Shadows
int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0;
if (Settings::Manager::getInt("maximum shadow map distance", "Shadows") != cShadowDist)
Settings::Manager::setInt("maximum shadow map distance", "Shadows", cShadowDist);
float cFadeStart = fadeStartSpinBox->value();
if (cShadowDist > 0 && Settings::Manager::getFloat("shadow fade start", "Shadows") != cFadeStart)
Settings::Manager::setFloat("shadow fade start", "Shadows", cFadeStart);
bool cActorShadows = actorShadowsCheckBox->checkState();
bool cObjectShadows = objectShadowsCheckBox->checkState();
bool cTerrainShadows = terrainShadowsCheckBox->checkState();
bool cPlayerShadows = playerShadowsCheckBox->checkState();
const int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0;
Settings::shadows().mMaximumShadowMapDistance.set(cShadowDist);
const float cFadeStart = fadeStartSpinBox->value();
if (cShadowDist > 0)
Settings::shadows().mShadowFadeStart.set(cFadeStart);
const bool cActorShadows = actorShadowsCheckBox->checkState() != Qt::Unchecked;
const bool cObjectShadows = objectShadowsCheckBox->checkState() != Qt::Unchecked;
const bool cTerrainShadows = terrainShadowsCheckBox->checkState() != Qt::Unchecked;
const bool cPlayerShadows = playerShadowsCheckBox->checkState() != Qt::Unchecked;
if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows)
{
if (!Settings::Manager::getBool("enable shadows", "Shadows"))
Settings::Manager::setBool("enable shadows", "Shadows", true);
if (Settings::Manager::getBool("actor shadows", "Shadows") != cActorShadows)
Settings::Manager::setBool("actor shadows", "Shadows", cActorShadows);
if (Settings::Manager::getBool("player shadows", "Shadows") != cPlayerShadows)
Settings::Manager::setBool("player shadows", "Shadows", cPlayerShadows);
if (Settings::Manager::getBool("object shadows", "Shadows") != cObjectShadows)
Settings::Manager::setBool("object shadows", "Shadows", cObjectShadows);
if (Settings::Manager::getBool("terrain shadows", "Shadows") != cTerrainShadows)
Settings::Manager::setBool("terrain shadows", "Shadows", cTerrainShadows);
Settings::shadows().mEnableShadows.set(true);
Settings::shadows().mActorShadows.set(cActorShadows);
Settings::shadows().mPlayerShadows.set(cPlayerShadows);
Settings::shadows().mObjectShadows.set(cObjectShadows);
Settings::shadows().mTerrainShadows.set(cTerrainShadows);
}
else
{
if (Settings::Manager::getBool("enable shadows", "Shadows"))
Settings::Manager::setBool("enable shadows", "Shadows", false);
if (Settings::Manager::getBool("actor shadows", "Shadows"))
Settings::Manager::setBool("actor shadows", "Shadows", false);
if (Settings::Manager::getBool("player shadows", "Shadows"))
Settings::Manager::setBool("player shadows", "Shadows", false);
if (Settings::Manager::getBool("object shadows", "Shadows"))
Settings::Manager::setBool("object shadows", "Shadows", false);
if (Settings::Manager::getBool("terrain shadows", "Shadows"))
Settings::Manager::setBool("terrain shadows", "Shadows", false);
Settings::shadows().mEnableShadows.set(false);
Settings::shadows().mActorShadows.set(false);
Settings::shadows().mPlayerShadows.set(false);
Settings::shadows().mObjectShadows.set(false);
Settings::shadows().mTerrainShadows.set(false);
}
bool cIndoorShadows = indoorShadowsCheckBox->checkState();
if (Settings::Manager::getBool("enable indoor shadows", "Shadows") != cIndoorShadows)
Settings::Manager::setBool("enable indoor shadows", "Shadows", cIndoorShadows);
Settings::shadows().mEnableIndoorShadows.set(indoorShadowsCheckBox->checkState() != Qt::Unchecked);
Settings::shadows().mShadowMapResolution.set(shadowResolutionComboBox->currentText().toInt());
int cShadowRes = shadowResolutionComboBox->currentText().toInt();
if (cShadowRes != Settings::Manager::getInt("shadow map resolution", "Shadows"))
Settings::Manager::setInt("shadow map resolution", "Shadows", cShadowRes);
auto cComputeSceneBounds = shadowComputeSceneBoundsComboBox->currentText().toStdString();
if (cComputeSceneBounds != Settings::Manager::getString("compute scene bounds", "Shadows"))
Settings::Manager::setString("compute scene bounds", "Shadows", cComputeSceneBounds);
auto index = shadowComputeSceneBoundsComboBox->currentIndex();
if (index == 0)
Settings::shadows().mComputeSceneBounds.set("bounds");
else if (index == 1)
Settings::shadows().mComputeSceneBounds.set("primitives");
else
Settings::shadows().mComputeSceneBounds.set("none");
}
QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
@ -347,19 +301,8 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
return result;
}
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 + ")");
}
else if (aspect == QLatin1String("4:3"))
{
resolution.append(tr("\t(Standard 4:3)"));
}
result.append(resolution);
auto str = Misc::getResolutionText(mode.w, mode.h, "%i × %i (%i:%i)");
result.append(QString(str.c_str()));
}
result.removeDuplicates();
@ -392,8 +335,12 @@ void Launcher::GraphicsPage::screenChanged(int screen)
void Launcher::GraphicsPage::slotFullScreenChanged(int mode)
{
if (mode == static_cast<int>(Settings::WindowMode::Fullscreen)
|| mode == static_cast<int>(Settings::WindowMode::WindowedFullscreen))
handleWindowModeChange(static_cast<Settings::WindowMode>(mode));
}
void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode)
{
if (mode == Settings::WindowMode::Fullscreen || mode == Settings::WindowMode::WindowedFullscreen)
{
standardRadioButton->toggle();
customRadioButton->setEnabled(false);

@ -3,7 +3,7 @@
#include "ui_graphicspage.h"
#include <components/settings/settings.hpp>
#include <components/settings/windowmode.hpp>
namespace Files
{
@ -40,6 +40,7 @@ namespace Launcher
static QRect getMaximumResolution();
bool setupSDL();
void handleWindowModeChange(Settings::WindowMode state);
};
}
#endif

@ -104,9 +104,9 @@ void Launcher::ImportPage::on_importerButton_clicked()
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(
tr("<html><head/><body><p><b>Could not open or create %1 for writing </b></p> \
<p>Please make sure you have the right permissions \
and try again.</p></body></html>")
tr("<html><head/><body><p><b>Could not open or create %1 for writing </b></p>"
"<p>Please make sure you have the right permissions "
"and try again.</p></body></html>")
.arg(file.fileName()));
msgBox.exec();
return;

@ -1,13 +1,5 @@
#include "maindialog.hpp"
#include <components/debug/debuglog.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/conversion.hpp>
#include <components/files/qtconversion.hpp>
#include <components/misc/helpviewer.hpp>
#include <components/misc/utf8qtextstream.hpp>
#include <components/version/version.hpp>
#include <QCloseEvent>
#include <QDir>
#include <QMessageBox>
@ -15,10 +7,15 @@
#include <QTime>
#include <components/debug/debugging.hpp>
#include <components/debug/debuglog.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/conversion.hpp>
#include <components/files/qtconfigpath.hpp>
#include <components/files/qtconversion.hpp>
#include <components/misc/helpviewer.hpp>
#include <components/misc/utf8qtextstream.hpp>
#include <components/settings/settings.hpp>
#include <components/version/version.hpp>
#include "datafilespage.hpp"
#include "graphicspage.hpp"
@ -121,13 +118,14 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog()
const auto& userConfigDir = mCfgMgr.getUserConfigPath();
if (!exists(userConfigDir))
{
if (!create_directories(userConfigDir))
std::error_code ec;
if (!create_directories(userConfigDir, ec))
{
cfgError(tr("Error opening OpenMW configuration file"),
tr("<br><b>Could not create directory %0</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>")
.arg(Files::pathToQString(canonical(userConfigDir))));
cfgError(tr("Error creating OpenMW configuration directory: code %0").arg(ec.value()),
tr("<br><b>Could not create directory %0</b><br><br>"
"%1<br>")
.arg(Files::pathToQString(userConfigDir))
.arg(QString(ec.message().c_str())));
return FirstRunDialogResultFailure;
}
}
@ -139,10 +137,10 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog()
msgBox.setIcon(QMessageBox::Question);
msgBox.setStandardButtons(QMessageBox::NoButton);
msgBox.setText(
tr("<html><head/><body><p><b>Welcome to OpenMW!</b></p> \
<p>It is recommended to run the Installation Wizard.</p> \
<p>The Wizard will let you select an existing Morrowind installation, \
or install Morrowind for OpenMW to use.</p></body></html>"));
tr("<html><head/><body><p><b>Welcome to OpenMW!</b></p>"
"<p>It is recommended to run the Installation Wizard.</p>"
"<p>The Wizard will let you select an existing Morrowind installation, "
"or install Morrowind for OpenMW to use.</p></body></html>"));
QAbstractButton* wizardButton
= msgBox.addButton(tr("Run &Installation Wizard"), QMessageBox::AcceptRole); // ActionRole doesn't work?!
@ -300,9 +298,9 @@ bool Launcher::MainDialog::setupLauncherSettings()
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
cfgError(tr("Error opening OpenMW configuration file"),
tr("<br><b>Could not open %0 for reading:</b><br><br>%1<br><br> \
Please make sure you have the right permissions \
and try again.<br>")
tr("<br><b>Could not open %0 for reading:</b><br><br>%1<br><br>"
"Please make sure you have the right permissions "
"and try again.<br>")
.arg(file.fileName())
.arg(file.errorString()));
return false;
@ -330,9 +328,9 @@ bool Launcher::MainDialog::setupGameSettings()
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
cfgError(tr("Error opening OpenMW configuration file"),
tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>")
tr("<br><b>Could not open %0 for reading</b><br><br>"
"Please make sure you have the right permissions "
"and try again.<br>")
.arg(file.fileName()));
return {};
}
@ -391,8 +389,8 @@ bool Launcher::MainDialog::setupGameData()
msgBox.setIcon(QMessageBox::Warning);
msgBox.setStandardButtons(QMessageBox::NoButton);
msgBox.setText(
tr("<br><b>Could not find the Data Files location</b><br><br> \
The directory containing the data files was not found."));
tr("<br><b>Could not find the Data Files location</b><br><br>"
"The directory containing the data files was not found."));
QAbstractButton* wizardButton = msgBox.addButton(tr("Run &Installation Wizard..."), QMessageBox::ActionRole);
QAbstractButton* skipButton = msgBox.addButton(tr("Skip"), QMessageBox::RejectRole);
@ -422,8 +420,8 @@ bool Launcher::MainDialog::setupGraphicsSettings()
catch (std::exception& e)
{
cfgError(tr("Error reading OpenMW configuration files"),
tr("<br>The problem may be due to an incomplete installation of OpenMW.<br> \
Reinstalling OpenMW may resolve the problem.<br>")
tr("<br>The problem may be due to an incomplete installation of OpenMW.<br>"
"Reinstalling OpenMW may resolve the problem.<br>")
+ e.what());
return false;
}
@ -460,13 +458,14 @@ bool Launcher::MainDialog::writeSettings()
if (!exists(userPath))
{
if (!create_directories(userPath))
std::error_code ec;
if (!create_directories(userPath, ec))
{
cfgError(tr("Error creating OpenMW configuration directory"),
tr("<br><b>Could not create %0</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>")
.arg(Files::pathToQString(userPath)));
cfgError(tr("Error creating OpenMW configuration directory: code %0").arg(ec.value()),
tr("<br><b>Could not create directory %0</b><br><br>"
"%1<br>")
.arg(Files::pathToQString(userPath))
.arg(QString(ec.message().c_str())));
return false;
}
}
@ -482,9 +481,9 @@ bool Launcher::MainDialog::writeSettings()
{
// File cannot be opened or created
cfgError(tr("Error writing OpenMW configuration file"),
tr("<br><b>Could not open or create %0 for writing</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>")
tr("<br><b>Could not open or create %0 for writing</b><br><br>"
"Please make sure you have the right permissions "
"and try again.<br>")
.arg(file.fileName()));
return false;
}
@ -513,9 +512,9 @@ bool Launcher::MainDialog::writeSettings()
{
// File cannot be opened or created
cfgError(tr("Error writing Launcher configuration file"),
tr("<br><b>Could not open or create %0 for writing</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>")
tr("<br><b>Could not open or create %0 for writing</b><br><br>"
"Please make sure you have the right permissions "
"and try again.<br>")
.arg(file.fileName()));
return false;
}
@ -565,8 +564,8 @@ void Launcher::MainDialog::play()
msgBox.setIcon(QMessageBox::Warning);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(
tr("<br><b>You do not have a game file selected.</b><br><br> \
OpenMW will not start without a game file selected.<br>"));
tr("<br><b>You do not have a game file selected.</b><br><br>"
"OpenMW will not start without a game file selected.<br>"));
msgBox.exec();
return;
}

@ -191,6 +191,7 @@ bool Launcher::SettingsPage::loadSettings()
}
loadSettingBool(Settings::game().mTurnToMovementDirection, *turnToMovementDirectionCheckBox);
loadSettingBool(Settings::game().mSmoothMovement, *smoothMovementCheckBox);
loadSettingBool(Settings::game().mPlayerMovementIgnoresAnimation, *playerMovementIgnoresAnimationCheckBox);
distantLandCheckBox->setCheckState(
Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging ? Qt::Checked : Qt::Unchecked);
@ -338,6 +339,7 @@ void Launcher::SettingsPage::saveSettings()
saveSettingBool(*shieldSheathingCheckBox, Settings::game().mShieldSheathing);
saveSettingBool(*turnToMovementDirectionCheckBox, Settings::game().mTurnToMovementDirection);
saveSettingBool(*smoothMovementCheckBox, Settings::game().mSmoothMovement);
saveSettingBool(*playerMovementIgnoresAnimationCheckBox, Settings::game().mPlayerMovementIgnoresAnimation);
const bool wantDistantLand = distantLandCheckBox->checkState() == Qt::Checked;
if (wantDistantLand != (Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging))

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>571</width>
<width>573</width>
<height>384</height>
</rect>
</property>
@ -30,7 +30,7 @@
<item row="1" column="0">
<widget class="QLabel" name="dataNoteLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;note: content files that are not part of current Content List are &lt;/span&gt;&lt;span style=&quot; font-style:italic; background-color:#00ff00;&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;note: content files that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
@ -57,7 +57,7 @@
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;note: directories that are not part of current Content List are &lt;/span&gt;&lt;span style=&quot; font-style:italic; background-color:#00ff00;&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;note: directories that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
@ -210,7 +210,7 @@
<item row="27" column="0" colspan="2">
<widget class="QLabel" name="archiveNoteLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;note: archives that are not part of current Content List are &lt;/span&gt;&lt;span style=&quot; font-style:italic; background-color:#00ff00;&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;note: archives that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>

@ -22,7 +22,7 @@
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QGridLayout" name="gridLayout_4" columnstretch="1,0">
<layout class="QGridLayout" name="gridLayout_4" columnstretch="1,1">
<item row="5" column="0">
<widget class="QLabel" name="screenLabel">
<property name="text">
@ -107,7 +107,7 @@
<item>
<widget class="QLabel" name="multiplyLabel">
<property name="text">
<string> x </string>
<string> × </string>
</property>
</widget>
</item>

@ -14,7 +14,7 @@
<item>
<widget class="QTabWidget" name="AdvancedTabWidget">
<property name="currentIndex">
<number>0</number>
<number>1</number>
</property>
<widget class="QWidget" name="GameMechanics">
<attribute name="title">
@ -116,10 +116,10 @@
<item row="4" column="0">
<widget class="QCheckBox" name="enableNavigatorCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable navigator. When enabled background threads are started to build nav mesh for world geometry. Pathfinding system uses nav mesh to build paths. When disabled only pathgrid is used to build paths. Single-core CPU systems may have big performance impact on exiting interior location and moving across exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesnt know where to go when you stand behind that stone and casting a firebolt.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a name=&quot;docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0&quot;/&gt;When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Build nav mesh for world geometry</string>
<string>Use navigation mesh for pathfinding</string>
</property>
</widget>
</item>
@ -293,8 +293,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>680</width>
<height>882</height>
<width>671</width>
<height>774</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@ -377,6 +377,16 @@
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="playerMovementIgnoresAnimationCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Player movement ignores animation</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

@ -164,12 +164,12 @@ namespace NavMeshTool
config.filterOutNonExistingPaths(dataDirs);
const auto resDir = variables["resources"].as<Files::MaybeQuotedPath>();
const auto& resDir = variables["resources"].as<Files::MaybeQuotedPath>();
Log(Debug::Info) << Version::getOpenmwVersionDescription();
dataDirs.insert(dataDirs.begin(), resDir / "vfs");
const auto fileCollections = Files::Collections(dataDirs);
const auto archives = variables["fallback-archive"].as<StringsVector>();
const auto contentFiles = variables["content"].as<StringsVector>();
const Files::Collections fileCollections(dataDirs);
const auto& archives = variables["fallback-archive"].as<StringsVector>();
const auto& contentFiles = variables["content"].as<StringsVector>();
const std::size_t threadsNumber = variables["threads"].as<std::size_t>();
if (threadsNumber < 1)

@ -131,7 +131,7 @@ namespace NavMeshTool
osg::ref_ptr<const Resource::BulletShape> shape = [&] {
try
{
return bulletShapeManager.getShape(Misc::ResourceHelpers::correctMeshPath(model, &vfs));
return bulletShapeManager.getShape(Misc::ResourceHelpers::correctMeshPath(model));
}
catch (const std::exception& e)
{

@ -45,9 +45,6 @@ std::unique_ptr<VFS::Archive> makeBsaArchive(const std::filesystem::path& path)
{
switch (Bsa::BSAFile::detectVersion(path))
{
case Bsa::BSAVER_UNKNOWN:
std::cerr << '"' << path << "\" is unknown BSA archive" << std::endl;
return nullptr;
case Bsa::BSAVER_COMPRESSED:
return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_COMPRESSED>::type>(path);
case Bsa::BSAVER_BA2_GNRL:
@ -56,11 +53,11 @@ std::unique_ptr<VFS::Archive> makeBsaArchive(const std::filesystem::path& path)
return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_BA2_DX10>::type>(path);
case Bsa::BSAVER_UNCOMPRESSED:
return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_UNCOMPRESSED>::type>(path);
case Bsa::BSAVER_UNKNOWN:
default:
std::cerr << "'" << Files::pathToUnicodeString(path) << "' is not a recognized BSA archive" << std::endl;
return nullptr;
}
std::cerr << '"' << path << "\" is unsupported BSA archive" << std::endl;
return nullptr;
}
std::unique_ptr<VFS::Archive> makeArchive(const std::filesystem::path& path)
@ -72,58 +69,86 @@ std::unique_ptr<VFS::Archive> makeArchive(const std::filesystem::path& path)
return nullptr;
}
void readNIF(
const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet)
{
const std::string pathStr = Files::pathToUnicodeString(path);
if (!quiet)
{
std::cout << "Reading NIF file '" << pathStr << "'";
if (!source.empty())
std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'";
std::cout << std::endl;
}
std::filesystem::path fullPath = !source.empty() ? source / path : path;
try
{
Nif::NIFFile file(fullPath);
Nif::Reader reader(file);
if (vfs != nullptr)
reader.parse(vfs->get(pathStr));
else
reader.parse(Files::openConstrainedFileStream(fullPath));
}
catch (std::exception& e)
{
std::cerr << "Failed to read '" << pathStr << "':" << std::endl << e.what() << std::endl;
}
}
/// Check all the nif files in a given VFS::Archive
/// \note Can not read a bsa file inside of a bsa file.
void readVFS(std::unique_ptr<VFS::Archive>&& anArchive, const std::filesystem::path& archivePath = {})
void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::path& archivePath, bool quiet)
{
if (anArchive == nullptr)
if (archive == nullptr)
return;
VFS::Manager myManager;
myManager.addArchive(std::move(anArchive));
myManager.buildIndex();
if (!quiet)
std::cout << "Reading data source '" << Files::pathToUnicodeString(archivePath) << "'" << std::endl;
VFS::Manager vfs;
vfs.addArchive(std::move(archive));
vfs.buildIndex();
for (const auto& name : myManager.getRecursiveDirectoryIterator(""))
for (const auto& name : vfs.getRecursiveDirectoryIterator(""))
{
try
if (isNIF(name))
{
if (isNIF(name))
{
// std::cout << "Decoding: " << name << std::endl;
Nif::NIFFile file(archivePath / name);
Nif::Reader reader(file);
reader.parse(myManager.get(name));
}
else if (isBSA(name))
{
if (!archivePath.empty() && !isBSA(archivePath))
{
// std::cout << "Reading BSA File: " << name << std::endl;
readVFS(makeBsaArchive(archivePath / name), archivePath / name);
// std::cout << "Done with BSA File: " << name << std::endl;
}
}
readNIF(archivePath, name, &vfs, quiet);
}
catch (std::exception& e)
}
if (!archivePath.empty() && !isBSA(archivePath))
{
Files::PathContainer dataDirs = { archivePath };
const Files::Collections fileCollections = Files::Collections(dataDirs);
const Files::MultiDirCollection& bsaCol = fileCollections.getCollection(".bsa");
const Files::MultiDirCollection& ba2Col = fileCollections.getCollection(".ba2");
for (auto& file : bsaCol)
{
readVFS(makeBsaArchive(file.second), file.second, quiet);
}
for (auto& file : ba2Col)
{
std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl;
readVFS(makeBsaArchive(file.second), file.second, quiet);
}
}
}
bool parseOptions(int argc, char** argv, std::vector<Files::MaybeQuotedPath>& files, bool& writeDebugLog,
std::vector<Files::MaybeQuotedPath>& archives)
bool parseOptions(int argc, char** argv, Files::PathContainer& files, Files::PathContainer& archives,
bool& writeDebugLog, bool& quiet)
{
bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF and BSA files
Usages:
niftool <nif files, BSA files, or directories>
Scan the file or directories for nif errors.
niftest <nif files, BSA files, or directories>
Scan the file or directories for NIF errors.
Allowed options)");
auto addOption = desc.add_options();
addOption("help,h", "print help message.");
addOption("write-debug-log,v", "write debug log for unsupported nif files");
addOption("quiet,q", "do not log read archives/files");
addOption("archives", bpo::value<Files::MaybeQuotedPathContainer>(), "path to archive files to provide files");
addOption("input-file", bpo::value<Files::MaybeQuotedPathContainer>(), "input file");
@ -143,17 +168,18 @@ Allowed options)");
return false;
}
writeDebugLog = variables.count("write-debug-log") > 0;
quiet = variables.count("quiet") > 0;
if (variables.count("input-file"))
{
files = variables["input-file"].as<Files::MaybeQuotedPathContainer>();
files = asPathContainer(variables["input-file"].as<Files::MaybeQuotedPathContainer>());
if (const auto it = variables.find("archives"); it != variables.end())
archives = it->second.as<Files::MaybeQuotedPathContainer>();
archives = asPathContainer(it->second.as<Files::MaybeQuotedPathContainer>());
return true;
}
}
catch (std::exception& e)
{
std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" << desc << std::endl;
std::cout << "Error parsing arguments: " << e.what() << "\n\n" << desc << std::endl;
return false;
}
@ -164,64 +190,62 @@ Allowed options)");
int main(int argc, char** argv)
{
std::vector<Files::MaybeQuotedPath> files;
Files::PathContainer files, sources;
bool writeDebugLog = false;
std::vector<Files::MaybeQuotedPath> archives;
if (!parseOptions(argc, argv, files, writeDebugLog, archives))
bool quiet = false;
if (!parseOptions(argc, argv, files, sources, writeDebugLog, quiet))
return 1;
Nif::Reader::setLoadUnsupportedFiles(true);
Nif::Reader::setWriteNifDebugLog(writeDebugLog);
std::unique_ptr<VFS::Manager> vfs;
if (!archives.empty())
if (!sources.empty())
{
vfs = std::make_unique<VFS::Manager>();
for (const std::filesystem::path& path : archives)
for (const std::filesystem::path& path : sources)
{
const std::string pathStr = Files::pathToUnicodeString(path);
if (!quiet)
std::cout << "Adding data source '" << pathStr << "'" << std::endl;
try
{
if (auto archive = makeArchive(path))
vfs->addArchive(std::move(archive));
else
std::cerr << '"' << path << "\" is unsupported archive" << std::endl;
vfs->buildIndex();
std::cerr << "Error: '" << pathStr << "' is not an archive or directory" << std::endl;
}
catch (std::exception& e)
{
std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl;
std::cerr << "Failed to add data source '" << pathStr << "': " << e.what() << std::endl;
}
}
vfs->buildIndex();
}
// std::cout << "Reading Files" << std::endl;
for (const auto& path : files)
{
const std::string pathStr = Files::pathToUnicodeString(path);
try
{
if (isNIF(path))
{
// std::cout << "Decoding: " << name << std::endl;
Nif::NIFFile file(path);
Nif::Reader reader(file);
if (vfs != nullptr)
reader.parse(vfs->get(Files::pathToUnicodeString(path)));
else
reader.parse(Files::openConstrainedFileStream(path));
readNIF({}, path, vfs.get(), quiet);
}
else if (auto archive = makeArchive(path))
{
readVFS(std::move(archive), path);
readVFS(std::move(archive), path, quiet);
}
else
{
std::cerr << "ERROR: \"" << Files::pathToUnicodeString(path)
<< "\" is not a nif file, bsa/ba2 file, or directory!" << std::endl;
std::cerr << "Error: '" << pathStr << "' is not a NIF file, BSA/BA2 archive, or directory" << std::endl;
}
}
catch (std::exception& e)
{
std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl;
std::cerr << "Failed to read '" << pathStr << "': " << e.what() << std::endl;
}
}
return 0;

@ -116,7 +116,7 @@ opencs_units (view/prefs
opencs_units (model/prefs
state setting intsetting doublesetting boolsetting enumsetting coloursetting shortcut
shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting
shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting subcategory
)
opencs_units (model/prefs
@ -139,8 +139,7 @@ set (OPENCS_RES ${CMAKE_SOURCE_DIR}/files/opencs/resources.qrc
)
set (OPENCS_UI
${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
${CMAKE_SOURCE_DIR}/files/ui/filedialog.ui
${CMAKE_CURRENT_SOURCE_DIR}/ui/filedialog.ui
)
source_group (openmw-cs FILES main.cpp ${OPENCS_SRC} ${OPENCS_HDR})

@ -200,6 +200,8 @@ std::pair<Files::PathContainer, std::vector<std::string>> CS::Editor::readConfig
dataDirs.insert(dataDirs.end(), dataLocal.begin(), dataLocal.end());
dataDirs.insert(dataDirs.begin(), mResources / "vfs");
// iterate the data directories and add them to the file dialog for loading
mFileDialog.addFiles(dataDirs);

@ -25,6 +25,7 @@
#include <components/esm3/loadsoun.hpp>
#include <components/esm3/loadspel.hpp>
#include <components/esm3/loadsscr.hpp>
#include <components/esm3/selectiongroup.hpp>
#include "../world/data.hpp"
#include "../world/idcollection.hpp"
@ -52,6 +53,9 @@ CSMDoc::Saving::Saving(Document& document, const std::filesystem::path& projectP
appendStage(new WriteCollectionStage<CSMWorld::IdCollection<ESM::Script>>(
mDocument.getData().getScripts(), mState, CSMWorld::Scope_Project));
appendStage(new WriteCollectionStage<CSMWorld::IdCollection<ESM::SelectionGroup>>(
mDocument.getData().getSelectionGroups(), mState, CSMWorld::Scope_Project));
appendStage(new CloseSaveStage(mState));
// save content file

@ -11,9 +11,8 @@
#include "state.hpp"
CSMPrefs::BoolSetting::BoolSetting(
Category* parent, QMutex* mutex, const std::string& key, const std::string& label, bool default_)
: Setting(parent, mutex, key, label)
, mDefault(default_)
Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index)
: TypedSetting(parent, mutex, key, label, index)
, mWidget(nullptr)
{
}
@ -24,10 +23,10 @@ CSMPrefs::BoolSetting& CSMPrefs::BoolSetting::setTooltip(const std::string& tool
return *this;
}
std::pair<QWidget*, QWidget*> CSMPrefs::BoolSetting::makeWidgets(QWidget* parent)
CSMPrefs::SettingWidgets CSMPrefs::BoolSetting::makeWidgets(QWidget* parent)
{
mWidget = new QCheckBox(QString::fromUtf8(getLabel().c_str()), parent);
mWidget->setCheckState(mDefault ? Qt::Checked : Qt::Unchecked);
mWidget = new QCheckBox(getLabel(), parent);
mWidget->setCheckState(getValue() ? Qt::Checked : Qt::Unchecked);
if (!mTooltip.empty())
{
@ -37,24 +36,19 @@ std::pair<QWidget*, QWidget*> CSMPrefs::BoolSetting::makeWidgets(QWidget* parent
connect(mWidget, &QCheckBox::stateChanged, this, &BoolSetting::valueChanged);
return std::make_pair(static_cast<QWidget*>(nullptr), mWidget);
return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget };
}
void CSMPrefs::BoolSetting::updateWidget()
{
if (mWidget)
{
mWidget->setCheckState(
Settings::Manager::getBool(getKey(), getParent()->getKey()) ? Qt::Checked : Qt::Unchecked);
mWidget->setCheckState(getValue() ? Qt::Checked : Qt::Unchecked);
}
}
void CSMPrefs::BoolSetting::valueChanged(int value)
{
{
QMutexLocker lock(getMutex());
Settings::Manager::setBool(getKey(), getParent()->getKey(), value);
}
setValue(value != Qt::Unchecked);
getParent()->getState()->update(*this);
}

@ -12,21 +12,21 @@ namespace CSMPrefs
{
class Category;
class BoolSetting : public Setting
class BoolSetting final : public TypedSetting<bool>
{
Q_OBJECT
std::string mTooltip;
bool mDefault;
QCheckBox* mWidget;
public:
BoolSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label, bool default_);
explicit BoolSetting(
Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index);
BoolSetting& setTooltip(const std::string& tooltip);
/// Return label, input widget.
std::pair<QWidget*, QWidget*> makeWidgets(QWidget* parent) override;
SettingWidgets makeWidgets(QWidget* parent) override;
void updateWidget() override;

@ -5,6 +5,7 @@
#include "setting.hpp"
#include "state.hpp"
#include "subcategory.hpp"
CSMPrefs::Category::Category(State* parent, const std::string& key)
: mParent(parent)
@ -23,6 +24,14 @@ CSMPrefs::State* CSMPrefs::Category::getState() const
}
void CSMPrefs::Category::addSetting(Setting* setting)
{
if (!mIndex.emplace(setting->getKey(), setting).second)
throw std::logic_error("Category " + mKey + " already has setting: " + setting->getKey());
mSettings.push_back(setting);
}
void CSMPrefs::Category::addSubcategory(Subcategory* setting)
{
mSettings.push_back(setting);
}
@ -39,11 +48,12 @@ CSMPrefs::Category::Iterator CSMPrefs::Category::end()
CSMPrefs::Setting& CSMPrefs::Category::operator[](const std::string& key)
{
for (Iterator iter = mSettings.begin(); iter != mSettings.end(); ++iter)
if ((*iter)->getKey() == key)
return **iter;
const auto it = mIndex.find(key);
if (it != mIndex.end())
return *it->second;
throw std::logic_error("Invalid user setting: " + key);
throw std::logic_error("Invalid user setting in " + mKey + " category: " + key);
}
void CSMPrefs::Category::update()

@ -3,12 +3,14 @@
#include <algorithm>
#include <string>
#include <unordered_map>
#include <vector>
namespace CSMPrefs
{
class State;
class Setting;
class Subcategory;
class Category
{
@ -20,6 +22,7 @@ namespace CSMPrefs
State* mParent;
std::string mKey;
Container mSettings;
std::unordered_map<std::string, Setting*> mIndex;
public:
Category(State* parent, const std::string& key);
@ -30,6 +33,8 @@ namespace CSMPrefs
void addSetting(Setting* setting);
void addSubcategory(Subcategory* setting);
Iterator begin();
Iterator end();

@ -14,9 +14,8 @@
#include "state.hpp"
CSMPrefs::ColourSetting::ColourSetting(
Category* parent, QMutex* mutex, const std::string& key, const std::string& label, QColor default_)
: Setting(parent, mutex, key, label)
, mDefault(std::move(default_))
Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index)
: TypedSetting(parent, mutex, key, label, index)
, mWidget(nullptr)
{
}
@ -27,11 +26,11 @@ CSMPrefs::ColourSetting& CSMPrefs::ColourSetting::setTooltip(const std::string&
return *this;
}
std::pair<QWidget*, QWidget*> CSMPrefs::ColourSetting::makeWidgets(QWidget* parent)
CSMPrefs::SettingWidgets CSMPrefs::ColourSetting::makeWidgets(QWidget* parent)
{
QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent);
QLabel* label = new QLabel(getLabel(), parent);
mWidget = new CSVWidget::ColorEditor(mDefault, parent);
mWidget = new CSVWidget::ColorEditor(toColor(), parent);
if (!mTooltip.empty())
{
@ -42,24 +41,18 @@ std::pair<QWidget*, QWidget*> CSMPrefs::ColourSetting::makeWidgets(QWidget* pare
connect(mWidget, &CSVWidget::ColorEditor::pickingFinished, this, &ColourSetting::valueChanged);
return std::make_pair(label, mWidget);
return SettingWidgets{ .mLabel = label, .mInput = mWidget };
}
void CSMPrefs::ColourSetting::updateWidget()
{
if (mWidget)
{
mWidget->setColor(QString::fromStdString(Settings::Manager::getString(getKey(), getParent()->getKey())));
}
mWidget->setColor(toColor());
}
void CSMPrefs::ColourSetting::valueChanged()
{
CSVWidget::ColorEditor& widget = dynamic_cast<CSVWidget::ColorEditor&>(*sender());
{
QMutexLocker lock(getMutex());
Settings::Manager::setString(getKey(), getParent()->getKey(), widget.color().name().toUtf8().data());
}
setValue(widget.color().name().toStdString());
getParent()->getState()->update(*this);
}

@ -20,22 +20,22 @@ namespace CSVWidget
namespace CSMPrefs
{
class Category;
class ColourSetting : public Setting
class ColourSetting final : public TypedSetting<std::string>
{
Q_OBJECT
std::string mTooltip;
QColor mDefault;
CSVWidget::ColorEditor* mWidget;
public:
ColourSetting(
Category* parent, QMutex* mutex, const std::string& key, const std::string& label, QColor default_);
explicit ColourSetting(
Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index);
ColourSetting& setTooltip(const std::string& tooltip);
/// Return label, input widget.
std::pair<QWidget*, QWidget*> makeWidgets(QWidget* parent) override;
SettingWidgets makeWidgets(QWidget* parent) override;
void updateWidget() override;

@ -15,12 +15,11 @@
#include "state.hpp"
CSMPrefs::DoubleSetting::DoubleSetting(
Category* parent, QMutex* mutex, const std::string& key, const std::string& label, double default_)
: Setting(parent, mutex, key, label)
Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index)
: TypedSetting(parent, mutex, key, label, index)
, mPrecision(2)
, mMin(0)
, mMax(std::numeric_limits<double>::max())
, mDefault(default_)
, mWidget(nullptr)
{
}
@ -56,14 +55,14 @@ CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setTooltip(const std::string&
return *this;
}
std::pair<QWidget*, QWidget*> CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent)
CSMPrefs::SettingWidgets CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent)
{
QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent);
QLabel* label = new QLabel(getLabel(), parent);
mWidget = new QDoubleSpinBox(parent);
mWidget->setDecimals(mPrecision);
mWidget->setRange(mMin, mMax);
mWidget->setValue(mDefault);
mWidget->setValue(getValue());
if (!mTooltip.empty())
{
@ -74,23 +73,17 @@ std::pair<QWidget*, QWidget*> CSMPrefs::DoubleSetting::makeWidgets(QWidget* pare
connect(mWidget, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &DoubleSetting::valueChanged);
return std::make_pair(label, mWidget);
return SettingWidgets{ .mLabel = label, .mInput = mWidget };
}
void CSMPrefs::DoubleSetting::updateWidget()
{
if (mWidget)
{
mWidget->setValue(Settings::Manager::getFloat(getKey(), getParent()->getKey()));
}
mWidget->setValue(getValue());
}
void CSMPrefs::DoubleSetting::valueChanged(double value)
{
{
QMutexLocker lock(getMutex());
Settings::Manager::setFloat(getKey(), getParent()->getKey(), value);
}
setValue(value);
getParent()->getState()->update(*this);
}

@ -9,7 +9,7 @@ namespace CSMPrefs
{
class Category;
class DoubleSetting : public Setting
class DoubleSetting final : public TypedSetting<double>
{
Q_OBJECT
@ -17,12 +17,11 @@ namespace CSMPrefs
double mMin;
double mMax;
std::string mTooltip;
double mDefault;
QDoubleSpinBox* mWidget;
public:
DoubleSetting(
Category* parent, QMutex* mutex, const std::string& key, const std::string& label, double default_);
explicit DoubleSetting(
Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index);
DoubleSetting& setPrecision(int precision);
@ -36,7 +35,7 @@ namespace CSMPrefs
DoubleSetting& setTooltip(const std::string& tooltip);
/// Return label, input widget.
std::pair<QWidget*, QWidget*> makeWidgets(QWidget* parent) override;
SettingWidgets makeWidgets(QWidget* parent) override;
void updateWidget() override;

@ -15,39 +15,10 @@
#include "category.hpp"
#include "state.hpp"
CSMPrefs::EnumValue::EnumValue(const std::string& value, const std::string& tooltip)
: mValue(value)
, mTooltip(tooltip)
{
}
CSMPrefs::EnumValue::EnumValue(const char* value)
: mValue(value)
{
}
CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const EnumValues& values)
{
mValues.insert(mValues.end(), values.mValues.begin(), values.mValues.end());
return *this;
}
CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const EnumValue& value)
{
mValues.push_back(value);
return *this;
}
CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const std::string& value, const std::string& tooltip)
{
mValues.emplace_back(value, tooltip);
return *this;
}
CSMPrefs::EnumSetting::EnumSetting(
Category* parent, QMutex* mutex, const std::string& key, const std::string& label, const EnumValue& default_)
: Setting(parent, mutex, key, label)
, mDefault(default_)
CSMPrefs::EnumSetting::EnumSetting(Category* parent, QMutex* mutex, std::string_view key, const QString& label,
std::span<const EnumValueView> values, Settings::Index& index)
: TypedSetting(parent, mutex, key, label, index)
, mValues(values)
, mWidget(nullptr)
{
}
@ -58,43 +29,28 @@ CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::setTooltip(const std::string& tool
return *this;
}
CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValues(const EnumValues& values)
CSMPrefs::SettingWidgets CSMPrefs::EnumSetting::makeWidgets(QWidget* parent)
{
mValues.add(values);
return *this;
}
CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue(const EnumValue& value)
{
mValues.add(value);
return *this;
}
CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue(const std::string& value, const std::string& tooltip)
{
mValues.add(value, tooltip);
return *this;
}
std::pair<QWidget*, QWidget*> CSMPrefs::EnumSetting::makeWidgets(QWidget* parent)
{
QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent);
QLabel* label = new QLabel(getLabel(), parent);
mWidget = new QComboBox(parent);
size_t index = 0;
for (size_t i = 0; i < mValues.mValues.size(); ++i)
for (std::size_t i = 0; i < mValues.size(); ++i)
{
if (mDefault.mValue == mValues.mValues[i].mValue)
index = i;
const EnumValueView& v = mValues[i];
mWidget->addItem(QString::fromUtf8(mValues.mValues[i].mValue.c_str()));
mWidget->addItem(QString::fromUtf8(v.mValue.data(), static_cast<int>(v.mValue.size())));
if (!mValues.mValues[i].mTooltip.empty())
mWidget->setItemData(i, QString::fromUtf8(mValues.mValues[i].mTooltip.c_str()), Qt::ToolTipRole);
if (!v.mTooltip.empty())
mWidget->setItemData(static_cast<int>(i),
QString::fromUtf8(v.mTooltip.data(), static_cast<int>(v.mTooltip.size())), Qt::ToolTipRole);
}
const std::string value = getValue();
const std::size_t index = std::find_if(mValues.begin(), mValues.end(), [&](const EnumValueView& v) {
return v.mValue == value;
}) - mValues.begin();
mWidget->setCurrentIndex(static_cast<int>(index));
if (!mTooltip.empty())
@ -105,26 +61,20 @@ std::pair<QWidget*, QWidget*> CSMPrefs::EnumSetting::makeWidgets(QWidget* parent
connect(mWidget, qOverload<int>(&QComboBox::currentIndexChanged), this, &EnumSetting::valueChanged);
return std::make_pair(label, mWidget);
return SettingWidgets{ .mLabel = label, .mInput = mWidget };
}
void CSMPrefs::EnumSetting::updateWidget()
{
if (mWidget)
{
int index
= mWidget->findText(QString::fromStdString(Settings::Manager::getString(getKey(), getParent()->getKey())));
mWidget->setCurrentIndex(index);
}
mWidget->setCurrentIndex(mWidget->findText(QString::fromStdString(getValue())));
}
void CSMPrefs::EnumSetting::valueChanged(int value)
{
{
QMutexLocker lock(getMutex());
Settings::Manager::setString(getKey(), getParent()->getKey(), mValues.mValues.at(value).mValue);
}
if (value < 0 || static_cast<std::size_t>(value) >= mValues.size())
throw std::logic_error("Invalid enum setting \"" + getKey() + "\" value index: " + std::to_string(value));
setValue(std::string(mValues[value].mValue));
getParent()->getState()->update(*this);
}

@ -1,10 +1,13 @@
#ifndef CSM_PREFS_ENUMSETTING_H
#define CSM_PREFS_ENUMSETTING_H
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "enumvalueview.hpp"
#include "setting.hpp"
class QComboBox;
@ -13,50 +16,22 @@ namespace CSMPrefs
{
class Category;
struct EnumValue
{
std::string mValue;
std::string mTooltip;
EnumValue(const std::string& value, const std::string& tooltip = "");
EnumValue(const char* value);
};
struct EnumValues
{
std::vector<EnumValue> mValues;
EnumValues& add(const EnumValues& values);
EnumValues& add(const EnumValue& value);
EnumValues& add(const std::string& value, const std::string& tooltip);
};
class EnumSetting : public Setting
class EnumSetting final : public TypedSetting<std::string>
{
Q_OBJECT
std::string mTooltip;
EnumValue mDefault;
EnumValues mValues;
std::span<const EnumValueView> mValues;
QComboBox* mWidget;
public:
EnumSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label,
const EnumValue& default_);
explicit EnumSetting(Category* parent, QMutex* mutex, std::string_view key, const QString& label,
std::span<const EnumValueView> values, Settings::Index& index);
EnumSetting& setTooltip(const std::string& tooltip);
EnumSetting& addValues(const EnumValues& values);
EnumSetting& addValue(const EnumValue& value);
EnumSetting& addValue(const std::string& value, const std::string& tooltip);
/// Return label, input widget.
std::pair<QWidget*, QWidget*> makeWidgets(QWidget* parent) override;
SettingWidgets makeWidgets(QWidget* parent) override;
void updateWidget() override;

@ -0,0 +1,15 @@
#ifndef OPENMW_APPS_OPENCS_MODEL_PREFS_ENUMVALUEVIEW_H
#define OPENMW_APPS_OPENCS_MODEL_PREFS_ENUMVALUEVIEW_H
#include <string_view>
namespace CSMPrefs
{
struct EnumValueView
{
std::string_view mValue;
std::string_view mTooltip;
};
}
#endif

@ -15,11 +15,10 @@
#include "state.hpp"
CSMPrefs::IntSetting::IntSetting(
Category* parent, QMutex* mutex, const std::string& key, const std::string& label, int default_)
: Setting(parent, mutex, key, label)
Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index)
: TypedSetting(parent, mutex, key, label, index)
, mMin(0)
, mMax(std::numeric_limits<int>::max())
, mDefault(default_)
, mWidget(nullptr)
{
}
@ -49,13 +48,13 @@ CSMPrefs::IntSetting& CSMPrefs::IntSetting::setTooltip(const std::string& toolti
return *this;
}
std::pair<QWidget*, QWidget*> CSMPrefs::IntSetting::makeWidgets(QWidget* parent)
CSMPrefs::SettingWidgets CSMPrefs::IntSetting::makeWidgets(QWidget* parent)
{
QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent);
QLabel* label = new QLabel(getLabel(), parent);
mWidget = new QSpinBox(parent);
mWidget->setRange(mMin, mMax);
mWidget->setValue(mDefault);
mWidget->setValue(getValue());
if (!mTooltip.empty())
{
@ -66,23 +65,17 @@ std::pair<QWidget*, QWidget*> CSMPrefs::IntSetting::makeWidgets(QWidget* parent)
connect(mWidget, qOverload<int>(&QSpinBox::valueChanged), this, &IntSetting::valueChanged);
return std::make_pair(label, mWidget);
return SettingWidgets{ .mLabel = label, .mInput = mWidget };
}
void CSMPrefs::IntSetting::updateWidget()
{
if (mWidget)
{
mWidget->setValue(Settings::Manager::getInt(getKey(), getParent()->getKey()));
}
mWidget->setValue(getValue());
}
void CSMPrefs::IntSetting::valueChanged(int value)
{
{
QMutexLocker lock(getMutex());
Settings::Manager::setInt(getKey(), getParent()->getKey(), value);
}
setValue(value);
getParent()->getState()->update(*this);
}

@ -12,18 +12,18 @@ namespace CSMPrefs
{
class Category;
class IntSetting : public Setting
class IntSetting final : public TypedSetting<int>
{
Q_OBJECT
int mMin;
int mMax;
std::string mTooltip;
int mDefault;
QSpinBox* mWidget;
public:
IntSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label, int default_);
explicit IntSetting(
Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index);
// defaults to [0, std::numeric_limits<int>::max()]
IntSetting& setRange(int min, int max);
@ -35,7 +35,7 @@ namespace CSMPrefs
IntSetting& setTooltip(const std::string& tooltip);
/// Return label, input widget.
std::pair<QWidget*, QWidget*> makeWidgets(QWidget* parent) override;
SettingWidgets makeWidgets(QWidget* parent) override;
void updateWidget() override;

@ -19,21 +19,22 @@ class QWidget;
namespace CSMPrefs
{
ModifierSetting::ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label)
: Setting(parent, mutex, key, label)
ModifierSetting::ModifierSetting(
Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index)
: TypedSetting(parent, mutex, key, label, index)
, mButton(nullptr)
, mEditorActive(false)
{
}
std::pair<QWidget*, QWidget*> ModifierSetting::makeWidgets(QWidget* parent)
SettingWidgets ModifierSetting::makeWidgets(QWidget* parent)
{
int modifier = 0;
State::get().getShortcutManager().getModifier(getKey(), modifier);
QString text = QString::fromUtf8(State::get().getShortcutManager().convertToString(modifier).c_str());
QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent);
QLabel* label = new QLabel(getLabel(), parent);
QPushButton* widget = new QPushButton(text, parent);
widget->setCheckable(true);
@ -46,14 +47,14 @@ namespace CSMPrefs
connect(widget, &QPushButton::toggled, this, &ModifierSetting::buttonToggled);
return std::make_pair(label, widget);
return SettingWidgets{ .mLabel = label, .mInput = widget };
}
void ModifierSetting::updateWidget()
{
if (mButton)
{
const std::string& shortcut = Settings::Manager::getString(getKey(), getParent()->getKey());
const std::string& shortcut = getValue();
int modifier;
State::get().getShortcutManager().convertFromString(shortcut, modifier);
@ -131,15 +132,7 @@ namespace CSMPrefs
void ModifierSetting::storeValue(int modifier)
{
State::get().getShortcutManager().setModifier(getKey(), modifier);
// Convert to string and assign
std::string value = State::get().getShortcutManager().convertToString(modifier);
{
QMutexLocker lock(getMutex());
Settings::Manager::setString(getKey(), getParent()->getKey(), value);
}
setValue(State::get().getShortcutManager().convertToString(modifier));
getParent()->getState()->update(*this);
}

@ -15,14 +15,16 @@ class QPushButton;
namespace CSMPrefs
{
class Category;
class ModifierSetting : public Setting
class ModifierSetting final : public TypedSetting<std::string>
{
Q_OBJECT
public:
ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label);
explicit ModifierSetting(
Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index);
std::pair<QWidget*, QWidget*> makeWidgets(QWidget* parent) override;
SettingWidgets makeWidgets(QWidget* parent) override;
void updateWidget() override;

@ -5,6 +5,7 @@
#include <QMutexLocker>
#include <components/settings/settings.hpp>
#include <components/settings/settingvalue.hpp>
#include "category.hpp"
#include "state.hpp"
@ -14,22 +15,17 @@ QMutex* CSMPrefs::Setting::getMutex()
return mMutex;
}
CSMPrefs::Setting::Setting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label)
CSMPrefs::Setting::Setting(
Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index)
: QObject(parent->getState())
, mParent(parent)
, mMutex(mutex)
, mKey(key)
, mLabel(label)
, mIndex(index)
{
}
std::pair<QWidget*, QWidget*> CSMPrefs::Setting::makeWidgets(QWidget* parent)
{
return std::pair<QWidget*, QWidget*>(0, 0);
}
void CSMPrefs::Setting::updateWidget() {}
const CSMPrefs::Category* CSMPrefs::Setting::getParent() const
{
return mParent;
@ -40,35 +36,6 @@ const std::string& CSMPrefs::Setting::getKey() const
return mKey;
}
const std::string& CSMPrefs::Setting::getLabel() const
{
return mLabel;
}
int CSMPrefs::Setting::toInt() const
{
QMutexLocker lock(mMutex);
return Settings::Manager::getInt(mKey, mParent->getKey());
}
double CSMPrefs::Setting::toDouble() const
{
QMutexLocker lock(mMutex);
return Settings::Manager::getFloat(mKey, mParent->getKey());
}
std::string CSMPrefs::Setting::toString() const
{
QMutexLocker lock(mMutex);
return Settings::Manager::getString(mKey, mParent->getKey());
}
bool CSMPrefs::Setting::isTrue() const
{
QMutexLocker lock(mMutex);
return Settings::Manager::getBool(mKey, mParent->getKey());
}
QColor CSMPrefs::Setting::toColor() const
{
// toString() handles lock

@ -4,15 +4,26 @@
#include <string>
#include <utility>
#include <QMutexLocker>
#include <QObject>
#include <components/settings/settingvalue.hpp>
#include "category.hpp"
class QWidget;
class QColor;
class QMutex;
class QGridLayout;
class QLabel;
namespace CSMPrefs
{
class Category;
struct SettingWidgets
{
QLabel* mLabel;
QWidget* mInput;
};
class Setting : public QObject
{
@ -21,44 +32,82 @@ namespace CSMPrefs
Category* mParent;
QMutex* mMutex;
std::string mKey;
std::string mLabel;
QString mLabel;
Settings::Index& mIndex;
protected:
QMutex* getMutex();
template <class T>
void resetValueImpl()
{
QMutexLocker lock(mMutex);
return mIndex.get<T>(mParent->getKey(), mKey).reset();
}
template <class T>
T getValueImpl() const
{
QMutexLocker lock(mMutex);
return mIndex.get<T>(mParent->getKey(), mKey).get();
}
template <class T>
void setValueImpl(const T& value)
{
QMutexLocker lock(mMutex);
return mIndex.get<T>(mParent->getKey(), mKey).set(value);
}
public:
Setting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label);
explicit Setting(
Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index);
~Setting() override = default;
/// Return label, input widget.
///
/// \note first can be a 0-pointer, which means that the label is part of the input
/// widget.
virtual std::pair<QWidget*, QWidget*> makeWidgets(QWidget* parent);
virtual SettingWidgets makeWidgets(QWidget* parent) = 0;
/// Updates the widget returned by makeWidgets() to the current setting.
///
/// \note If make_widgets() has not been called yet then nothing happens.
virtual void updateWidget();
virtual void updateWidget() = 0;
virtual void reset() = 0;
const Category* getParent() const;
const std::string& getKey() const;
const std::string& getLabel() const;
const QString& getLabel() const { return mLabel; }
int toInt() const;
int toInt() const { return getValueImpl<int>(); }
double toDouble() const;
double toDouble() const { return getValueImpl<double>(); }
std::string toString() const;
std::string toString() const { return getValueImpl<std::string>(); }
bool isTrue() const;
bool isTrue() const { return getValueImpl<bool>(); }
QColor toColor() const;
};
template <class T>
class TypedSetting : public Setting
{
public:
using Setting::Setting;
void reset() final
{
resetValueImpl<T>();
updateWidget();
}
T getValue() const { return getValueImpl<T>(); }
void setValue(const T& value) { return setValueImpl(value); }
};
// note: fullKeys have the format categoryKey/settingKey
bool operator==(const Setting& setting, const std::string& fullKey);
bool operator==(const std::string& fullKey, const Setting& setting);

@ -91,7 +91,7 @@ namespace CSMPrefs
return false;
}
void ShortcutManager::setModifier(const std::string& name, int modifier)
void ShortcutManager::setModifier(std::string_view name, int modifier)
{
// Add to map/modify
ModifierMap::iterator item = mModifiers.find(name);

@ -32,7 +32,7 @@ namespace CSMPrefs
void setSequence(const std::string& name, const QKeySequence& sequence);
bool getModifier(const std::string& name, int& modifier) const;
void setModifier(const std::string& name, int modifier);
void setModifier(std::string_view name, int modifier);
std::string convertToString(const QKeySequence& sequence) const;
std::string convertToString(int modifier) const;
@ -49,9 +49,9 @@ namespace CSMPrefs
private:
// Need a multimap in case multiple shortcuts share the same name
typedef std::multimap<std::string, Shortcut*> ShortcutMap;
typedef std::multimap<std::string, Shortcut*, std::less<>> ShortcutMap;
typedef std::map<std::string, QKeySequence> SequenceMap;
typedef std::map<std::string, int> ModifierMap;
typedef std::map<std::string, int, std::less<>> ModifierMap;
typedef std::map<int, std::string> NameMap;
typedef std::map<std::string, int> KeyMap;

@ -18,8 +18,9 @@
namespace CSMPrefs
{
ShortcutSetting::ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label)
: Setting(parent, mutex, key, label)
ShortcutSetting::ShortcutSetting(
Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index)
: TypedSetting(parent, mutex, key, label, index)
, mButton(nullptr)
, mEditorActive(false)
, mEditorPos(0)
@ -30,14 +31,14 @@ namespace CSMPrefs
}
}
std::pair<QWidget*, QWidget*> ShortcutSetting::makeWidgets(QWidget* parent)
SettingWidgets ShortcutSetting::makeWidgets(QWidget* parent)
{
QKeySequence sequence;
State::get().getShortcutManager().getSequence(getKey(), sequence);
QString text = QString::fromUtf8(State::get().getShortcutManager().convertToString(sequence).c_str());
QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent);
QLabel* label = new QLabel(getLabel(), parent);
QPushButton* widget = new QPushButton(text, parent);
widget->setCheckable(true);
@ -50,14 +51,14 @@ namespace CSMPrefs
connect(widget, &QPushButton::toggled, this, &ShortcutSetting::buttonToggled);
return std::make_pair(label, widget);
return SettingWidgets{ .mLabel = label, .mInput = widget };
}
void ShortcutSetting::updateWidget()
{
if (mButton)
{
const std::string& shortcut = Settings::Manager::getString(getKey(), getParent()->getKey());
const std::string shortcut = getValue();
QKeySequence sequence;
State::get().getShortcutManager().convertFromString(shortcut, sequence);
@ -170,15 +171,7 @@ namespace CSMPrefs
void ShortcutSetting::storeValue(const QKeySequence& sequence)
{
State::get().getShortcutManager().setSequence(getKey(), sequence);
// Convert to string and assign
std::string value = State::get().getShortcutManager().convertToString(sequence);
{
QMutexLocker lock(getMutex());
Settings::Manager::setString(getKey(), getParent()->getKey(), value);
}
setValue(State::get().getShortcutManager().convertToString(sequence));
getParent()->getState()->update(*this);
}

@ -17,14 +17,16 @@ class QWidget;
namespace CSMPrefs
{
class Category;
class ShortcutSetting : public Setting
class ShortcutSetting final : public TypedSetting<std::string>
{
Q_OBJECT
public:
ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label);
explicit ShortcutSetting(
Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index);
std::pair<QWidget*, QWidget*> makeWidgets(QWidget* parent) override;
SettingWidgets makeWidgets(QWidget* parent) override;
void updateWidget() override;

@ -13,6 +13,7 @@
#include <apps/opencs/model/prefs/enumsetting.hpp>
#include <apps/opencs/model/prefs/setting.hpp>
#include <apps/opencs/model/prefs/shortcutmanager.hpp>
#include <apps/opencs/model/prefs/subcategory.hpp>
#include <components/settings/categories.hpp>
#include <components/settings/settings.hpp>
@ -24,50 +25,43 @@
#include "modifiersetting.hpp"
#include "shortcutsetting.hpp"
#include "stringsetting.hpp"
#include "values.hpp"
CSMPrefs::State* CSMPrefs::State::sThis = nullptr;
void CSMPrefs::State::declare()
{
declareCategory("Windows");
declareInt("default-width", "Default window width", 800)
declareInt(mValues->mWindows.mDefaultWidth, "Default window width")
.setTooltip("Newly opened top-level windows will open with this width.")
.setMin(80);
declareInt("default-height", "Default window height", 600)
declareInt(mValues->mWindows.mDefaultHeight, "Default window height")
.setTooltip("Newly opened top-level windows will open with this height.")
.setMin(80);
declareBool("show-statusbar", "Show Status Bar", true)
declareBool(mValues->mWindows.mShowStatusbar, "Show Status Bar")
.setTooltip(
"If a newly open top level window is showing status bars or not. "
" Note that this does not affect existing windows.");
declareSeparator();
declareBool("reuse", "Reuse Subviews", true)
declareBool(mValues->mWindows.mReuse, "Reuse Subviews")
.setTooltip(
"When a new subview is requested and a matching subview already "
" exist, do not open a new subview and use the existing one instead.");
declareInt("max-subviews", "Maximum number of subviews per top-level window", 256)
declareInt(mValues->mWindows.mMaxSubviews, "Maximum number of subviews per top-level window")
.setTooltip(
"If the maximum number is reached and a new subview is opened "
"it will be placed into a new top-level window.")
.setRange(1, 256);
declareBool("hide-subview", "Hide single subview", false)
declareBool(mValues->mWindows.mHideSubview, "Hide single subview")
.setTooltip(
"When a view contains only a single subview, hide the subview title "
"bar and if this subview is closed also close the view (unless it is the last "
"view for this document)");
declareInt("minimum-width", "Minimum subview width", 325)
declareInt(mValues->mWindows.mMinimumWidth, "Minimum subview width")
.setTooltip("Minimum width of subviews.")
.setRange(50, 10000);
declareSeparator();
EnumValue scrollbarOnly("Scrollbar Only",
"Simple addition of scrollbars, the view window "
"does not grow automatically.");
declareEnum("mainwindow-scrollbar", "Horizontal scrollbar mode for main window.", scrollbarOnly)
.addValue(scrollbarOnly)
.addValue("Grow Only", "The view window grows as subviews are added. No scrollbars.")
.addValue("Grow then Scroll", "The view window grows. The scrollbar appears once it cannot grow any further.");
declareEnum(mValues->mWindows.mMainwindowScrollbar, "Horizontal scrollbar mode for main window.");
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
declareBool("grow-limit", "Grow Limit Screen", false)
declareBool(mValues->mWindows.mGrowLimit, "Grow Limit Screen")
.setTooltip(
"When \"Grow then Scroll\" option is selected, the window size grows to"
" the width of the virtual desktop. \nIf this option is selected the the window growth"
@ -75,93 +69,64 @@ void CSMPrefs::State::declare()
#endif
declareCategory("Records");
EnumValue iconAndText("Icon and Text");
EnumValues recordValues;
recordValues.add(iconAndText).add("Icon Only").add("Text Only");
declareEnum("status-format", "Modification status display format", iconAndText).addValues(recordValues);
declareEnum("type-format", "ID type display format", iconAndText).addValues(recordValues);
declareEnum(mValues->mRecords.mStatusFormat, "Modification status display format");
declareEnum(mValues->mRecords.mTypeFormat, "ID type display format");
declareCategory("ID Tables");
EnumValue inPlaceEdit("Edit in Place", "Edit the clicked cell");
EnumValue editRecord("Edit Record", "Open a dialogue subview for the clicked record");
EnumValue view("View", "Open a scene subview for the clicked record (not available everywhere)");
EnumValue editRecordAndClose("Edit Record and Close");
EnumValues doubleClickValues;
doubleClickValues.add(inPlaceEdit)
.add(editRecord)
.add(view)
.add("Revert")
.add("Delete")
.add(editRecordAndClose)
.add("View and Close", "Open a scene subview for the clicked record and close the table subview");
declareEnum("double", "Double Click", inPlaceEdit).addValues(doubleClickValues);
declareEnum("double-s", "Shift Double Click", editRecord).addValues(doubleClickValues);
declareEnum("double-c", "Control Double Click", view).addValues(doubleClickValues);
declareEnum("double-sc", "Shift Control Double Click", editRecordAndClose).addValues(doubleClickValues);
declareSeparator();
EnumValue jumpAndSelect("Jump and Select", "Scroll new record into view and make it the selection");
declareEnum("jump-to-added", "Action on adding or cloning a record", jumpAndSelect)
.addValue(jumpAndSelect)
.addValue("Jump Only", "Scroll new record into view")
.addValue("No Jump", "No special action");
declareBool("extended-config", "Manually specify affected record types for an extended delete/revert", false)
declareEnum(mValues->mIdTables.mDouble, "Double Click");
declareEnum(mValues->mIdTables.mDoubleS, "Shift Double Click");
declareEnum(mValues->mIdTables.mDoubleC, "Control Double Click");
declareEnum(mValues->mIdTables.mDoubleSc, "Shift Control Double Click");
declareEnum(mValues->mIdTables.mJumpToAdded, "Action on adding or cloning a record");
declareBool(
mValues->mIdTables.mExtendedConfig, "Manually specify affected record types for an extended delete/revert")
.setTooltip(
"Delete and revert commands have an extended form that also affects "
"associated records.\n\n"
"If this option is enabled, types of affected records are selected "
"manually before a command execution.\nOtherwise, all associated "
"records are deleted/reverted immediately.");
declareBool("subview-new-window", "Open Record in new window", false)
declareBool(mValues->mIdTables.mSubviewNewWindow, "Open Record in new window")
.setTooltip(
"When editing a record, open the view in a new window,"
" rather than docked in the main view.");
declareCategory("ID Dialogues");
declareBool("toolbar", "Show toolbar", true);
declareBool(mValues->mIdDialogues.mToolbar, "Show toolbar");
declareCategory("Reports");
EnumValue actionNone("None");
EnumValue actionEdit("Edit", "Open a table or dialogue suitable for addressing the listed report");
EnumValue actionRemove("Remove", "Remove the report from the report table");
EnumValue actionEditAndRemove("Edit And Remove",
"Open a table or dialogue suitable for addressing the listed report, then remove the report from the report "
"table");
EnumValues reportValues;
reportValues.add(actionNone).add(actionEdit).add(actionRemove).add(actionEditAndRemove);
declareEnum("double", "Double Click", actionEdit).addValues(reportValues);
declareEnum("double-s", "Shift Double Click", actionRemove).addValues(reportValues);
declareEnum("double-c", "Control Double Click", actionEditAndRemove).addValues(reportValues);
declareEnum("double-sc", "Shift Control Double Click", actionNone).addValues(reportValues);
declareBool("ignore-base-records", "Ignore base records in verifier", false);
declareEnum(mValues->mReports.mDouble, "Double Click");
declareEnum(mValues->mReports.mDoubleS, "Shift Double Click");
declareEnum(mValues->mReports.mDoubleC, "Control Double Click");
declareEnum(mValues->mReports.mDoubleSc, "Shift Control Double Click");
declareBool(mValues->mReports.mIgnoreBaseRecords, "Ignore base records in verifier");
declareCategory("Search & Replace");
declareInt("char-before", "Characters before search string", 10)
declareInt(mValues->mSearchAndReplace.mCharBefore, "Characters before search string")
.setTooltip("Maximum number of character to display in search result before the searched text");
declareInt("char-after", "Characters after search string", 10)
declareInt(mValues->mSearchAndReplace.mCharAfter, "Characters after search string")
.setTooltip("Maximum number of character to display in search result after the searched text");
declareBool("auto-delete", "Delete row from result table after a successful replace", true);
declareBool(mValues->mSearchAndReplace.mAutoDelete, "Delete row from result table after a successful replace");
declareCategory("Scripts");
declareBool("show-linenum", "Show Line Numbers", true)
declareBool(mValues->mScripts.mShowLinenum, "Show Line Numbers")
.setTooltip(
"Show line numbers to the left of the script editor window."
"The current row and column numbers of the text cursor are shown at the bottom.");
declareBool("wrap-lines", "Wrap Lines", false).setTooltip("Wrap lines longer than width of script editor.");
declareBool("mono-font", "Use monospace font", true);
declareInt("tab-width", "Tab Width", 4).setTooltip("Number of characters for tab width").setRange(1, 10);
EnumValue warningsNormal("Normal", "Report warnings as warning");
declareEnum("warnings", "Warning Mode", warningsNormal)
.addValue("Ignore", "Do not report warning")
.addValue(warningsNormal)
.addValue("Strict", "Promote warning to an error");
declareBool("toolbar", "Show toolbar", true);
declareInt("compile-delay", "Delay between updating of source errors", 100)
declareBool(mValues->mScripts.mWrapLines, "Wrap Lines")
.setTooltip("Wrap lines longer than width of script editor.");
declareBool(mValues->mScripts.mMonoFont, "Use monospace font");
declareInt(mValues->mScripts.mTabWidth, "Tab Width")
.setTooltip("Number of characters for tab width")
.setRange(1, 10);
declareEnum(mValues->mScripts.mWarnings, "Warning Mode");
declareBool(mValues->mScripts.mToolbar, "Show toolbar");
declareInt(mValues->mScripts.mCompileDelay, "Delay between updating of source errors")
.setTooltip("Delay in milliseconds")
.setRange(0, 10000);
declareInt("error-height", "Initial height of the error panel", 100).setRange(100, 10000);
declareBool("highlight-occurrences", "Highlight other occurrences of selected names", true);
declareInt(mValues->mScripts.mErrorHeight, "Initial height of the error panel").setRange(100, 10000);
declareBool(mValues->mScripts.mHighlightOccurrences, "Highlight other occurrences of selected names");
declareColour("colour-highlight", "Colour of highlighted occurrences", QColor("lightcyan"));
declareSeparator();
declareColour("colour-int", "Highlight Colour: Integer Literals", QColor("darkmagenta"));
declareColour("colour-float", "Highlight Colour: Float Literals", QColor("magenta"));
declareColour("colour-name", "Highlight Colour: Names", QColor("grey"));
@ -171,51 +136,55 @@ void CSMPrefs::State::declare()
declareColour("colour-id", "Highlight Colour: IDs", QColor("blue"));
declareCategory("General Input");
declareBool("cycle", "Cyclic next/previous", false)
declareBool(mValues->mGeneralInput.mCycle, "Cyclic next/previous")
.setTooltip(
"When using next/previous functions at the last/first item of a "
"list go to the first/last item");
declareCategory("3D Scene Input");
declareDouble("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0);
declareDouble("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0);
declareSeparator();
declareDouble("p-navi-free-sensitivity", "Free Camera Sensitivity", 1 / 650.).setPrecision(5).setRange(0.0, 1.0);
declareBool("p-navi-free-invert", "Invert Free Camera Mouse Input", false);
declareDouble("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0);
declareDouble("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28);
declareDouble("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0);
declareSeparator();
declareDouble("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1 / 650.).setPrecision(5).setRange(0.0, 1.0);
declareBool("p-navi-orbit-invert", "Invert Orbit Camera Mouse Input", false);
declareDouble("navi-orbit-rot-speed", "Orbital Camera Rotational Speed", 3.14 / 4).setRange(0.001, 6.28);
declareDouble("navi-orbit-speed-mult", "Orbital Camera Speed Multiplier (from Modifier)", 4)
declareDouble(mValues->mSceneInput.mNaviWheelFactor, "Camera Zoom Sensitivity").setRange(-100.0, 100.0);
declareDouble(mValues->mSceneInput.mSNaviSensitivity, "Secondary Camera Movement Sensitivity")
.setRange(-1000.0, 1000.0);
declareDouble(mValues->mSceneInput.mPNaviFreeSensitivity, "Free Camera Sensitivity")
.setPrecision(5)
.setRange(0.0, 1.0);
declareBool(mValues->mSceneInput.mPNaviFreeInvert, "Invert Free Camera Mouse Input");
declareDouble(mValues->mSceneInput.mNaviFreeLinSpeed, "Free Camera Linear Speed").setRange(1.0, 10000.0);
declareDouble(mValues->mSceneInput.mNaviFreeRotSpeed, "Free Camera Rotational Speed").setRange(0.001, 6.28);
declareDouble(mValues->mSceneInput.mNaviFreeSpeedMult, "Free Camera Speed Multiplier (from Modifier)")
.setRange(0.001, 1000.0);
declareBool("navi-orbit-const-roll", "Keep camera roll constant for orbital camera", true);
declareSeparator();
declareBool("context-select", "Context Sensitive Selection", false);
declareDouble("drag-factor", "Mouse sensitivity during drag operations", 1.0).setRange(0.001, 100.0);
declareDouble("drag-wheel-factor", "Mouse wheel sensitivity during drag operations", 1.0).setRange(0.001, 100.0);
declareDouble("drag-shift-factor", "Shift-acceleration factor during drag operations", 4.0)
declareDouble(mValues->mSceneInput.mPNaviOrbitSensitivity, "Orbit Camera Sensitivity")
.setPrecision(5)
.setRange(0.0, 1.0);
declareBool(mValues->mSceneInput.mPNaviOrbitInvert, "Invert Orbit Camera Mouse Input");
declareDouble(mValues->mSceneInput.mNaviOrbitRotSpeed, "Orbital Camera Rotational Speed").setRange(0.001, 6.28);
declareDouble(mValues->mSceneInput.mNaviOrbitSpeedMult, "Orbital Camera Speed Multiplier (from Modifier)")
.setRange(0.001, 1000.0);
declareBool(mValues->mSceneInput.mNaviOrbitConstRoll, "Keep camera roll constant for orbital camera");
declareBool(mValues->mSceneInput.mContextSelect, "Context Sensitive Selection");
declareDouble(mValues->mSceneInput.mDragFactor, "Mouse sensitivity during drag operations").setRange(0.001, 100.0);
declareDouble(mValues->mSceneInput.mDragWheelFactor, "Mouse wheel sensitivity during drag operations")
.setRange(0.001, 100.0);
declareDouble(mValues->mSceneInput.mDragShiftFactor, "Shift-acceleration factor during drag operations")
.setTooltip("Acceleration factor during drag operations while holding down shift")
.setRange(0.001, 100.0);
declareDouble("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1);
declareDouble(mValues->mSceneInput.mRotateFactor, "Free rotation factor").setPrecision(4).setRange(0.0001, 0.1);
declareCategory("Rendering");
declareInt("framerate-limit", "FPS limit", 60)
declareInt(mValues->mRendering.mFramerateLimit, "FPS limit")
.setTooltip("Framerate limit in 3D preview windows. Zero value means \"unlimited\".")
.setRange(0, 10000);
declareInt("camera-fov", "Camera FOV", 90).setRange(10, 170);
declareBool("camera-ortho", "Orthographic projection for camera", false);
declareInt("camera-ortho-size", "Orthographic projection size parameter", 100)
declareInt(mValues->mRendering.mCameraFov, "Camera FOV").setRange(10, 170);
declareBool(mValues->mRendering.mCameraOrtho, "Orthographic projection for camera");
declareInt(mValues->mRendering.mCameraOrthoSize, "Orthographic projection size parameter")
.setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.")
.setRange(10, 10000);
declareDouble("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0, 1);
declareBool("scene-use-gradient", "Use Gradient Background", true);
declareDouble(mValues->mRendering.mObjectMarkerAlpha, "Object Marker Transparency").setPrecision(2).setRange(0, 1);
declareBool(mValues->mRendering.mSceneUseGradient, "Use Gradient Background");
declareColour("scene-day-background-colour", "Day Background Colour", QColor(110, 120, 128, 255));
declareColour("scene-day-gradient-colour", "Day Gradient Colour", QColor(47, 51, 51, 255))
.setTooltip(
@ -231,82 +200,52 @@ void CSMPrefs::State::declare()
.setTooltip(
"Sets the gradient color to use in conjunction with the night background color. Ignored if "
"the gradient option is disabled.");
declareBool("scene-day-night-switch-nodes", "Use Day/Night Switch Nodes", true);
declareBool(mValues->mRendering.mSceneDayNightSwitchNodes, "Use Day/Night Switch Nodes");
declareCategory("Tooltips");
declareBool("scene", "Show Tooltips in 3D scenes", true);
declareBool("scene-hide-basic", "Hide basic 3D scenes tooltips", false);
declareInt("scene-delay", "Tooltip delay in milliseconds", 500).setMin(1);
EnumValue createAndInsert("Create cell and insert");
EnumValue showAndInsert("Show cell and insert");
EnumValue dontInsert("Discard");
EnumValue insertAnyway("Insert anyway");
EnumValues insertOutsideCell;
insertOutsideCell.add(createAndInsert).add(dontInsert).add(insertAnyway);
EnumValues insertOutsideVisibleCell;
insertOutsideVisibleCell.add(showAndInsert).add(dontInsert).add(insertAnyway);
EnumValue createAndLandEdit("Create cell and land, then edit");
EnumValue showAndLandEdit("Show cell and edit");
EnumValue dontLandEdit("Discard");
EnumValues landeditOutsideCell;
landeditOutsideCell.add(createAndLandEdit).add(dontLandEdit);
EnumValues landeditOutsideVisibleCell;
landeditOutsideVisibleCell.add(showAndLandEdit).add(dontLandEdit);
EnumValue SelectOnly("Select only");
EnumValue SelectAdd("Add to selection");
EnumValue SelectRemove("Remove from selection");
EnumValue selectInvert("Invert selection");
EnumValues primarySelectAction;
primarySelectAction.add(SelectOnly).add(SelectAdd).add(SelectRemove).add(selectInvert);
EnumValues secondarySelectAction;
secondarySelectAction.add(SelectOnly).add(SelectAdd).add(SelectRemove).add(selectInvert);
declareBool(mValues->mTooltips.mScene, "Show Tooltips in 3D scenes");
declareBool(mValues->mTooltips.mSceneHideBasic, "Hide basic 3D scenes tooltips");
declareInt(mValues->mTooltips.mSceneDelay, "Tooltip delay in milliseconds").setMin(1);
declareCategory("3D Scene Editing");
declareDouble("gridsnap-movement", "Grid snap size", 16);
declareDouble("gridsnap-rotation", "Angle snap size", 15);
declareDouble("gridsnap-scale", "Scale snap size", 0.25);
declareInt("distance", "Drop Distance", 50)
declareDouble(mValues->mSceneEditing.mGridsnapMovement, "Grid snap size");
declareDouble(mValues->mSceneEditing.mGridsnapRotation, "Angle snap size");
declareDouble(mValues->mSceneEditing.mGridsnapScale, "Scale snap size");
declareInt(mValues->mSceneEditing.mDistance, "Drop Distance")
.setTooltip(
"If an instance drop can not be placed against another object at the "
"insert point, it will be placed by this distance from the insert point instead");
declareEnum("outside-drop", "Handling drops outside of cells", createAndInsert).addValues(insertOutsideCell);
declareEnum("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert)
.addValues(insertOutsideVisibleCell);
declareEnum("outside-landedit", "Handling terrain edit outside of cells", createAndLandEdit)
.setTooltip("Behavior of terrain editing, if land editing brush reaches an area without cell record.")
.addValues(landeditOutsideCell);
declareEnum("outside-visible-landedit", "Handling terrain edit outside of visible cells", showAndLandEdit)
.setTooltip("Behavior of terrain editing, if land editing brush reaches an area that is not currently visible.")
.addValues(landeditOutsideVisibleCell);
declareInt("texturebrush-maximumsize", "Maximum texture brush size", 50).setMin(1);
declareInt("shapebrush-maximumsize", "Maximum height edit brush size", 100)
declareEnum(mValues->mSceneEditing.mOutsideDrop, "Handling drops outside of cells");
declareEnum(mValues->mSceneEditing.mOutsideVisibleDrop, "Handling drops outside of visible cells");
declareEnum(mValues->mSceneEditing.mOutsideLandedit, "Handling terrain edit outside of cells")
.setTooltip("Behavior of terrain editing, if land editing brush reaches an area without cell record.");
declareEnum(mValues->mSceneEditing.mOutsideVisibleLandedit, "Handling terrain edit outside of visible cells")
.setTooltip(
"Behavior of terrain editing, if land editing brush reaches an area that is not currently visible.");
declareInt(mValues->mSceneEditing.mTexturebrushMaximumsize, "Maximum texture brush size").setMin(1);
declareInt(mValues->mSceneEditing.mShapebrushMaximumsize, "Maximum height edit brush size")
.setTooltip("Setting for the slider range of brush size in terrain height editing.")
.setMin(1);
declareBool("landedit-post-smoothpainting", "Smooth land after painting height", false)
declareBool(mValues->mSceneEditing.mLandeditPostSmoothpainting, "Smooth land after painting height")
.setTooltip("Raise and lower tools will leave bumpy finish without this option");
declareDouble("landedit-post-smoothstrength", "Smoothing strength (post-edit)", 0.25)
declareDouble(mValues->mSceneEditing.mLandeditPostSmoothstrength, "Smoothing strength (post-edit)")
.setTooltip(
"If smoothing land after painting height is used, this is the percentage of smooth applied afterwards. "
"Negative values may be used to roughen instead of smooth.")
.setMin(-1)
.setMax(1);
declareBool("open-list-view", "Open displays list view", false)
declareBool(mValues->mSceneEditing.mOpenListView, "Open displays list view")
.setTooltip(
"When opening a reference from the scene view, it will open the"
" instance list view instead of the individual instance record view.");
declareEnum("primary-select-action", "Action for primary select", SelectOnly)
declareEnum(mValues->mSceneEditing.mPrimarySelectAction, "Action for primary select")
.setTooltip(
"Selection can be chosen between select only, add to selection, remove from selection and invert "
"selection.")
.addValues(primarySelectAction);
declareEnum("secondary-select-action", "Action for secondary select", SelectAdd)
"selection.");
declareEnum(mValues->mSceneEditing.mSecondarySelectAction, "Action for secondary select")
.setTooltip(
"Selection can be chosen between select only, add to selection, remove from selection and invert "
"selection.")
.addValues(secondarySelectAction);
"selection.");
declareCategory("Key Bindings");
@ -401,7 +340,7 @@ void CSMPrefs::State::declare()
"scene-select-secondary", "Secondary Select", QKeySequence(Qt::ControlModifier | (int)Qt::MiddleButton));
declareShortcut(
"scene-select-tertiary", "Tertiary Select", QKeySequence(Qt::ShiftModifier | (int)Qt::MiddleButton));
declareModifier("scene-speed-modifier", "Speed Modifier", Qt::Key_Shift);
declareModifier(mValues->mKeyBindings.mSceneSpeedModifier, "Speed Modifier");
declareShortcut("scene-delete", "Delete Instance", QKeySequence(Qt::Key_Delete));
declareShortcut("scene-instance-drop-terrain", "Drop to terrain level", QKeySequence(Qt::Key_G));
declareShortcut("scene-instance-drop-collision", "Drop to collision", QKeySequence(Qt::Key_H));
@ -415,6 +354,30 @@ void CSMPrefs::State::declare()
declareShortcut("scene-edit-abort", "Abort", QKeySequence(Qt::Key_Escape));
declareShortcut("scene-focus-toolbar", "Toggle Toolbar Focus", QKeySequence(Qt::Key_T));
declareShortcut("scene-render-stats", "Debug Rendering Stats", QKeySequence(Qt::Key_F3));
declareShortcut("scene-duplicate", "Duplicate Instance", QKeySequence(Qt::ShiftModifier | Qt::Key_C));
declareShortcut("scene-clear-selection", "Clear Selection", QKeySequence(Qt::Key_Space));
declareShortcut("scene-unhide-all", "Unhide All Objects", QKeySequence(Qt::AltModifier | Qt::Key_H));
declareShortcut("scene-toggle-visibility", "Toggle Selection Visibility", QKeySequence(Qt::Key_H));
declareShortcut("scene-group-1", "Select Group 1", QKeySequence(Qt::Key_1));
declareShortcut("scene-save-1", "Save Group 1", QKeySequence(Qt::ControlModifier | Qt::Key_1));
declareShortcut("scene-group-2", "Select Group 2", QKeySequence(Qt::Key_2));
declareShortcut("scene-save-2", "Save Group 2", QKeySequence(Qt::ControlModifier | Qt::Key_2));
declareShortcut("scene-group-3", "Select Group 3", QKeySequence(Qt::Key_3));
declareShortcut("scene-save-3", "Save Group 3", QKeySequence(Qt::ControlModifier | Qt::Key_3));
declareShortcut("scene-group-4", "Select Group 4", QKeySequence(Qt::Key_4));
declareShortcut("scene-save-4", "Save Group 4", QKeySequence(Qt::ControlModifier | Qt::Key_4));
declareShortcut("scene-group-5", "Selection Group 5", QKeySequence(Qt::Key_5));
declareShortcut("scene-save-5", "Save Group 5", QKeySequence(Qt::ControlModifier | Qt::Key_5));
declareShortcut("scene-group-6", "Selection Group 6", QKeySequence(Qt::Key_6));
declareShortcut("scene-save-6", "Save Group 6", QKeySequence(Qt::ControlModifier | Qt::Key_6));
declareShortcut("scene-group-7", "Selection Group 7", QKeySequence(Qt::Key_7));
declareShortcut("scene-save-7", "Save Group 7", QKeySequence(Qt::ControlModifier | Qt::Key_7));
declareShortcut("scene-group-8", "Selection Group 8", QKeySequence(Qt::Key_8));
declareShortcut("scene-save-8", "Save Group 8", QKeySequence(Qt::ControlModifier | Qt::Key_8));
declareShortcut("scene-group-9", "Selection Group 9", QKeySequence(Qt::Key_9));
declareShortcut("scene-save-9", "Save Group 9", QKeySequence(Qt::ControlModifier | Qt::Key_9));
declareShortcut("scene-group-0", "Selection Group 10", QKeySequence(Qt::Key_0));
declareShortcut("scene-save-0", "Save Group 10", QKeySequence(Qt::ControlModifier | Qt::Key_0));
declareSubcategory("1st/Free Camera");
declareShortcut("free-forward", "Forward", QKeySequence(Qt::Key_W));
@ -440,13 +403,12 @@ void CSMPrefs::State::declare()
declareShortcut("script-editor-uncomment", "Uncomment Selection", QKeySequence());
declareCategory("Models");
declareString("baseanim", "base animations", "meshes/base_anim.nif")
.setTooltip("3rd person base model with textkeys-data");
declareString("baseanimkna", "base animations, kna", "meshes/base_animkna.nif")
declareString(mValues->mModels.mBaseanim, "base animations").setTooltip("3rd person base model with textkeys-data");
declareString(mValues->mModels.mBaseanimkna, "base animations, kna")
.setTooltip("3rd person beast race base model with textkeys-data");
declareString("baseanimfemale", "base animations, female", "meshes/base_anim_female.nif")
declareString(mValues->mModels.mBaseanimfemale, "base animations, female")
.setTooltip("3rd person female base model with textkeys-data");
declareString("wolfskin", "base animations, wolf", "meshes/wolf/skin.nif").setTooltip("3rd person werewolf skin");
declareString(mValues->mModels.mWolfskin, "base animations, wolf").setTooltip("3rd person werewolf skin");
}
void CSMPrefs::State::declareCategory(const std::string& key)
@ -463,90 +425,65 @@ void CSMPrefs::State::declareCategory(const std::string& key)
}
}
CSMPrefs::IntSetting& CSMPrefs::State::declareInt(const std::string& key, const std::string& label, int default_)
CSMPrefs::IntSetting& CSMPrefs::State::declareInt(Settings::SettingValue<int>& value, const QString& label)
{
if (mCurrentCategory == mCategories.end())
throw std::logic_error("no category for setting");
setDefault(key, std::to_string(default_));
default_ = Settings::Manager::getInt(key, mCurrentCategory->second.getKey());
CSMPrefs::IntSetting* setting = new CSMPrefs::IntSetting(&mCurrentCategory->second, &mMutex, key, label, default_);
CSMPrefs::IntSetting* setting
= new CSMPrefs::IntSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex);
mCurrentCategory->second.addSetting(setting);
return *setting;
}
CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble(
const std::string& key, const std::string& label, double default_)
CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble(Settings::SettingValue<double>& value, const QString& label)
{
if (mCurrentCategory == mCategories.end())
throw std::logic_error("no category for setting");
std::ostringstream stream;
stream << default_;
setDefault(key, stream.str());
default_ = Settings::Manager::getFloat(key, mCurrentCategory->second.getKey());
CSMPrefs::DoubleSetting* setting
= new CSMPrefs::DoubleSetting(&mCurrentCategory->second, &mMutex, key, label, default_);
= new CSMPrefs::DoubleSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex);
mCurrentCategory->second.addSetting(setting);
return *setting;
}
CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(const std::string& key, const std::string& label, bool default_)
CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(Settings::SettingValue<bool>& value, const QString& label)
{
if (mCurrentCategory == mCategories.end())
throw std::logic_error("no category for setting");
setDefault(key, default_ ? "true" : "false");
default_ = Settings::Manager::getBool(key, mCurrentCategory->second.getKey());
CSMPrefs::BoolSetting* setting
= new CSMPrefs::BoolSetting(&mCurrentCategory->second, &mMutex, key, label, default_);
= new CSMPrefs::BoolSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex);
mCurrentCategory->second.addSetting(setting);
return *setting;
}
CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum(
const std::string& key, const std::string& label, EnumValue default_)
CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum(EnumSettingValue& value, const QString& label)
{
if (mCurrentCategory == mCategories.end())
throw std::logic_error("no category for setting");
setDefault(key, default_.mValue);
default_.mValue = Settings::Manager::getString(key, mCurrentCategory->second.getKey());
CSMPrefs::EnumSetting* setting
= new CSMPrefs::EnumSetting(&mCurrentCategory->second, &mMutex, key, label, default_);
CSMPrefs::EnumSetting* setting = new CSMPrefs::EnumSetting(
&mCurrentCategory->second, &mMutex, value.getValue().mName, label, value.getEnumValues(), *mIndex);
mCurrentCategory->second.addSetting(setting);
return *setting;
}
CSMPrefs::ColourSetting& CSMPrefs::State::declareColour(
const std::string& key, const std::string& label, QColor default_)
CSMPrefs::ColourSetting& CSMPrefs::State::declareColour(const std::string& key, const QString& label, QColor default_)
{
if (mCurrentCategory == mCategories.end())
throw std::logic_error("no category for setting");
setDefault(key, default_.name().toUtf8().data());
default_.setNamedColor(
QString::fromUtf8(Settings::Manager::getString(key, mCurrentCategory->second.getKey()).c_str()));
CSMPrefs::ColourSetting* setting
= new CSMPrefs::ColourSetting(&mCurrentCategory->second, &mMutex, key, label, default_);
= new CSMPrefs::ColourSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex);
mCurrentCategory->second.addSetting(setting);
@ -554,39 +491,32 @@ CSMPrefs::ColourSetting& CSMPrefs::State::declareColour(
}
CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut(
const std::string& key, const std::string& label, const QKeySequence& default_)
const std::string& key, const QString& label, const QKeySequence& default_)
{
if (mCurrentCategory == mCategories.end())
throw std::logic_error("no category for setting");
std::string seqStr = getShortcutManager().convertToString(default_);
setDefault(key, seqStr);
// Setup with actual data
QKeySequence sequence;
getShortcutManager().convertFromString(
Settings::Manager::getString(key, mCurrentCategory->second.getKey()), sequence);
getShortcutManager().convertFromString(mIndex->get<std::string>(mCurrentCategory->second.getKey(), key), sequence);
getShortcutManager().setSequence(key, sequence);
CSMPrefs::ShortcutSetting* setting = new CSMPrefs::ShortcutSetting(&mCurrentCategory->second, &mMutex, key, label);
CSMPrefs::ShortcutSetting* setting
= new CSMPrefs::ShortcutSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex);
mCurrentCategory->second.addSetting(setting);
return *setting;
}
CSMPrefs::StringSetting& CSMPrefs::State::declareString(
const std::string& key, const std::string& label, std::string default_)
Settings::SettingValue<std::string>& value, const QString& label)
{
if (mCurrentCategory == mCategories.end())
throw std::logic_error("no category for setting");
setDefault(key, default_);
default_ = Settings::Manager::getString(key, mCurrentCategory->second.getKey());
CSMPrefs::StringSetting* setting
= new CSMPrefs::StringSetting(&mCurrentCategory->second, &mMutex, key, label, default_);
= new CSMPrefs::StringSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex);
mCurrentCategory->second.addSetting(setting);
@ -594,55 +524,31 @@ CSMPrefs::StringSetting& CSMPrefs::State::declareString(
}
CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(
const std::string& key, const std::string& label, int default_)
Settings::SettingValue<std::string>& value, const QString& label)
{
if (mCurrentCategory == mCategories.end())
throw std::logic_error("no category for setting");
std::string modStr = getShortcutManager().convertToString(default_);
setDefault(key, modStr);
// Setup with actual data
int modifier;
getShortcutManager().convertFromString(
Settings::Manager::getString(key, mCurrentCategory->second.getKey()), modifier);
getShortcutManager().setModifier(key, modifier);
getShortcutManager().convertFromString(value.get(), modifier);
getShortcutManager().setModifier(value.mName, modifier);
CSMPrefs::ModifierSetting* setting = new CSMPrefs::ModifierSetting(&mCurrentCategory->second, &mMutex, key, label);
CSMPrefs::ModifierSetting* setting
= new CSMPrefs::ModifierSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex);
mCurrentCategory->second.addSetting(setting);
return *setting;
}
void CSMPrefs::State::declareSeparator()
{
if (mCurrentCategory == mCategories.end())
throw std::logic_error("no category for setting");
CSMPrefs::Setting* setting = new CSMPrefs::Setting(&mCurrentCategory->second, &mMutex, "", "");
mCurrentCategory->second.addSetting(setting);
}
void CSMPrefs::State::declareSubcategory(const std::string& label)
void CSMPrefs::State::declareSubcategory(const QString& label)
{
if (mCurrentCategory == mCategories.end())
throw std::logic_error("no category for setting");
CSMPrefs::Setting* setting = new CSMPrefs::Setting(&mCurrentCategory->second, &mMutex, "", label);
mCurrentCategory->second.addSetting(setting);
}
void CSMPrefs::State::setDefault(const std::string& key, const std::string& default_)
{
Settings::CategorySetting fullKey(mCurrentCategory->second.getKey(), key);
Settings::CategorySettingValueMap::iterator iter = Settings::Manager::mDefaultSettings.find(fullKey);
if (iter == Settings::Manager::mDefaultSettings.end())
Settings::Manager::mDefaultSettings.insert(std::make_pair(fullKey, default_));
mCurrentCategory->second.addSubcategory(
new CSMPrefs::Subcategory(&mCurrentCategory->second, &mMutex, label, *mIndex));
}
CSMPrefs::State::State(const Files::ConfigurationManager& configurationManager)
@ -650,6 +556,8 @@ CSMPrefs::State::State(const Files::ConfigurationManager& configurationManager)
, mDefaultConfigFile("defaults-cs.bin")
, mConfigurationManager(configurationManager)
, mCurrentCategory(mCategories.end())
, mIndex(std::make_unique<Settings::Index>())
, mValues(std::make_unique<Values>(*mIndex))
{
if (sThis)
throw std::logic_error("An instance of CSMPRefs::State already exists");
@ -709,27 +617,13 @@ CSMPrefs::State& CSMPrefs::State::get()
void CSMPrefs::State::resetCategory(const std::string& category)
{
for (Settings::CategorySettingValueMap::iterator i = Settings::Manager::mUserSettings.begin();
i != Settings::Manager::mUserSettings.end(); ++i)
{
// if the category matches
if (i->first.first == category)
{
// mark the setting as changed
Settings::Manager::mChangedSettings.insert(std::make_pair(i->first.first, i->first.second));
// reset the value to the default
i->second = Settings::Manager::mDefaultSettings[i->first];
}
}
Collection::iterator container = mCategories.find(category);
if (container != mCategories.end())
{
Category settings = container->second;
for (Category::Iterator i = settings.begin(); i != settings.end(); ++i)
for (Setting* setting : container->second)
{
(*i)->updateWidget();
update(**i);
setting->reset();
update(*setting);
}
}
}

@ -17,6 +17,11 @@
class QColor;
namespace Settings
{
class Index;
}
namespace CSMPrefs
{
class IntSetting;
@ -27,6 +32,8 @@ namespace CSMPrefs
class ModifierSetting;
class Setting;
class StringSetting;
class EnumSettingValue;
struct Values;
/// \brief User settings state
///
@ -50,43 +57,40 @@ namespace CSMPrefs
Collection mCategories;
Iterator mCurrentCategory;
QMutex mMutex;
std::unique_ptr<Settings::Index> mIndex;
std::unique_ptr<Values> mValues;
// not implemented
State(const State&);
State& operator=(const State&);
private:
void declare();
void declareCategory(const std::string& key);
IntSetting& declareInt(const std::string& key, const std::string& label, int default_);
DoubleSetting& declareDouble(const std::string& key, const std::string& label, double default_);
BoolSetting& declareBool(const std::string& key, const std::string& label, bool default_);
IntSetting& declareInt(Settings::SettingValue<int>& value, const QString& label);
EnumSetting& declareEnum(const std::string& key, const std::string& label, EnumValue default_);
DoubleSetting& declareDouble(Settings::SettingValue<double>& value, const QString& label);
ColourSetting& declareColour(const std::string& key, const std::string& label, QColor default_);
BoolSetting& declareBool(Settings::SettingValue<bool>& value, const QString& label);
ShortcutSetting& declareShortcut(
const std::string& key, const std::string& label, const QKeySequence& default_);
EnumSetting& declareEnum(EnumSettingValue& value, const QString& label);
StringSetting& declareString(const std::string& key, const std::string& label, std::string default_);
ColourSetting& declareColour(const std::string& key, const QString& label, QColor default_);
ModifierSetting& declareModifier(const std::string& key, const std::string& label, int modifier_);
ShortcutSetting& declareShortcut(const std::string& key, const QString& label, const QKeySequence& default_);
void declareSeparator();
StringSetting& declareString(Settings::SettingValue<std::string>& value, const QString& label);
void declareSubcategory(const std::string& label);
ModifierSetting& declareModifier(Settings::SettingValue<std::string>& value, const QString& label);
void setDefault(const std::string& key, const std::string& default_);
void declareSubcategory(const QString& label);
public:
State(const Files::ConfigurationManager& configurationManager);
State(const State&) = delete;
~State();
State& operator=(const State&) = delete;
void save();
Iterator begin();

@ -12,9 +12,8 @@
#include "state.hpp"
CSMPrefs::StringSetting::StringSetting(
Category* parent, QMutex* mutex, const std::string& key, const std::string& label, std::string_view default_)
: Setting(parent, mutex, key, label)
, mDefault(default_)
Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index)
: TypedSetting(parent, mutex, key, label, index)
, mWidget(nullptr)
{
}
@ -25,9 +24,9 @@ CSMPrefs::StringSetting& CSMPrefs::StringSetting::setTooltip(const std::string&
return *this;
}
std::pair<QWidget*, QWidget*> CSMPrefs::StringSetting::makeWidgets(QWidget* parent)
CSMPrefs::SettingWidgets CSMPrefs::StringSetting::makeWidgets(QWidget* parent)
{
mWidget = new QLineEdit(QString::fromUtf8(mDefault.c_str()), parent);
mWidget = new QLineEdit(QString::fromStdString(getValue()), parent);
if (!mTooltip.empty())
{
@ -37,23 +36,17 @@ std::pair<QWidget*, QWidget*> CSMPrefs::StringSetting::makeWidgets(QWidget* pare
connect(mWidget, &QLineEdit::textChanged, this, &StringSetting::textChanged);
return std::make_pair(static_cast<QWidget*>(nullptr), mWidget);
return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget };
}
void CSMPrefs::StringSetting::updateWidget()
{
if (mWidget)
{
mWidget->setText(QString::fromStdString(Settings::Manager::getString(getKey(), getParent()->getKey())));
}
mWidget->setText(QString::fromStdString(getValue()));
}
void CSMPrefs::StringSetting::textChanged(const QString& text)
{
{
QMutexLocker lock(getMutex());
Settings::Manager::setString(getKey(), getParent()->getKey(), text.toStdString());
}
setValue(text.toStdString());
getParent()->getState()->update(*this);
}

@ -14,22 +14,22 @@ class QWidget;
namespace CSMPrefs
{
class Category;
class StringSetting : public Setting
class StringSetting final : public TypedSetting<std::string>
{
Q_OBJECT
std::string mTooltip;
std::string mDefault;
QLineEdit* mWidget;
public:
StringSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label,
std::string_view default_);
explicit StringSetting(
Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index);
StringSetting& setTooltip(const std::string& tooltip);
/// Return label, input widget.
std::pair<QWidget*, QWidget*> makeWidgets(QWidget* parent) override;
SettingWidgets makeWidgets(QWidget* parent) override;
void updateWidget() override;

@ -0,0 +1,18 @@
#include "subcategory.hpp"
#include <QGridLayout>
namespace CSMPrefs
{
class Category;
Subcategory::Subcategory(Category* parent, QMutex* mutex, const QString& label, Settings::Index& index)
: Setting(parent, mutex, "", label, index)
{
}
SettingWidgets Subcategory::makeWidgets(QWidget* /*parent*/)
{
return SettingWidgets{ .mLabel = nullptr, .mInput = nullptr };
}
}

@ -0,0 +1,28 @@
#ifndef OPENMW_APPS_OPENCS_MODEL_PREFS_SUBCATEGORY_H
#define OPENMW_APPS_OPENCS_MODEL_PREFS_SUBCATEGORY_H
#include "setting.hpp"
#include <string>
#include <utility>
namespace CSMPrefs
{
class Category;
class Subcategory final : public Setting
{
Q_OBJECT
public:
explicit Subcategory(Category* parent, QMutex* mutex, const QString& label, Settings::Index& index);
SettingWidgets makeWidgets(QWidget* parent) override;
void updateWidget() override {}
void reset() override {}
};
}
#endif

@ -0,0 +1,546 @@
#ifndef OPENMW_APPS_OPENCS_MODEL_PREFS_VALUES_H
#define OPENMW_APPS_OPENCS_MODEL_PREFS_VALUES_H
#include "enumvalueview.hpp"
#include <components/settings/sanitizer.hpp>
#include <components/settings/settingvalue.hpp>
#include <Qt>
#include <QtGlobal>
#include <array>
#include <memory>
#include <span>
#include <string>
#include <string_view>
namespace CSMPrefs
{
class EnumSanitizer final : public Settings::Sanitizer<std::string>
{
public:
explicit EnumSanitizer(std::span<const EnumValueView> values)
: mValues(values)
{
}
std::string apply(const std::string& value) const override
{
const auto hasValue = [&](const EnumValueView& v) { return v.mValue == value; };
if (std::find_if(mValues.begin(), mValues.end(), hasValue) == mValues.end())
{
std::ostringstream message;
message << "Invalid enum value: " << value;
throw std::runtime_error(message.str());
}
return value;
}
private:
std::span<const EnumValueView> mValues;
};
inline std::unique_ptr<Settings::Sanitizer<std::string>> makeEnumSanitizerString(
std::span<const EnumValueView> values)
{
return std::make_unique<EnumSanitizer>(values);
}
class EnumSettingValue
{
public:
explicit EnumSettingValue(Settings::Index& index, std::string_view category, std::string_view name,
std::span<const EnumValueView> values, std::size_t defaultValueIndex)
: mValue(
index, category, name, std::string(values[defaultValueIndex].mValue), makeEnumSanitizerString(values))
, mEnumValues(values)
{
}
Settings::SettingValue<std::string>& getValue() { return mValue; }
std::span<const EnumValueView> getEnumValues() const { return mEnumValues; }
private:
Settings::SettingValue<std::string> mValue;
std::span<const EnumValueView> mEnumValues;
};
struct WindowsCategory : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
static constexpr std::string_view sName = "Windows";
static constexpr std::array<EnumValueView, 3> sMainwindowScrollbarValues{
EnumValueView{
"Scrollbar Only", "Simple addition of scrollbars, the view window does not grow automatically." },
EnumValueView{ "Grow Only", "The view window grows as subviews are added. No scrollbars." },
EnumValueView{
"Grow then Scroll", "The view window grows. The scrollbar appears once it cannot grow any further." },
};
Settings::SettingValue<int> mDefaultWidth{ mIndex, sName, "default-width", 800 };
Settings::SettingValue<int> mDefaultHeight{ mIndex, sName, "default-height", 600 };
Settings::SettingValue<bool> mShowStatusbar{ mIndex, sName, "show-statusbar", true };
Settings::SettingValue<bool> mReuse{ mIndex, sName, "reuse", true };
Settings::SettingValue<int> mMaxSubviews{ mIndex, sName, "max-subviews", 256 };
Settings::SettingValue<bool> mHideSubview{ mIndex, sName, "hide-subview", false };
Settings::SettingValue<int> mMinimumWidth{ mIndex, sName, "minimum-width", 325 };
EnumSettingValue mMainwindowScrollbar{ mIndex, sName, "mainwindow-scrollbar", sMainwindowScrollbarValues, 0 };
Settings::SettingValue<bool> mGrowLimit{ mIndex, sName, "grow-limit", false };
};
struct RecordsCategory : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
static constexpr std::string_view sName = "Records";
static constexpr std::array<EnumValueView, 3> sRecordValues{
EnumValueView{ "Icon and Text", "" },
EnumValueView{ "Icon Only", "" },
EnumValueView{ "Text Only", "" },
};
EnumSettingValue mStatusFormat{ mIndex, sName, "status-format", sRecordValues, 0 };
EnumSettingValue mTypeFormat{ mIndex, sName, "type-format", sRecordValues, 0 };
};
struct IdTablesCategory : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
static constexpr std::string_view sName = "ID Tables";
static constexpr std::array<EnumValueView, 7> sDoubleClickValues{
EnumValueView{ "Edit in Place", "Edit the clicked cell" },
EnumValueView{ "Edit Record", "Open a dialogue subview for the clicked record" },
EnumValueView{ "View", "Open a scene subview for the clicked record (not available everywhere)" },
EnumValueView{ "Revert", "" },
EnumValueView{ "Delete", "" },
EnumValueView{ "Edit Record and Close", "" },
EnumValueView{
"View and Close", "Open a scene subview for the clicked record and close the table subview" },
};
static constexpr std::array<EnumValueView, 3> sJumpAndSelectValues{
EnumValueView{ "Jump and Select", "Scroll new record into view and make it the selection" },
EnumValueView{ "Jump Only", "Scroll new record into view" },
EnumValueView{ "No Jump", "No special action" },
};
EnumSettingValue mDouble{ mIndex, sName, "double", sDoubleClickValues, 0 };
EnumSettingValue mDoubleS{ mIndex, sName, "double-s", sDoubleClickValues, 1 };
EnumSettingValue mDoubleC{ mIndex, sName, "double-c", sDoubleClickValues, 2 };
EnumSettingValue mDoubleSc{ mIndex, sName, "double-sc", sDoubleClickValues, 5 };
EnumSettingValue mJumpToAdded{ mIndex, sName, "jump-to-added", sJumpAndSelectValues, 0 };
Settings::SettingValue<bool> mExtendedConfig{ mIndex, sName, "extended-config", false };
Settings::SettingValue<bool> mSubviewNewWindow{ mIndex, sName, "subview-new-window", false };
};
struct IdDialoguesCategory : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
static constexpr std::string_view sName = "ID Dialogues";
Settings::SettingValue<bool> mToolbar{ mIndex, sName, "toolbar", true };
};
struct ReportsCategory : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
static constexpr std::string_view sName = "Reports";
static constexpr std::array<EnumValueView, 4> sReportValues{
EnumValueView{ "None", "" },
EnumValueView{ "Edit", "Open a table or dialogue suitable for addressing the listed report" },
EnumValueView{ "Remove", "Remove the report from the report table" },
EnumValueView{ "Edit And Remove",
"Open a table or dialogue suitable for addressing the listed report, then remove the report from the "
"report table" },
};
EnumSettingValue mDouble{ mIndex, sName, "double", sReportValues, 1 };
EnumSettingValue mDoubleS{ mIndex, sName, "double-s", sReportValues, 2 };
EnumSettingValue mDoubleC{ mIndex, sName, "double-c", sReportValues, 3 };
EnumSettingValue mDoubleSc{ mIndex, sName, "double-sc", sReportValues, 0 };
Settings::SettingValue<bool> mIgnoreBaseRecords{ mIndex, sName, "ignore-base-records", false };
};
struct SearchAndReplaceCategory : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
static constexpr std::string_view sName = "Search & Replace";
Settings::SettingValue<int> mCharBefore{ mIndex, sName, "char-before", 10 };
Settings::SettingValue<int> mCharAfter{ mIndex, sName, "char-after", 10 };
Settings::SettingValue<bool> mAutoDelete{ mIndex, sName, "auto-delete", true };
};
struct ScriptsCategory : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
static constexpr std::string_view sName = "Scripts";
static constexpr std::array<EnumValueView, 3> sWarningValues{
EnumValueView{ "Ignore", "Do not report warning" },
EnumValueView{ "Normal", "Report warnings as warning" },
EnumValueView{ "Strict", "Promote warning to an error" },
};
Settings::SettingValue<bool> mShowLinenum{ mIndex, sName, "show-linenum", true };
Settings::SettingValue<bool> mWrapLines{ mIndex, sName, "wrap-lines", false };
Settings::SettingValue<bool> mMonoFont{ mIndex, sName, "mono-font", true };
Settings::SettingValue<int> mTabWidth{ mIndex, sName, "tab-width", 4 };
EnumSettingValue mWarnings{ mIndex, sName, "warnings", sWarningValues, 1 };
Settings::SettingValue<bool> mToolbar{ mIndex, sName, "toolbar", true };
Settings::SettingValue<int> mCompileDelay{ mIndex, sName, "compile-delay", 100 };
Settings::SettingValue<int> mErrorHeight{ mIndex, sName, "error-height", 100 };
Settings::SettingValue<bool> mHighlightOccurrences{ mIndex, sName, "highlight-occurrences", true };
Settings::SettingValue<std::string> mColourHighlight{ mIndex, sName, "colour-highlight", "lightcyan" };
Settings::SettingValue<std::string> mColourInt{ mIndex, sName, "colour-int", "darkmagenta" };
Settings::SettingValue<std::string> mColourFloat{ mIndex, sName, "colour-float", "magenta" };
Settings::SettingValue<std::string> mColourName{ mIndex, sName, "colour-name", "grey" };
Settings::SettingValue<std::string> mColourKeyword{ mIndex, sName, "colour-keyword", "red" };
Settings::SettingValue<std::string> mColourSpecial{ mIndex, sName, "colour-special", "darkorange" };
Settings::SettingValue<std::string> mColourComment{ mIndex, sName, "colour-comment", "green" };
Settings::SettingValue<std::string> mColourId{ mIndex, sName, "colour-id", "blue" };
};
struct GeneralInputCategory : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
static constexpr std::string_view sName = "General Input";
Settings::SettingValue<bool> mCycle{ mIndex, sName, "cycle", false };
};
struct SceneInputCategory : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
static constexpr std::string_view sName = "3D Scene Input";
Settings::SettingValue<double> mNaviWheelFactor{ mIndex, sName, "navi-wheel-factor", 8 };
Settings::SettingValue<double> mSNaviSensitivity{ mIndex, sName, "s-navi-sensitivity", 50 };
Settings::SettingValue<double> mPNaviFreeSensitivity{ mIndex, sName, "p-navi-free-sensitivity", 1 / 650.0 };
Settings::SettingValue<bool> mPNaviFreeInvert{ mIndex, sName, "p-navi-free-invert", false };
Settings::SettingValue<double> mNaviFreeLinSpeed{ mIndex, sName, "navi-free-lin-speed", 1000 };
Settings::SettingValue<double> mNaviFreeRotSpeed{ mIndex, sName, "navi-free-rot-speed", 3.14 / 2 };
Settings::SettingValue<double> mNaviFreeSpeedMult{ mIndex, sName, "navi-free-speed-mult", 8 };
Settings::SettingValue<double> mPNaviOrbitSensitivity{ mIndex, sName, "p-navi-orbit-sensitivity", 1 / 650.0 };
Settings::SettingValue<bool> mPNaviOrbitInvert{ mIndex, sName, "p-navi-orbit-invert", false };
Settings::SettingValue<double> mNaviOrbitRotSpeed{ mIndex, sName, "navi-orbit-rot-speed", 3.14 / 4 };
Settings::SettingValue<double> mNaviOrbitSpeedMult{ mIndex, sName, "navi-orbit-speed-mult", 4 };
Settings::SettingValue<bool> mNaviOrbitConstRoll{ mIndex, sName, "navi-orbit-const-roll", true };
Settings::SettingValue<bool> mContextSelect{ mIndex, sName, "context-select", false };
Settings::SettingValue<double> mDragFactor{ mIndex, sName, "drag-factor", 1 };
Settings::SettingValue<double> mDragWheelFactor{ mIndex, sName, "drag-wheel-factor", 1 };
Settings::SettingValue<double> mDragShiftFactor{ mIndex, sName, "drag-shift-factor", 4 };
Settings::SettingValue<double> mRotateFactor{ mIndex, sName, "rotate-factor", 0.007 };
};
struct RenderingCategory : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
static constexpr std::string_view sName = "Rendering";
Settings::SettingValue<int> mFramerateLimit{ mIndex, sName, "framerate-limit", 60 };
Settings::SettingValue<int> mCameraFov{ mIndex, sName, "camera-fov", 90 };
Settings::SettingValue<bool> mCameraOrtho{ mIndex, sName, "camera-ortho", false };
Settings::SettingValue<int> mCameraOrthoSize{ mIndex, sName, "camera-ortho-size", 100 };
Settings::SettingValue<double> mObjectMarkerAlpha{ mIndex, sName, "object-marker-alpha", 0.5 };
Settings::SettingValue<bool> mSceneUseGradient{ mIndex, sName, "scene-use-gradient", true };
Settings::SettingValue<std::string> mSceneDayBackgroundColour{ mIndex, sName, "scene-day-background-colour",
"#6e7880" };
Settings::SettingValue<std::string> mSceneDayGradientColour{ mIndex, sName, "scene-day-gradient-colour",
"#2f3333" };
Settings::SettingValue<std::string> mSceneBrightBackgroundColour{ mIndex, sName,
"scene-bright-background-colour", "#4f575c" };
Settings::SettingValue<std::string> mSceneBrightGradientColour{ mIndex, sName, "scene-bright-gradient-colour",
"#2f3333" };
Settings::SettingValue<std::string> mSceneNightBackgroundColour{ mIndex, sName, "scene-night-background-colour",
"#404d4f" };
Settings::SettingValue<std::string> mSceneNightGradientColour{ mIndex, sName, "scene-night-gradient-colour",
"#2f3333" };
Settings::SettingValue<bool> mSceneDayNightSwitchNodes{ mIndex, sName, "scene-day-night-switch-nodes", true };
};
struct TooltipsCategory : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
static constexpr std::string_view sName = "Tooltips";
Settings::SettingValue<bool> mScene{ mIndex, sName, "scene", true };
Settings::SettingValue<bool> mSceneHideBasic{ mIndex, sName, "scene-hide-basic", false };
Settings::SettingValue<int> mSceneDelay{ mIndex, sName, "scene-delay", 500 };
};
struct SceneEditingCategory : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
static constexpr std::string_view sName = "3D Scene Editing";
static constexpr std::array<EnumValueView, 3> sInsertOutsideCellValues{
EnumValueView{ "Create cell and insert", "" },
EnumValueView{ "Discard", "" },
EnumValueView{ "Insert anyway", "" },
};
static constexpr std::array<EnumValueView, 3> sInsertOutsideVisibleCellValues{
EnumValueView{ "Show cell and insert", "" },
EnumValueView{ "Discard", "" },
EnumValueView{ "Insert anyway", "" },
};
static constexpr std::array<EnumValueView, 2> sLandEditOutsideCellValues{
EnumValueView{ "Create cell and land, then edit", "" },
EnumValueView{ "Discard", "" },
};
static constexpr std::array<EnumValueView, 2> sLandEditOutsideVisibleCellValues{
EnumValueView{ "Show cell and edit", "" },
EnumValueView{ "Discard", "" },
};
static constexpr std::array<EnumValueView, 4> sSelectAction{
EnumValueView{ "Select only", "" },
EnumValueView{ "Add to selection", "" },
EnumValueView{ "Remove from selection", "" },
EnumValueView{ "Invert selection", "" },
};
Settings::SettingValue<double> mGridsnapMovement{ mIndex, sName, "gridsnap-movement", 16 };
Settings::SettingValue<double> mGridsnapRotation{ mIndex, sName, "gridsnap-rotation", 15 };
Settings::SettingValue<double> mGridsnapScale{ mIndex, sName, "gridsnap-scale", 0.25 };
Settings::SettingValue<int> mDistance{ mIndex, sName, "distance", 50 };
EnumSettingValue mOutsideDrop{ mIndex, sName, "outside-drop", sInsertOutsideCellValues, 0 };
EnumSettingValue mOutsideVisibleDrop{ mIndex, sName, "outside-visible-drop", sInsertOutsideVisibleCellValues,
0 };
EnumSettingValue mOutsideLandedit{ mIndex, sName, "outside-landedit", sLandEditOutsideCellValues, 0 };
EnumSettingValue mOutsideVisibleLandedit{ mIndex, sName, "outside-visible-landedit",
sLandEditOutsideVisibleCellValues, 0 };
Settings::SettingValue<int> mTexturebrushMaximumsize{ mIndex, sName, "texturebrush-maximumsize", 50 };
Settings::SettingValue<int> mShapebrushMaximumsize{ mIndex, sName, "shapebrush-maximumsize", 100 };
Settings::SettingValue<bool> mLandeditPostSmoothpainting{ mIndex, sName, "landedit-post-smoothpainting",
false };
Settings::SettingValue<double> mLandeditPostSmoothstrength{ mIndex, sName, "landedit-post-smoothstrength",
0.25 };
Settings::SettingValue<bool> mOpenListView{ mIndex, sName, "open-list-view", false };
EnumSettingValue mPrimarySelectAction{ mIndex, sName, "primary-select-action", sSelectAction, 0 };
EnumSettingValue mSecondarySelectAction{ mIndex, sName, "secondary-select-action", sSelectAction, 1 };
};
struct KeyBindingsCategory : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
static constexpr std::string_view sName = "Key Bindings";
Settings::SettingValue<std::string> mDocumentFileNewgame{ mIndex, sName, "document-file-newgame", "Ctrl+N" };
Settings::SettingValue<std::string> mDocumentFileNewaddon{ mIndex, sName, "document-file-newaddon", "" };
Settings::SettingValue<std::string> mDocumentFileOpen{ mIndex, sName, "document-file-open", "Ctrl+O" };
Settings::SettingValue<std::string> mDocumentFileSave{ mIndex, sName, "document-file-save", "Ctrl+S" };
Settings::SettingValue<std::string> mDocumentHelpHelp{ mIndex, sName, "document-help-help", "F1" };
Settings::SettingValue<std::string> mDocumentHelpTutorial{ mIndex, sName, "document-help-tutorial", "" };
Settings::SettingValue<std::string> mDocumentFileVerify{ mIndex, sName, "document-file-verify", "" };
Settings::SettingValue<std::string> mDocumentFileMerge{ mIndex, sName, "document-file-merge", "" };
Settings::SettingValue<std::string> mDocumentFileErrorlog{ mIndex, sName, "document-file-errorlog", "" };
Settings::SettingValue<std::string> mDocumentFileMetadata{ mIndex, sName, "document-file-metadata", "" };
Settings::SettingValue<std::string> mDocumentFileClose{ mIndex, sName, "document-file-close", "Ctrl+W" };
Settings::SettingValue<std::string> mDocumentFileExit{ mIndex, sName, "document-file-exit", "Ctrl+Q" };
Settings::SettingValue<std::string> mDocumentEditUndo{ mIndex, sName, "document-edit-undo", "Ctrl+Z" };
Settings::SettingValue<std::string> mDocumentEditRedo{ mIndex, sName, "document-edit-redo", "Ctrl+Shift+Z" };
Settings::SettingValue<std::string> mDocumentEditPreferences{ mIndex, sName, "document-edit-preferences", "" };
Settings::SettingValue<std::string> mDocumentEditSearch{ mIndex, sName, "document-edit-search", "Ctrl+F" };
Settings::SettingValue<std::string> mDocumentViewNewview{ mIndex, sName, "document-view-newview", "" };
Settings::SettingValue<std::string> mDocumentViewStatusbar{ mIndex, sName, "document-view-statusbar", "" };
Settings::SettingValue<std::string> mDocumentViewFilters{ mIndex, sName, "document-view-filters", "" };
Settings::SettingValue<std::string> mDocumentWorldRegions{ mIndex, sName, "document-world-regions", "" };
Settings::SettingValue<std::string> mDocumentWorldCells{ mIndex, sName, "document-world-cells", "" };
Settings::SettingValue<std::string> mDocumentWorldReferencables{ mIndex, sName, "document-world-referencables",
"" };
Settings::SettingValue<std::string> mDocumentWorldReferences{ mIndex, sName, "document-world-references", "" };
Settings::SettingValue<std::string> mDocumentWorldLands{ mIndex, sName, "document-world-lands", "" };
Settings::SettingValue<std::string> mDocumentWorldLandtextures{ mIndex, sName, "document-world-landtextures",
"" };
Settings::SettingValue<std::string> mDocumentWorldPathgrid{ mIndex, sName, "document-world-pathgrid", "" };
Settings::SettingValue<std::string> mDocumentWorldRegionmap{ mIndex, sName, "document-world-regionmap", "" };
Settings::SettingValue<std::string> mDocumentMechanicsGlobals{ mIndex, sName, "document-mechanics-globals",
"" };
Settings::SettingValue<std::string> mDocumentMechanicsGamesettings{ mIndex, sName,
"document-mechanics-gamesettings", "" };
Settings::SettingValue<std::string> mDocumentMechanicsScripts{ mIndex, sName, "document-mechanics-scripts",
"" };
Settings::SettingValue<std::string> mDocumentMechanicsSpells{ mIndex, sName, "document-mechanics-spells", "" };
Settings::SettingValue<std::string> mDocumentMechanicsEnchantments{ mIndex, sName,
"document-mechanics-enchantments", "" };
Settings::SettingValue<std::string> mDocumentMechanicsMagiceffects{ mIndex, sName,
"document-mechanics-magiceffects", "" };
Settings::SettingValue<std::string> mDocumentMechanicsStartscripts{ mIndex, sName,
"document-mechanics-startscripts", "" };
Settings::SettingValue<std::string> mDocumentCharacterSkills{ mIndex, sName, "document-character-skills", "" };
Settings::SettingValue<std::string> mDocumentCharacterClasses{ mIndex, sName, "document-character-classes",
"" };
Settings::SettingValue<std::string> mDocumentCharacterFactions{ mIndex, sName, "document-character-factions",
"" };
Settings::SettingValue<std::string> mDocumentCharacterRaces{ mIndex, sName, "document-character-races", "" };
Settings::SettingValue<std::string> mDocumentCharacterBirthsigns{ mIndex, sName,
"document-character-birthsigns", "" };
Settings::SettingValue<std::string> mDocumentCharacterTopics{ mIndex, sName, "document-character-topics", "" };
Settings::SettingValue<std::string> mDocumentCharacterJournals{ mIndex, sName, "document-character-journals",
"" };
Settings::SettingValue<std::string> mDocumentCharacterTopicinfos{ mIndex, sName,
"document-character-topicinfos", "" };
Settings::SettingValue<std::string> mDocumentCharacterJournalinfos{ mIndex, sName,
"document-character-journalinfos", "" };
Settings::SettingValue<std::string> mDocumentCharacterBodyparts{ mIndex, sName, "document-character-bodyparts",
"" };
Settings::SettingValue<std::string> mDocumentAssetsReload{ mIndex, sName, "document-assets-reload", "F5" };
Settings::SettingValue<std::string> mDocumentAssetsSounds{ mIndex, sName, "document-assets-sounds", "" };
Settings::SettingValue<std::string> mDocumentAssetsSoundgens{ mIndex, sName, "document-assets-soundgens", "" };
Settings::SettingValue<std::string> mDocumentAssetsMeshes{ mIndex, sName, "document-assets-meshes", "" };
Settings::SettingValue<std::string> mDocumentAssetsIcons{ mIndex, sName, "document-assets-icons", "" };
Settings::SettingValue<std::string> mDocumentAssetsMusic{ mIndex, sName, "document-assets-music", "" };
Settings::SettingValue<std::string> mDocumentAssetsSoundres{ mIndex, sName, "document-assets-soundres", "" };
Settings::SettingValue<std::string> mDocumentAssetsTextures{ mIndex, sName, "document-assets-textures", "" };
Settings::SettingValue<std::string> mDocumentAssetsVideos{ mIndex, sName, "document-assets-videos", "" };
Settings::SettingValue<std::string> mDocumentDebugRun{ mIndex, sName, "document-debug-run", "" };
Settings::SettingValue<std::string> mDocumentDebugShutdown{ mIndex, sName, "document-debug-shutdown", "" };
Settings::SettingValue<std::string> mDocumentDebugProfiles{ mIndex, sName, "document-debug-profiles", "" };
Settings::SettingValue<std::string> mDocumentDebugRunlog{ mIndex, sName, "document-debug-runlog", "" };
Settings::SettingValue<std::string> mTableEdit{ mIndex, sName, "table-edit", "" };
Settings::SettingValue<std::string> mTableAdd{ mIndex, sName, "table-add", "Shift+A" };
Settings::SettingValue<std::string> mTableClone{ mIndex, sName, "table-clone", "Shift+D" };
Settings::SettingValue<std::string> mTouchRecord{ mIndex, sName, "touch-record", "" };
Settings::SettingValue<std::string> mTableRevert{ mIndex, sName, "table-revert", "" };
Settings::SettingValue<std::string> mTableRemove{ mIndex, sName, "table-remove", "Delete" };
Settings::SettingValue<std::string> mTableMoveup{ mIndex, sName, "table-moveup", "" };
Settings::SettingValue<std::string> mTableMovedown{ mIndex, sName, "table-movedown", "" };
Settings::SettingValue<std::string> mTableView{ mIndex, sName, "table-view", "Shift+C" };
Settings::SettingValue<std::string> mTablePreview{ mIndex, sName, "table-preview", "Shift+V" };
Settings::SettingValue<std::string> mTableExtendeddelete{ mIndex, sName, "table-extendeddelete", "" };
Settings::SettingValue<std::string> mTableExtendedrevert{ mIndex, sName, "table-extendedrevert", "" };
Settings::SettingValue<std::string> mReporttableShow{ mIndex, sName, "reporttable-show", "" };
Settings::SettingValue<std::string> mReporttableRemove{ mIndex, sName, "reporttable-remove", "Delete" };
Settings::SettingValue<std::string> mReporttableReplace{ mIndex, sName, "reporttable-replace", "" };
Settings::SettingValue<std::string> mReporttableRefresh{ mIndex, sName, "reporttable-refresh", "" };
Settings::SettingValue<std::string> mSceneNaviPrimary{ mIndex, sName, "scene-navi-primary", "LMB" };
Settings::SettingValue<std::string> mSceneNaviSecondary{ mIndex, sName, "scene-navi-secondary", "Ctrl+LMB" };
Settings::SettingValue<std::string> mSceneOpenPrimary{ mIndex, sName, "scene-open-primary", "Shift+LMB" };
Settings::SettingValue<std::string> mSceneEditPrimary{ mIndex, sName, "scene-edit-primary", "RMB" };
Settings::SettingValue<std::string> mSceneEditSecondary{ mIndex, sName, "scene-edit-secondary", "Ctrl+RMB" };
Settings::SettingValue<std::string> mSceneSelectPrimary{ mIndex, sName, "scene-select-primary", "MMB" };
Settings::SettingValue<std::string> mSceneSelectSecondary{ mIndex, sName, "scene-select-secondary",
"Ctrl+MMB" };
Settings::SettingValue<std::string> mSceneSelectTertiary{ mIndex, sName, "scene-select-tertiary", "Shift+MMB" };
Settings::SettingValue<std::string> mSceneSpeedModifier{ mIndex, sName, "scene-speed-modifier", "Shift" };
Settings::SettingValue<std::string> mSceneDelete{ mIndex, sName, "scene-delete", "Delete" };
Settings::SettingValue<std::string> mSceneInstanceDropTerrain{ mIndex, sName, "scene-instance-drop-terrain",
"G" };
Settings::SettingValue<std::string> mSceneInstanceDropCollision{ mIndex, sName, "scene-instance-drop-collision",
"H" };
Settings::SettingValue<std::string> mSceneInstanceDropTerrainSeparately{ mIndex, sName,
"scene-instance-drop-terrain-separately", "" };
Settings::SettingValue<std::string> mSceneInstanceDropCollisionSeparately{ mIndex, sName,
"scene-instance-drop-collision-separately", "" };
Settings::SettingValue<std::string> mSceneDuplicate{ mIndex, sName, "scene-duplicate", "Shift+C" };
Settings::SettingValue<std::string> mSceneLoadCamCell{ mIndex, sName, "scene-load-cam-cell", "Keypad+5" };
Settings::SettingValue<std::string> mSceneLoadCamEastcell{ mIndex, sName, "scene-load-cam-eastcell",
"Keypad+6" };
Settings::SettingValue<std::string> mSceneLoadCamNorthcell{ mIndex, sName, "scene-load-cam-northcell",
"Keypad+8" };
Settings::SettingValue<std::string> mSceneLoadCamWestcell{ mIndex, sName, "scene-load-cam-westcell",
"Keypad+4" };
Settings::SettingValue<std::string> mSceneLoadCamSouthcell{ mIndex, sName, "scene-load-cam-southcell",
"Keypad+2" };
Settings::SettingValue<std::string> mSceneEditAbort{ mIndex, sName, "scene-edit-abort", "Escape" };
Settings::SettingValue<std::string> mSceneFocusToolbar{ mIndex, sName, "scene-focus-toolbar", "T" };
Settings::SettingValue<std::string> mSceneRenderStats{ mIndex, sName, "scene-render-stats", "F3" };
Settings::SettingValue<std::string> mSceneClearSelection{ mIndex, sName, "scene-clear-selection", "Space" };
Settings::SettingValue<std::string> mSceneUnhideAll{ mIndex, sName, "scene-unhide-all", "Alt+H" };
Settings::SettingValue<std::string> mSceneToggleVisibility{ mIndex, sName, "scene-toggle-visibility", "H" };
Settings::SettingValue<std::string> mSceneGroup0{ mIndex, sName, "scene-group-0", "0" };
Settings::SettingValue<std::string> mSceneSave0{ mIndex, sName, "scene-save-0", "Ctrl+0" };
Settings::SettingValue<std::string> mSceneGroup1{ mIndex, sName, "scene-group-1", "1" };
Settings::SettingValue<std::string> mSceneSave1{ mIndex, sName, "scene-save-1", "Ctrl+1" };
Settings::SettingValue<std::string> mSceneGroup2{ mIndex, sName, "scene-group-2", "2" };
Settings::SettingValue<std::string> mSceneSave2{ mIndex, sName, "scene-save-2", "Ctrl+2" };
Settings::SettingValue<std::string> mSceneGroup3{ mIndex, sName, "scene-group-3", "3" };
Settings::SettingValue<std::string> mSceneSave3{ mIndex, sName, "scene-save-3", "Ctrl+3" };
Settings::SettingValue<std::string> mSceneGroup4{ mIndex, sName, "scene-group-4", "4" };
Settings::SettingValue<std::string> mSceneSave4{ mIndex, sName, "scene-save-4", "Ctrl+4" };
Settings::SettingValue<std::string> mSceneGroup5{ mIndex, sName, "scene-group-5", "5" };
Settings::SettingValue<std::string> mSceneSave5{ mIndex, sName, "scene-save-5", "Ctrl+5" };
Settings::SettingValue<std::string> mSceneGroup6{ mIndex, sName, "scene-group-6", "6" };
Settings::SettingValue<std::string> mSceneSave6{ mIndex, sName, "scene-save-6", "Ctrl+6" };
Settings::SettingValue<std::string> mSceneGroup7{ mIndex, sName, "scene-group-7", "7" };
Settings::SettingValue<std::string> mSceneSave7{ mIndex, sName, "scene-save-7", "Ctrl+7" };
Settings::SettingValue<std::string> mSceneGroup8{ mIndex, sName, "scene-group-8", "8" };
Settings::SettingValue<std::string> mSceneSave8{ mIndex, sName, "scene-save-8", "Ctrl+8" };
Settings::SettingValue<std::string> mSceneGroup9{ mIndex, sName, "scene-group-9", "9" };
Settings::SettingValue<std::string> mSceneSave9{ mIndex, sName, "scene-save-9", "Ctrl+9" };
Settings::SettingValue<std::string> mFreeForward{ mIndex, sName, "free-forward", "W" };
Settings::SettingValue<std::string> mFreeBackward{ mIndex, sName, "free-backward", "S" };
Settings::SettingValue<std::string> mFreeLeft{ mIndex, sName, "free-left", "A" };
Settings::SettingValue<std::string> mFreeRight{ mIndex, sName, "free-right", "D" };
Settings::SettingValue<std::string> mFreeRollLeft{ mIndex, sName, "free-roll-left", "Q" };
Settings::SettingValue<std::string> mFreeRollRight{ mIndex, sName, "free-roll-right", "E" };
Settings::SettingValue<std::string> mFreeSpeedMode{ mIndex, sName, "free-speed-mode", "F" };
Settings::SettingValue<std::string> mOrbitUp{ mIndex, sName, "orbit-up", "W" };
Settings::SettingValue<std::string> mOrbitDown{ mIndex, sName, "orbit-down", "S" };
Settings::SettingValue<std::string> mOrbitLeft{ mIndex, sName, "orbit-left", "A" };
Settings::SettingValue<std::string> mOrbitRight{ mIndex, sName, "orbit-right", "D" };
Settings::SettingValue<std::string> mOrbitRollLeft{ mIndex, sName, "orbit-roll-left", "Q" };
Settings::SettingValue<std::string> mOrbitRollRight{ mIndex, sName, "orbit-roll-right", "E" };
Settings::SettingValue<std::string> mOrbitSpeedMode{ mIndex, sName, "orbit-speed-mode", "F" };
Settings::SettingValue<std::string> mOrbitCenterSelection{ mIndex, sName, "orbit-center-selection", "C" };
Settings::SettingValue<std::string> mScriptEditorComment{ mIndex, sName, "script-editor-comment", "" };
Settings::SettingValue<std::string> mScriptEditorUncomment{ mIndex, sName, "script-editor-uncomment", "" };
};
struct ModelsCategory : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
static constexpr std::string_view sName = "Models";
Settings::SettingValue<std::string> mBaseanim{ mIndex, sName, "baseanim", "meshes/base_anim.nif" };
Settings::SettingValue<std::string> mBaseanimkna{ mIndex, sName, "baseanimkna", "meshes/base_animkna.nif" };
Settings::SettingValue<std::string> mBaseanimfemale{ mIndex, sName, "baseanimfemale",
"meshes/base_anim_female.nif" };
Settings::SettingValue<std::string> mWolfskin{ mIndex, sName, "wolfskin", "meshes/wolf/skin.nif" };
};
struct Values : Settings::WithIndex
{
using Settings::WithIndex::WithIndex;
WindowsCategory mWindows{ mIndex };
RecordsCategory mRecords{ mIndex };
IdTablesCategory mIdTables{ mIndex };
IdDialoguesCategory mIdDialogues{ mIndex };
ReportsCategory mReports{ mIndex };
SearchAndReplaceCategory mSearchAndReplace{ mIndex };
ScriptsCategory mScripts{ mIndex };
GeneralInputCategory mGeneralInput{ mIndex };
SceneInputCategory mSceneInput{ mIndex };
RenderingCategory mRendering{ mIndex };
TooltipsCategory mTooltips{ mIndex };
SceneEditingCategory mSceneEditing{ mIndex };
KeyBindingsCategory mKeyBindings{ mIndex };
ModelsCategory mModels{ mIndex };
};
}
#endif

@ -189,9 +189,9 @@ void CSMTools::FixLandsAndLandTexturesMergeStage::perform(int stage, CSMDoc::Mes
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&>(
*mState.mTarget->getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures));
const std::string& id = mState.mTarget->getData().getLand().getId(stage).getRefIdString();
const auto& id = mState.mTarget->getData().getLand().getId(stage);
CSMWorld::TouchLandCommand cmd(landTable, ltexTable, id);
CSMWorld::TouchLandCommand cmd(landTable, ltexTable, id.getRefIdString());
cmd.redo();
// Get rid of base data

@ -41,17 +41,17 @@ void CSMTools::RaceCheckStage::performPerRecord(int stage, CSMDoc::Messages& mes
messages.add(id, "Description is missing", "", CSMDoc::Message::Severity_Warning);
// test for positive height
if (race.mData.mHeight.mMale <= 0)
if (race.mData.mMaleHeight <= 0)
messages.add(id, "Male height is non-positive", "", CSMDoc::Message::Severity_Error);
if (race.mData.mHeight.mFemale <= 0)
if (race.mData.mFemaleHeight <= 0)
messages.add(id, "Female height is non-positive", "", CSMDoc::Message::Severity_Error);
// test for non-negative weight
if (race.mData.mWeight.mMale < 0)
if (race.mData.mMaleWeight < 0)
messages.add(id, "Male weight is negative", "", CSMDoc::Message::Severity_Error);
if (race.mData.mWeight.mFemale < 0)
if (race.mData.mFemaleWeight < 0)
messages.add(id, "Female weight is negative", "", CSMDoc::Message::Severity_Error);
/// \todo check data members that can't be edited in the table view

@ -693,22 +693,12 @@ void CSMTools::ReferenceableCheckStage::npcCheck(
}
else if (npc.mNpdt.mHealth != 0)
{
if (npc.mNpdt.mStrength == 0)
messages.add(id, "Strength is equal to zero", "", CSMDoc::Message::Severity_Warning);
if (npc.mNpdt.mIntelligence == 0)
messages.add(id, "Intelligence is equal to zero", "", CSMDoc::Message::Severity_Warning);
if (npc.mNpdt.mWillpower == 0)
messages.add(id, "Willpower is equal to zero", "", CSMDoc::Message::Severity_Warning);
if (npc.mNpdt.mAgility == 0)
messages.add(id, "Agility is equal to zero", "", CSMDoc::Message::Severity_Warning);
if (npc.mNpdt.mSpeed == 0)
messages.add(id, "Speed is equal to zero", "", CSMDoc::Message::Severity_Warning);
if (npc.mNpdt.mEndurance == 0)
messages.add(id, "Endurance is equal to zero", "", CSMDoc::Message::Severity_Warning);
if (npc.mNpdt.mPersonality == 0)
messages.add(id, "Personality is equal to zero", "", CSMDoc::Message::Severity_Warning);
if (npc.mNpdt.mLuck == 0)
messages.add(id, "Luck is equal to zero", "", CSMDoc::Message::Severity_Warning);
for (size_t i = 0; i < npc.mNpdt.mAttributes.size(); ++i)
{
if (npc.mNpdt.mAttributes[i] == 0)
messages.add(id, ESM::Attribute::indexToRefId(i).getRefIdString() + " is equal to zero", {},
CSMDoc::Message::Severity_Warning);
}
}
if (level <= 0)

@ -98,9 +98,8 @@ void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages& message
if (cellRef.mEnchantmentCharge < -1)
messages.add(id, "Negative number of enchantment points", "", CSMDoc::Message::Severity_Error);
// Check if gold value isn't negative
if (cellRef.mGoldValue < 0)
messages.add(id, "Negative gold value", "", CSMDoc::Message::Severity_Error);
if (cellRef.mCount < 1)
messages.add(id, "Reference without count", {}, CSMDoc::Message::Severity_Error);
}
int CSMTools::ReferenceCheckStage::setup()

@ -7,6 +7,7 @@
#include <string_view>
#include <vector>
#include <apps/opencs/model/prefs/state.hpp>
#include <apps/opencs/model/world/columns.hpp>
#include <apps/opencs/model/world/idcollection.hpp>
#include <apps/opencs/model/world/record.hpp>
@ -132,11 +133,11 @@ namespace CSMWorld
bool beast = mRaceData ? mRaceData->isBeast() : false;
if (beast)
return Settings::Manager::getString("baseanimkna", "Models");
return CSMPrefs::get()["Models"]["baseanimkna"].toString();
else if (mFemale)
return Settings::Manager::getString("baseanimfemale", "Models");
return CSMPrefs::get()["Models"]["baseanimfemale"].toString();
else
return Settings::Manager::getString("baseanim", "Models");
return CSMPrefs::get()["Models"]["baseanim"].toString();
}
ESM::RefId ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const

@ -333,6 +333,37 @@ namespace CSMWorld
return true;
}
SelectionGroupColumn::SelectionGroupColumn()
: Column<ESM::SelectionGroup>(Columns::ColumnId_SelectionGroupObjects, ColumnBase::Display_None)
{
}
QVariant SelectionGroupColumn::get(const Record<ESM::SelectionGroup>& record) const
{
QVariant data;
QStringList selectionInfo;
const std::vector<std::string>& instances = record.get().selectedInstances;
for (const std::string& instance : instances)
selectionInfo << QString::fromStdString(instance);
data.setValue(selectionInfo);
return data;
}
void SelectionGroupColumn::set(Record<ESM::SelectionGroup>& record, const QVariant& data)
{
ESM::SelectionGroup record2 = record.get();
for (const auto& item : data.toStringList())
record2.selectedInstances.push_back(item.toStdString());
record.setModified(record2);
}
bool SelectionGroupColumn::isEditable() const
{
return false;
}
std::optional<std::uint32_t> getSkillIndex(std::string_view value)
{
int index = ESM::Skill::refIdToIndex(ESM::RefId::stringRefId(value));

@ -16,6 +16,7 @@
#include <components/esm3/loadinfo.hpp>
#include <components/esm3/loadrace.hpp>
#include <components/esm3/loadskil.hpp>
#include <components/esm3/selectiongroup.hpp>
#include <components/esm3/variant.hpp>
#include <optional>
@ -570,19 +571,34 @@ namespace CSMWorld
QVariant get(const Record<ESXRecordT>& record) const override
{
const ESM::Race::MaleFemaleF& value = mWeight ? record.get().mData.mWeight : record.get().mData.mHeight;
return mMale ? value.mMale : value.mFemale;
if (mWeight)
{
if (mMale)
return record.get().mData.mMaleWeight;
return record.get().mData.mFemaleWeight;
}
if (mMale)
return record.get().mData.mMaleHeight;
return record.get().mData.mFemaleHeight;
}
void set(Record<ESXRecordT>& record, const QVariant& data) override
{
ESXRecordT record2 = record.get();
ESM::Race::MaleFemaleF& value = mWeight ? record2.mData.mWeight : record2.mData.mHeight;
(mMale ? value.mMale : value.mFemale) = data.toFloat();
if (mWeight)
{
if (mMale)
record2.mData.mMaleWeight = data.toFloat();
else
record2.mData.mFemaleWeight = data.toFloat();
}
else
{
if (mMale)
record2.mData.mMaleHeight = data.toFloat();
else
record2.mData.mFemaleHeight = data.toFloat();
}
record.setModified(record2);
}
@ -1095,19 +1111,19 @@ namespace CSMWorld
};
template <typename ESXRecordT>
struct GoldValueColumn : public Column<ESXRecordT>
struct StackSizeColumn : public Column<ESXRecordT>
{
GoldValueColumn()
: Column<ESXRecordT>(Columns::ColumnId_CoinValue, ColumnBase::Display_Integer)
StackSizeColumn()
: Column<ESXRecordT>(Columns::ColumnId_StackCount, ColumnBase::Display_Integer)
{
}
QVariant get(const Record<ESXRecordT>& record) const override { return record.get().mGoldValue; }
QVariant get(const Record<ESXRecordT>& record) const override { return record.get().mCount; }
void set(Record<ESXRecordT>& record, const QVariant& data) override
{
ESXRecordT record2 = record.get();
record2.mGoldValue = data.toInt();
record2.mCount = data.toInt();
record.setModified(record2);
}
@ -2376,6 +2392,17 @@ namespace CSMWorld
void set(Record<ESM::BodyPart>& record, const QVariant& data) override;
bool isEditable() const override;
};
struct SelectionGroupColumn : public Column<ESM::SelectionGroup>
{
SelectionGroupColumn();
QVariant get(const Record<ESM::SelectionGroup>& record) const override;
void set(Record<ESM::SelectionGroup>& record, const QVariant& data) override;
bool isEditable() const override;
};
}
// This is required to access the type as a QVariant.

@ -56,7 +56,7 @@ namespace CSMWorld
{ ColumnId_FactionIndex, "Faction Index" },
{ ColumnId_Charges, "Charges" },
{ ColumnId_Enchantment, "Enchantment" },
{ ColumnId_CoinValue, "Coin Value" },
{ ColumnId_StackCount, "Count" },
{ ColumnId_Teleport, "Teleport" },
{ ColumnId_TeleportCell, "Teleport Cell" },
{ ColumnId_LockLevel, "Lock Level" },

@ -45,7 +45,7 @@ namespace CSMWorld
ColumnId_FactionIndex = 31,
ColumnId_Charges = 32,
ColumnId_Enchantment = 33,
ColumnId_CoinValue = 34,
ColumnId_StackCount = 34,
ColumnId_Teleport = 35,
ColumnId_TeleportCell = 36,
ColumnId_LockLevel = 37,
@ -347,6 +347,8 @@ namespace CSMWorld
ColumnId_LevelledCreatureId = 315,
ColumnId_SelectionGroupObjects = 316,
// Allocated to a separate value range, so we don't get a collision should we ever need
// to extend the number of use values.
ColumnId_UseValue1 = 0x10000,

@ -587,7 +587,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
mRefs.addColumn(new FactionIndexColumn<CellRef>);
mRefs.addColumn(new ChargesColumn<CellRef>);
mRefs.addColumn(new EnchantmentChargesColumn<CellRef>);
mRefs.addColumn(new GoldValueColumn<CellRef>);
mRefs.addColumn(new StackSizeColumn<CellRef>);
mRefs.addColumn(new TeleportColumn<CellRef>);
mRefs.addColumn(new TeleportCellColumn<CellRef>);
mRefs.addColumn(new PosColumn<CellRef>(&CellRef::mDoorDest, 0, true));
@ -620,6 +620,11 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
mDebugProfiles.addColumn(new DescriptionColumn<ESM::DebugProfile>);
mDebugProfiles.addColumn(new ScriptColumn<ESM::DebugProfile>(ScriptColumn<ESM::DebugProfile>::Type_Lines));
mSelectionGroups.addColumn(new StringIdColumn<ESM::SelectionGroup>);
mSelectionGroups.addColumn(new RecordStateColumn<ESM::SelectionGroup>);
mSelectionGroups.addColumn(new FixedRecordTypeColumn<ESM::SelectionGroup>(UniversalId::Type_SelectionGroup));
mSelectionGroups.addColumn(new SelectionGroupColumn);
mMetaData.appendBlankRecord(ESM::RefId::stringRefId("sys::meta"));
mMetaData.addColumn(new StringIdColumn<MetaData>(true));
@ -664,6 +669,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Textures)), UniversalId::Type_Texture);
addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Videos)), UniversalId::Type_Video);
addModel(new IdTable(&mMetaData), UniversalId::Type_MetaData);
addModel(new IdTable(&mSelectionGroups), UniversalId::Type_SelectionGroup);
mActorAdapter = std::make_unique<ActorAdapter>(*this);
@ -908,6 +914,16 @@ CSMWorld::IdCollection<ESM::DebugProfile>& CSMWorld::Data::getDebugProfiles()
return mDebugProfiles;
}
CSMWorld::IdCollection<ESM::SelectionGroup>& CSMWorld::Data::getSelectionGroups()
{
return mSelectionGroups;
}
const CSMWorld::IdCollection<ESM::SelectionGroup>& CSMWorld::Data::getSelectionGroups() const
{
return mSelectionGroups;
}
const CSMWorld::IdCollection<CSMWorld::Land>& CSMWorld::Data::getLand() const
{
return mLand;
@ -1369,6 +1385,17 @@ bool CSMWorld::Data::continueLoading(CSMDoc::Messages& messages)
mDebugProfiles.load(*mReader, mBase);
break;
case ESM::REC_SELG:
if (!mProject)
{
unhandledRecord = true;
break;
}
mSelectionGroups.load(*mReader, mBase);
break;
default:
unhandledRecord = true;

@ -33,6 +33,7 @@
#include <components/esm3/loadsoun.hpp>
#include <components/esm3/loadspel.hpp>
#include <components/esm3/loadsscr.hpp>
#include <components/esm3/selectiongroup.hpp>
#include <components/files/multidircollection.hpp>
#include <components/misc/algorithm.hpp>
#include <components/to_utf8/to_utf8.hpp>
@ -105,6 +106,7 @@ namespace CSMWorld
IdCollection<ESM::BodyPart> mBodyParts;
IdCollection<ESM::MagicEffect> mMagicEffects;
IdCollection<ESM::DebugProfile> mDebugProfiles;
IdCollection<ESM::SelectionGroup> mSelectionGroups;
IdCollection<ESM::SoundGenerator> mSoundGens;
IdCollection<ESM::StartScript> mStartScripts;
NestedInfoCollection mTopicInfos;
@ -251,6 +253,10 @@ namespace CSMWorld
IdCollection<ESM::DebugProfile>& getDebugProfiles();
const IdCollection<ESM::SelectionGroup>& getSelectionGroups() const;
IdCollection<ESM::SelectionGroup>& getSelectionGroups();
const IdCollection<CSMWorld::Land>& getLand() const;
IdCollection<CSMWorld::Land>& getLand();

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

Loading…
Cancel
Save