Merge remote-tracking branch 'upstream/master' into osgshadow-test-vdsm

pull/541/head
AnyOldName3 6 years ago
commit 8482236a82

@ -13,10 +13,10 @@ Debian:
before_script: before_script:
- export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR
- apt-get update -yq - apt-get update -yq
- apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev
# - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old # - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old
- curl -L http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-3_amd64.deb -o libbullet-dev_2.87+dfsg-3_amd64.deb - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb
- curl -L http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-3_amd64.deb -o libbullet2.87_2.87+dfsg-3_amd64.deb - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb
- curl -L https://http.kali.org/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb - curl -L https://http.kali.org/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb
- curl -L https://http.kali.org/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb - curl -L https://http.kali.org/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb
- curl -L https://http.kali.org/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb - curl -L https://http.kali.org/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb

@ -19,7 +19,7 @@ addons:
# Dev # Dev
cmake, clang-7, clang-tools-7, gcc-8, g++-8, cmake, clang-7, clang-tools-7, gcc-8, g++-8,
# Boost # Boost
libboost-filesystem-dev, libboost-program-options-dev, libboost-system-dev, libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev,
# FFmpeg # FFmpeg
libavcodec-dev, libavformat-dev, libavutil-dev, libswresample-dev, libswscale-dev, libavcodec-dev, libavformat-dev, libavutil-dev, libswresample-dev, libswscale-dev,
# Audio, Video and Misc. deps # Audio, Video and Misc. deps
@ -37,9 +37,9 @@ addons:
build_command: "make VERBOSE=1 -j3" build_command: "make VERBOSE=1 -j3"
matrix: matrix:
include: include:
- name: OpenMW (all) on MacOS xcode9.4 - name: OpenMW (all) on macOS Xcode 10.1
os: osx os: osx
osx_image: xcode9.4 osx_image: xcode10.1
if: branch != coverity_scan if: branch != coverity_scan
- name: OpenMW (all) on Ubuntu Xenial GCC-5 - name: OpenMW (all) on Ubuntu Xenial GCC-5
os: linux os: linux
@ -90,6 +90,7 @@ script:
- cd ./build - cd ./build
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ${ANALYZE} make -j3; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ${ANALYZE} make -j3; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
- cd "${TRAVIS_BUILD_DIR}" - cd "${TRAVIS_BUILD_DIR}"

@ -1,7 +1,11 @@
0.46.0 0.46.0
------ ------
Bug #2987: Editor: some chance and AI data fields can overflow
Bug #3623: Fix HiDPI on Windows Bug #3623: Fix HiDPI on Windows
Bug #4329: Removed birthsign abilities are restored after reloading the save
Bug #4383: Bow model obscures crosshair when arrow is drawn
Bug #4411: Reloading a saved game while falling prevents damage in some cases
Bug #4540: Rain delay when exiting water Bug #4540: Rain delay when exiting water
Bug #4701: PrisonMarker record is not hardcoded like other markers Bug #4701: PrisonMarker record is not hardcoded like other markers
Bug #4714: Crash upon game load in the repair menu while the "Your repair failed!" message is active Bug #4714: Crash upon game load in the repair menu while the "Your repair failed!" message is active
@ -11,11 +15,18 @@
Bug #4745: Editor: Interior cell lighting field values are not displayed as colors Bug #4745: Editor: Interior cell lighting field values are not displayed as colors
Bug #4746: Non-solid player can't run or sneak Bug #4746: Non-solid player can't run or sneak
Bug #4750: Sneaking doesn't work in first person view if the player is in attack ready state Bug #4750: Sneaking doesn't work in first person view if the player is in attack ready state
Bug #4768: Fallback numerical value recovery chokes on invalid arguments
Bug #4775: Slowfall effect resets player jumping flag
Bug #4778: Interiors of Illusion puzzle in Sotha Sil Expanded mod is broken
Bug #4800: Standing collisions are not updated immediately when an object is teleported without a cell change
Bug #4803: Stray special characters before begin statement break script compilation
Bug #4804: Particle system with the "Has Sizes = false" causes an exception
Feature #2229: Improve pathfinding AI Feature #2229: Improve pathfinding AI
Feature #3442: Default values for fallbacks from ini file Feature #3442: Default values for fallbacks from ini file
Feature #3610: Option to invert X axis Feature #3610: Option to invert X axis
Feature #4673: Weapon sheathing Feature #4673: Weapon sheathing
Feature #4730: Native animated containers support Feature #4730: Native animated containers support
Feature #4812: Support NiSwitchNode
Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4686: Upgrade media decoder to a more current FFmpeg API
0.45.0 0.45.0

@ -1,10 +1,11 @@
#!/bin/sh -e #!/bin/sh -e
brew update brew update
brew unlink cmake || true
brew outdated cmake || brew upgrade cmake brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/a3b64391ebace30b84de8e7997665a1621c0b2c0/Formula/cmake.rb
brew switch cmake 3.12.4
brew outdated pkgconfig || brew upgrade pkgconfig brew outdated pkgconfig || brew upgrade pkgconfig
brew install qt brew install qt
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-7cf2789.zip -o ~/openmw-deps.zip curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-110f3d3.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null

@ -34,6 +34,8 @@ VS_VERSION=""
NMAKE="" NMAKE=""
PLATFORM="" PLATFORM=""
CONFIGURATION="" CONFIGURATION=""
TEST_FRAMEWORK=""
GOOGLE_INSTALL_ROOT=""
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
ARGSTR=$1 ARGSTR=$1
@ -78,6 +80,9 @@ while [ $# -gt 0 ]; do
CONFIGURATION=$1 CONFIGURATION=$1
shift ;; shift ;;
t )
TEST_FRAMEWORK=true ;;
h ) h )
cat <<EOF cat <<EOF
Usage: $0 [-cdehkpuvV] Usage: $0 [-cdehkpuvV]
@ -94,6 +99,8 @@ Options:
Keep the old build directory, default is to delete it. Keep the old build directory, default is to delete it.
-p <Win32/Win64> -p <Win32/Win64>
Set the build platform, can also be set with environment variable PLATFORM. Set the build platform, can also be set with environment variable PLATFORM.
-t
Build unit tests / Google test
-u -u
Configure for unity builds. Configure for unity builds.
-v <2013/2015/2017> -v <2013/2015/2017>
@ -395,6 +402,16 @@ if [ -z $SKIP_DOWNLOAD ]; then
download "SDL 2.0.7" \ download "SDL 2.0.7" \
"https://www.libsdl.org/release/SDL2-devel-2.0.7-VC.zip" \ "https://www.libsdl.org/release/SDL2-devel-2.0.7-VC.zip" \
"SDL2-2.0.7.zip" "SDL2-2.0.7.zip"
# Google test and mock
if [ ! -z $TEST_FRAMEWORK ]; then
echo "Google test 1.8.1..."
if [ -d googletest ]; then
printf " Google test exists, skipping."
else
git clone -b release-1.8.1 https://github.com/google/googletest.git
fi
fi
fi fi
cd .. #/.. cd .. #/..
@ -670,6 +687,52 @@ printf "SDL 2.0.7... "
add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll" add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll"
echo Done. echo Done.
} }
cd $DEPS
echo
# Google Test and Google Mock
if [ ! -z $TEST_FRAMEWORK ]; then
printf "Google test 1.8.1 ..."
cd googletest
if [ ! -d build ]; then
mkdir build
fi
cd build
GOOGLE_INSTALL_ROOT="${DEPS_INSTALL}/GoogleTest"
if [ $CONFIGURATION == "Debug" ]; then
DEBUG_SUFFIX="d"
else
DEBUG_SUFFIX=""
fi
if [ ! -d $GOOGLE_INSTALL_ROOT ]; then
cmake .. -DCMAKE_BUILD_TYPE="${CONFIGURATION}" -DCMAKE_INSTALL_PREFIX="${GOOGLE_INSTALL_ROOT}" -DCMAKE_USE_WIN32_THREADS_INIT=1 -G "${GENERATOR}" -DBUILD_SHARED_LIBS=1
cmake --build . --config "${CONFIGURATION}"
cmake --build . --target install --config "${CONFIGURATION}"
add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gtest_main${DEBUG_SUFFIX}.dll"
add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gtest${DEBUG_SUFFIX}.dll"
add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gmock_main${DEBUG_SUFFIX}.dll"
add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gmock${DEBUG_SUFFIX}.dll"
fi
add_cmake_opts -DBUILD_UNITTESTS=yes
# FindGTest and FindGMock do not work perfectly on Windows
# but we can help them by telling them everything we know about installation
add_cmake_opts -DGMOCK_ROOT="$GOOGLE_INSTALL_ROOT"
add_cmake_opts -DGTEST_ROOT="$GOOGLE_INSTALL_ROOT"
add_cmake_opts -DGTEST_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest${DEBUG_SUFFIX}.lib"
add_cmake_opts -DGTEST_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest_main${DEBUG_SUFFIX}.lib"
add_cmake_opts -DGMOCK_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock${DEBUG_SUFFIX}.lib"
add_cmake_opts -DGMOCK_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock_main${DEBUG_SUFFIX}.lib"
add_cmake_opts -DGTEST_LINKED_AS_SHARED_LIBRARY=True
echo Done.
fi
echo echo
cd $DEPS_INSTALL/.. cd $DEPS_INSTALL/..
echo echo

@ -11,7 +11,7 @@ cd build
cmake \ cmake \
-D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \ -D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.9" \ -D CMAKE_OSX_DEPLOYMENT_TARGET="10.9" \
-D CMAKE_OSX_SYSROOT="macosx10.13" \ -D CMAKE_OSX_SYSROOT="macosx10.14" \
-D CMAKE_BUILD_TYPE=Release \ -D CMAKE_BUILD_TYPE=Release \
-D OPENMW_OSX_DEPLOYMENT=TRUE \ -D OPENMW_OSX_DEPLOYMENT=TRUE \
-D DESIRED_QT_VERSION=5 \ -D DESIRED_QT_VERSION=5 \

@ -0,0 +1,15 @@
#!/usr/bin/env bash
hdiutil attach ./*.dmg -mountpoint "${TRAVIS_BUILD_DIR}/openmw-package" > /dev/null || echo "hdutil has failed"
EXPECTED_PACKAGE_FILES=('Applications' 'OpenMW-CS.app' 'OpenMW.app')
PACKAGE_FILES=$(ls "${TRAVIS_BUILD_DIR}/openmw-package" | LC_ALL=C sort)
DIFF=$(diff <(printf "%s\n" "${EXPECTED_PACKAGE_FILES[@]}") <(printf "%s\n" "${PACKAGE_FILES[@]}"))
DIFF_STATUS=$?
if [[ $DIFF_STATUS -ne 0 ]]; then
echo "The package should only contain an Applications symlink and two applications, see the following diff for details." >&2
echo "$DIFF" >&2
exit 1
fi

@ -1,11 +1,26 @@
#!/bin/sh #!/bin/sh
cd build # This script expect the following environment variables to be set:
# - OSX_DEPLOY_KEY: private SSH key, must be encoded like this before adding it to Travis secrets: https://github.com/travis-ci/travis-ci/issues/7715#issuecomment-433301692
# - OSX_DEPLOY_HOST: string specifying SSH of the following format: ssh-user@ssh-host
# - OSX_DEPLOY_PORT: SSH port, it can't be a part of the host string because scp doesn't accept hosts with ports
# - OSX_DEPLOY_HOST_FINGERPRINT: fingerprint of the host, can be obtained by using ssh-keygen -F [host]:port & putting it in double quotes when adding to Travis secrets
DATE=`date +'%d%m%Y'` SSH_KEY_PATH="$HOME/.ssh/openmw_deploy"
SHORT_COMMIT=`git rev-parse --short ${TRAVIS_COMMIT}` REMOTE_PATH="\$HOME/nightly"
echo "$OSX_DEPLOY_KEY" > "$SSH_KEY_PATH"
chmod 600 "$SSH_KEY_PATH"
echo "$OSX_DEPLOY_HOST_FINGERPRINT" >> "$HOME/.ssh/known_hosts"
cd build || exit 1
DATE=$(date +'%d%m%Y')
SHORT_COMMIT=$(git rev-parse --short "${TRAVIS_COMMIT}")
TARGET_FILENAME="OpenMW-${DATE}-${SHORT_COMMIT}.dmg" TARGET_FILENAME="OpenMW-${DATE}-${SHORT_COMMIT}.dmg"
if ! curl --ssl -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}" --silent | grep $SHORT_COMMIT > /dev/null; then if ! ssh -p "$OSX_DEPLOY_PORT" -i "$SSH_KEY_PATH" "$OSX_DEPLOY_HOST" sh -c "ls \"$REMOTE_PATH\"" | grep "$SHORT_COMMIT" > /dev/null; then
curl --ssl --ftp-create-dirs -T *.dmg -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}${TARGET_FILENAME}" scp -P "$OSX_DEPLOY_PORT" -i "$SSH_KEY_PATH" ./*.dmg "$OSX_DEPLOY_HOST:$REMOTE_PATH/$TARGET_FILENAME"
else
echo "An existing nightly build for commit ${SHORT_COMMIT} has been found, skipping upload."
fi fi

@ -296,9 +296,9 @@ if(QT_STATIC)
endif() endif()
set(BOOST_COMPONENTS system filesystem program_options) set(BOOST_COMPONENTS system filesystem program_options iostreams)
if(WIN32) if(WIN32)
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale zlib)
endif(WIN32) endif(WIN32)
IF(BOOST_STATIC) IF(BOOST_STATIC)
@ -604,7 +604,7 @@ set(RECASTNAVIGATION_DEMO OFF CACHE BOOL "Do not build RecastDemo")
set(RECASTNAVIGATION_STATIC ON CACHE BOOL "Build recastnavigation static libraries") set(RECASTNAVIGATION_STATIC ON CACHE BOOL "Build recastnavigation static libraries")
set(RECASTNAVIGATION_TESTS OFF CACHE BOOL "Do not build recastnavigation tests") set(RECASTNAVIGATION_TESTS OFF CACHE BOOL "Do not build recastnavigation tests")
add_subdirectory (extern/recastnavigation) add_subdirectory (extern/recastnavigation EXCLUDE_FROM_ALL)
add_subdirectory (extern/osg-ffmpeg-videoplayer) add_subdirectory (extern/osg-ffmpeg-videoplayer)
add_subdirectory (extern/oics) add_subdirectory (extern/oics)
if (BUILD_OPENCS) if (BUILD_OPENCS)
@ -795,6 +795,10 @@ endif()
# Apple bundling # Apple bundling
if (OPENMW_OSX_DEPLOYMENT AND APPLE AND DESIRED_QT_VERSION MATCHES 5) if (OPENMW_OSX_DEPLOYMENT AND APPLE AND DESIRED_QT_VERSION MATCHES 5)
if (${CMAKE_MAJOR_VERSION} STREQUAL "3" AND ${CMAKE_MINOR_VERSION} STREQUAL "13")
message(FATAL_ERROR "macOS packaging is broken in CMake 3.13.*, see https://gitlab.com/OpenMW/openmw/issues/4767. Please use an older version like 3.12.4")
endif ()
get_property(QT_COCOA_PLUGIN_PATH TARGET Qt5::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE) get_property(QT_COCOA_PLUGIN_PATH TARGET Qt5::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE)
get_filename_component(QT_COCOA_PLUGIN_DIR "${QT_COCOA_PLUGIN_PATH}" DIRECTORY) get_filename_component(QT_COCOA_PLUGIN_DIR "${QT_COCOA_PLUGIN_PATH}" DIRECTORY)
get_filename_component(QT_COCOA_PLUGIN_GROUP "${QT_COCOA_PLUGIN_DIR}" NAME) get_filename_component(QT_COCOA_PLUGIN_GROUP "${QT_COCOA_PLUGIN_DIR}" NAME)

@ -269,7 +269,7 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages)
bool interior = cellRecord.mId.substr (0, 1)!="#"; bool interior = cellRecord.mId.substr (0, 1)!="#";
// count new references and adjust RefNumCount accordingsly // count new references and adjust RefNumCount accordingsly
int newRefNum = cellRecord.mRefNumCounter; unsigned int newRefNum = cellRecord.mRefNumCounter;
if (references!=mState.getSubRecords().end()) if (references!=mState.getSubRecords().end())
{ {
@ -279,11 +279,17 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages)
const CSMWorld::Record<CSMWorld::CellRef>& ref = const CSMWorld::Record<CSMWorld::CellRef>& ref =
mDocument.getData().getReferences().getRecord (*iter); mDocument.getData().getReferences().getRecord (*iter);
if (ref.get().mNew || CSMWorld::CellRef refRecord = ref.get();
if (refRecord.mNew ||
(!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly && (!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly &&
/// \todo consider worldspace /// \todo consider worldspace
CSMWorld::CellCoordinates (ref.get().getCellIndex()).getId("")!=ref.get().mCell)) CSMWorld::CellCoordinates (refRecord.getCellIndex()).getId("") != refRecord.mCell))
++cellRecord.mRefNumCounter; ++cellRecord.mRefNumCounter;
if (refRecord.mRefNum.mIndex >= newRefNum)
newRefNum = refRecord.mRefNum.mIndex + 1;
} }
} }
@ -328,7 +334,7 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages)
stream << "#" << index.first << " " << index.second; stream << "#" << index.first << " " << index.second;
} }
if (refRecord.mNew || if (refRecord.mNew || refRecord.mRefNum.mIndex == 0 ||
(!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly && (!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly &&
refRecord.mCell!=stream.str())) refRecord.mCell!=stream.str()))
{ {

@ -3,7 +3,6 @@
#include <stdexcept> #include <stdexcept>
#include <algorithm> #include <algorithm>
#include <sstream>
#include "intsetting.hpp" #include "intsetting.hpp"
#include "doublesetting.hpp" #include "doublesetting.hpp"
@ -393,9 +392,7 @@ CSMPrefs::IntSetting& CSMPrefs::State::declareInt (const std::string& key,
if (mCurrentCategory==mCategories.end()) if (mCurrentCategory==mCategories.end())
throw std::logic_error ("no category for setting"); throw std::logic_error ("no category for setting");
std::ostringstream stream; setDefault(key, std::to_string(default_));
stream << default_;
setDefault (key, stream.str());
default_ = mSettings.getInt (key, mCurrentCategory->second.getKey()); default_ = mSettings.getInt (key, mCurrentCategory->second.getKey());
@ -414,9 +411,7 @@ CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble (const std::string& key,
if (mCurrentCategory==mCategories.end()) if (mCurrentCategory==mCategories.end())
throw std::logic_error ("no category for setting"); throw std::logic_error ("no category for setting");
std::ostringstream stream; setDefault(key, std::to_string(default_));
stream << default_;
setDefault (key, stream.str());
default_ = mSettings.getFloat (key, mCurrentCategory->second.getKey()); default_ = mSettings.getFloat (key, mCurrentCategory->second.getKey());

@ -470,6 +470,13 @@ void CSMTools::ReferenceableCheckStage::creatureCheck (
if (creature.mData.mSoul < 0) if (creature.mData.mSoul < 0)
messages.add(id, "Soul value is negative", "", CSMDoc::Message::Severity_Error); messages.add(id, "Soul value is negative", "", CSMDoc::Message::Severity_Error);
if (creature.mAiData.mAlarm > 100)
messages.add(id, "Alarm rating is over 100", "", CSMDoc::Message::Severity_Warning);
if (creature.mAiData.mFight > 100)
messages.add(id, "Fight rating is over 100", "", CSMDoc::Message::Severity_Warning);
if (creature.mAiData.mFlee > 100)
messages.add(id, "Flee rating is over 100", "", CSMDoc::Message::Severity_Warning);
for (int i = 0; i < 6; ++i) for (int i = 0; i < 6; ++i)
{ {
if (creature.mData.mAttack[i] < 0) if (creature.mData.mAttack[i] < 0)
@ -700,6 +707,13 @@ void CSMTools::ReferenceableCheckStage::npcCheck (
if (level <= 0) if (level <= 0)
messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning); messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning);
if (npc.mAiData.mAlarm > 100)
messages.add(id, "Alarm rating is over 100", "", CSMDoc::Message::Severity_Warning);
if (npc.mAiData.mFight > 100)
messages.add(id, "Fight rating is over 100", "", CSMDoc::Message::Severity_Warning);
if (npc.mAiData.mFlee > 100)
messages.add(id, "Flee rating is over 100", "", CSMDoc::Message::Severity_Warning);
if (gold < 0) if (gold < 0)
messages.add(id, "Gold count is negative", "", CSMDoc::Message::Severity_Error); messages.add(id, "Gold count is negative", "", CSMDoc::Message::Severity_Error);
@ -1014,6 +1028,11 @@ template<typename Tool> void CSMTools::ReferenceableCheckStage::toolCheck (
template<typename List> void CSMTools::ReferenceableCheckStage::listCheck ( template<typename List> void CSMTools::ReferenceableCheckStage::listCheck (
const List& someList, CSMDoc::Messages& messages, const std::string& someID) const List& someList, CSMDoc::Messages& messages, const std::string& someID)
{ {
if (someList.mChanceNone > 100)
{
messages.add(someID, "Chance that no object is used is over 100 percent", "", CSMDoc::Message::Severity_Warning);
}
for (unsigned i = 0; i < someList.mList.size(); ++i) for (unsigned i = 0; i < someList.mList.size(); ++i)
{ {
if (mReferencables.searchId(someList.mList[i].mId).first == -1) if (mReferencables.searchId(someList.mList[i].mId).first == -1)

@ -42,5 +42,11 @@ void CSMTools::RegionCheckStage::perform (int stage, CSMDoc::Messages& messages)
if (chances != 100) if (chances != 100)
messages.add(id, "Weather chances do not add up to 100", "", CSMDoc::Message::Severity_Error); messages.add(id, "Weather chances do not add up to 100", "", CSMDoc::Message::Severity_Error);
for (const ESM::Region::SoundRef& sound : region.mSoundList)
{
if (sound.mChance > 100)
messages.add(id, "Chance of '" + sound.mSound.toString() + "' sound to play is over 100 percent", "", CSMDoc::Message::Severity_Warning);
}
/// \todo check data members that can't be edited in the table view /// \todo check data members that can't be edited in the table view
} }

@ -77,14 +77,9 @@ void CSMTools::Search::searchRecordStateCell (const CSMWorld::IdTableBase *model
{ {
std::vector<std::string> states = std::vector<std::string> states =
CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification); CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification);
std::ostringstream message;
message << states.at (data);
std::ostringstream hint; const std::string hint = "r: " + std::to_string(model->getColumnId(index.column()));
hint << "r: " << model->getColumnId (index.column()); messages.add (id, states.at(data), hint);
messages.add (id, message.str(), hint.str());
} }
} }

@ -17,6 +17,7 @@
#include "collectionbase.hpp" #include "collectionbase.hpp"
#include "land.hpp" #include "land.hpp"
#include "landtexture.hpp" #include "landtexture.hpp"
#include "ref.hpp"
namespace CSMWorld namespace CSMWorld
{ {
@ -259,6 +260,11 @@ namespace CSMWorld
copy.mState = RecordBase::State_ModifiedOnly; copy.mState = RecordBase::State_ModifiedOnly;
IdAccessorT().setId(copy.get(), destination); IdAccessorT().setId(copy.get(), destination);
if (type == UniversalId::Type_Reference) {
CSMWorld::CellRef* ptr = (CSMWorld::CellRef*) &copy.mModified;
ptr->mRefNum.mIndex = 0;
}
int index = getAppendIndex(destination, type); int index = getAppendIndex(destination, type);
insertRecord(copy, getAppendIndex(destination, type)); insertRecord(copy, getAppendIndex(destination, type));

@ -4,6 +4,7 @@
#include <memory> #include <memory>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include <components/misc/constants.hpp>
#include "../doc/document.hpp" #include "../doc/document.hpp"
@ -140,29 +141,44 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons
std::unique_ptr<CSMWorld::UpdateCellCommand> modifyCell; std::unique_ptr<CSMWorld::UpdateCellCommand> modifyCell;
std::unique_ptr<CSMWorld::ModifyCommand> modifyDataRefNum;
int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt(); int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt();
if (columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos) if (columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos)
{ {
IdTableProxyModel *proxy = dynamic_cast<IdTableProxyModel *> (model); const float oldPosition = model->data (index).toFloat();
int row = proxy ? proxy->mapToSource (index).row() : index.row();
// This is not guaranteed to be the same as \a model, since a proxy could be used. // Modulate by cell size, update cell id if reference has been moved to a new cell
IdTable& model2 = dynamic_cast<IdTable&> (*mDocument.getData().getTableModel (mId)); if (std::abs(std::fmod(oldPosition, Constants::CellSizeInUnits))
- std::abs(std::fmod(new_.toFloat(), Constants::CellSizeInUnits)) >= 0.5f)
{
IdTableProxyModel *proxy = dynamic_cast<IdTableProxyModel *> (model);
int cellColumn = model2.searchColumnIndex (Columns::ColumnId_Cell); int row = proxy ? proxy->mapToSource (index).row() : index.row();
if (cellColumn!=-1) // This is not guaranteed to be the same as \a model, since a proxy could be used.
{ IdTable& model2 = dynamic_cast<IdTable&> (*mDocument.getData().getTableModel (mId));
QModelIndex cellIndex = model2.index (row, cellColumn);
std::string cellId = model2.data (cellIndex).toString().toUtf8().data(); int cellColumn = model2.searchColumnIndex (Columns::ColumnId_Cell);
if (cellId.find ('#')!=std::string::npos) if (cellColumn!=-1)
{ {
// Need to recalculate the cell QModelIndex cellIndex = model2.index (row, cellColumn);
modifyCell.reset (new UpdateCellCommand (model2, row));
std::string cellId = model2.data (cellIndex).toString().toUtf8().data();
if (cellId.find ('#')!=std::string::npos)
{
// Need to recalculate the cell and (if necessary) clear the instance's refNum
modifyCell.reset (new UpdateCellCommand (model2, row));
// Not sure which model this should be applied to
int refNumColumn = model2.searchColumnIndex (Columns::ColumnId_RefNum);
if (refNumColumn!=-1)
modifyDataRefNum.reset (new ModifyCommand(*model, model->index(row, refNumColumn), 0));
}
} }
} }
} }
@ -175,6 +191,8 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons
CommandMacro macro (mDocument.getUndoStack()); CommandMacro macro (mDocument.getUndoStack());
macro.push (modifyData.release()); macro.push (modifyData.release());
macro.push (modifyCell.release()); macro.push (modifyCell.release());
if (modifyDataRefNum.get())
macro.push (modifyDataRefNum.release());
} }
else else
mDocument.getUndoStack().push (modifyData.release()); mDocument.getUndoStack().push (modifyData.release());

@ -205,7 +205,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat
mRegions.getNestableColumn(index)->addColumn( mRegions.getNestableColumn(index)->addColumn(
new NestedChildColumn (Columns::ColumnId_SoundName, ColumnBase::Display_Sound)); new NestedChildColumn (Columns::ColumnId_SoundName, ColumnBase::Display_Sound));
mRegions.getNestableColumn(index)->addColumn( mRegions.getNestableColumn(index)->addColumn(
new NestedChildColumn (Columns::ColumnId_SoundChance, ColumnBase::Display_Integer)); new NestedChildColumn (Columns::ColumnId_SoundChance, ColumnBase::Display_UnsignedInteger8));
mBirthsigns.addColumn (new StringIdColumn<ESM::BirthSign>); mBirthsigns.addColumn (new StringIdColumn<ESM::BirthSign>);
mBirthsigns.addColumn (new RecordStateColumn<ESM::BirthSign>); mBirthsigns.addColumn (new RecordStateColumn<ESM::BirthSign>);
@ -989,23 +989,29 @@ void CSMWorld::Data::loadFallbackEntries()
std::make_pair("PrisonMarker", "marker_prison.nif") std::make_pair("PrisonMarker", "marker_prison.nif")
}; };
for (const std::pair<std::string, std::string> marker : staticMarkers) for (const std::pair<std::string, std::string> &marker : staticMarkers)
{ {
if (mReferenceables.searchId (marker.first)==-1) if (mReferenceables.searchId (marker.first)==-1)
{ {
ESM::Static newMarker;
newMarker.mId = marker.first;
newMarker.mModel = marker.second;
CSMWorld::Record<ESM::Static> record; CSMWorld::Record<ESM::Static> record;
record.mBase = ESM::Static(marker.first, marker.second); record.mBase = newMarker;
record.mState = CSMWorld::RecordBase::State_BaseOnly; record.mState = CSMWorld::RecordBase::State_BaseOnly;
mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Static); mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Static);
} }
} }
for (const std::pair<std::string, std::string> marker : doorMarkers) for (const std::pair<std::string, std::string> &marker : doorMarkers)
{ {
if (mReferenceables.searchId (marker.first)==-1) if (mReferenceables.searchId (marker.first)==-1)
{ {
ESM::Door newMarker;
newMarker.mId = marker.first;
newMarker.mModel = marker.second;
CSMWorld::Record<ESM::Door> record; CSMWorld::Record<ESM::Door> record;
record.mBase = ESM::Door(marker.first, std::string(), marker.second, std::string(), std::string(), std::string()); record.mBase = newMarker;
record.mState = CSMWorld::RecordBase::State_BaseOnly; record.mState = CSMWorld::RecordBase::State_BaseOnly;
mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Door); mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Door);
} }

@ -221,7 +221,11 @@ std::string CSMWorld::IdTable::getId(int row) const
///This method can return only indexes to the top level table cells ///This method can return only indexes to the top level table cells
QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const
{ {
return index(mIdCollection->getIndex (id), column); int row = mIdCollection->searchId (id);
if (row != -1)
return index(row, column);
return QModelIndex();
} }
void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& record, CSMWorld::UniversalId::Type type) void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& record, CSMWorld::UniversalId::Type type)

@ -1,9 +1,5 @@
#include "ref.hpp" #include "ref.hpp"
#include <cmath>
#include <sstream>
#include "cellcoordinates.hpp" #include "cellcoordinates.hpp"
CSMWorld::CellRef::CellRef() : mNew (true) CSMWorld::CellRef::CellRef() : mNew (true)

@ -1,8 +1,5 @@
#include "refcollection.hpp" #include "refcollection.hpp"
#include <sstream>
#include <components/debug/debuglog.hpp>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include <components/esm/loadcell.hpp> #include <components/esm/loadcell.hpp>
@ -32,40 +29,31 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
if (cell.get().isExterior()) if (cell.get().isExterior())
{ {
// ignoring moved references sub-record; instead calculate cell from coordinates // Autocalculate the cell index from coordinates first
std::pair<int, int> index = ref.getCellIndex(); std::pair<int, int> index = ref.getCellIndex();
std::ostringstream stream; ref.mCell = "#" + std::to_string(index.first) + " " + std::to_string(index.second);
stream << "#" << index.first << " " << index.second;
ref.mCell = stream.str();
if (!base && // don't try to update base records // Handle non-base moved references
mref.mRefNum.mIndex != 0) // MVRF tag found if (!base && mref.mRefNum.mIndex != 0)
{ {
// there is a requirement for a placeholder where the original object was // Moved references must have a link back to their original cell
// // See discussion: https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30
// see the forum discussions here for more details:
// https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30
ref.mOriginalCell = cell2.mId; ref.mOriginalCell = cell2.mId;
// It is not always possibe to ignore moved references sub-record and // Some mods may move references outside of the bounds, which often happens they are deleted.
// calculate from coordinates. Some mods may place the ref in positions // This results in nonsensical autocalculated cell IDs, so we must use the record target cell.
// outside normal bounds, resulting in non sensical cell id's. This often
// happens if the moved ref was deleted. // Log a warning if the record target cell is different
//
// Use the target cell from the MVRF tag but if different output an error
// message
if (index.first != mref.mTarget[0] || index.second != mref.mTarget[1]) if (index.first != mref.mTarget[0] || index.second != mref.mTarget[1])
{ {
Log(Debug::Warning) << "Warning: the Position of moved ref " std::string indexCell = ref.mCell;
<< ref.mRefID << " does not match the target cell"; ref.mCell = "#" + std::to_string(mref.mTarget[0]) + " " + std::to_string(mref.mTarget[1]);
Log(Debug::Warning) << "Position: #" << index.first << " " << index.second
<<", Target #"<< mref.mTarget[0] << " " << mref.mTarget[1]; CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId (cellIndex));
messages.add(id, "The position of the moved reference " + ref.mRefID + " (cell " + indexCell + ")"
stream.clear(); " does not match the target cell (" + ref.mCell + ")",
stream << "#" << mref.mTarget[0] << " " << mref.mTarget[1]; std::string(), CSMDoc::Message::Severity_Warning);
ref.mCell = stream.str(); // overwrite
} }
} }
} }
@ -87,7 +75,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell,
mCells.getId (cellIndex)); mCells.getId (cellIndex));
messages.add (id, "Attempt to delete a non-existing reference"); messages.add (id, "Attempt to delete a non-existent reference");
continue; continue;
} }
@ -140,7 +128,5 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
std::string CSMWorld::RefCollection::getNewId() std::string CSMWorld::RefCollection::getNewId()
{ {
std::ostringstream stream; return "ref#" + std::to_string(mNextId++);
stream << "ref#" << mNextId++;
return stream.str();
} }

@ -128,13 +128,13 @@ CSMWorld::RefIdCollection::RefIdCollection()
ActorColumns actorsColumns (nameColumns); ActorColumns actorsColumns (nameColumns);
mColumns.push_back (RefIdColumn (Columns::ColumnId_AiHello, ColumnBase::Display_Integer)); mColumns.push_back (RefIdColumn (Columns::ColumnId_AiHello, ColumnBase::Display_UnsignedInteger8));
actorsColumns.mHello = &mColumns.back(); actorsColumns.mHello = &mColumns.back();
mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFlee, ColumnBase::Display_Integer)); mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFlee, ColumnBase::Display_UnsignedInteger8));
actorsColumns.mFlee = &mColumns.back(); actorsColumns.mFlee = &mColumns.back();
mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFight, ColumnBase::Display_Integer)); mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFight, ColumnBase::Display_UnsignedInteger8));
actorsColumns.mFight = &mColumns.back(); actorsColumns.mFight = &mColumns.back();
mColumns.push_back (RefIdColumn (Columns::ColumnId_AiAlarm, ColumnBase::Display_Integer)); mColumns.push_back (RefIdColumn (Columns::ColumnId_AiAlarm, ColumnBase::Display_UnsignedInteger8));
actorsColumns.mAlarm = &mColumns.back(); actorsColumns.mAlarm = &mColumns.back();
// Nested table // Nested table
@ -645,7 +645,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_LevelledItemType, CSMWorld::ColumnBase::Display_Boolean)); new RefIdColumn (Columns::ColumnId_LevelledItemType, CSMWorld::ColumnBase::Display_Boolean));
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_LevelledItemChanceNone, CSMWorld::ColumnBase::Display_Integer)); new RefIdColumn (Columns::ColumnId_LevelledItemChanceNone, CSMWorld::ColumnBase::Display_UnsignedInteger8));
mAdapters.insert (std::make_pair (UniversalId::Type_Activator, mAdapters.insert (std::make_pair (UniversalId::Type_Activator,
new NameRefIdAdapter<ESM::Activator> (UniversalId::Type_Activator, nameColumns))); new NameRefIdAdapter<ESM::Activator> (UniversalId::Type_Activator, nameColumns)));

@ -77,7 +77,7 @@ int CSMWorld::Resources::getIndex (const std::string& id) const
std::ostringstream stream; std::ostringstream stream;
stream << "Invalid resource: " << mBaseDirectory << '/' << id; stream << "Invalid resource: " << mBaseDirectory << '/' << id;
throw std::runtime_error (stream.str().c_str()); throw std::runtime_error (stream.str());
} }
return index; return index;

@ -110,7 +110,11 @@ QModelIndex CSMWorld::ResourceTable::parent (const QModelIndex& index) const
QModelIndex CSMWorld::ResourceTable::getModelIndex (const std::string& id, int column) const QModelIndex CSMWorld::ResourceTable::getModelIndex (const std::string& id, int column) const
{ {
return index (mResources->getIndex (id), column); int row = mResources->searchId(id);
if (row != -1)
return index (row, column);
return QModelIndex();
} }
int CSMWorld::ResourceTable::searchColumnIndex (Columns::ColumnId id) const int CSMWorld::ResourceTable::searchColumnIndex (Columns::ColumnId id) const

@ -670,27 +670,40 @@ void CSVRender::Object::apply (CSMWorld::CommandMacro& commands)
if (mOverrideFlags & Override_Position) if (mOverrideFlags & Override_Position)
{ {
for (int i=0; i<3; ++i) //Do cell check first so positions can be compared
const CSMWorld::CellRef& ref = collection.getRecord(recordIndex).get();
if (CSMWorld::CellCoordinates::isExteriorCell(ref.mCell))
{ {
int column = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> ( // Find cell index at new position
CSMWorld::Columns::ColumnId_PositionXPos+i)); std::pair<int, int> cellIndex = CSMWorld::CellCoordinates::coordinatesToCellIndex(
mPositionOverride.pos[0], mPositionOverride.pos[1]);
std::pair<int, int> originalIndex = ref.getCellIndex();
commands.push (new CSMWorld::ModifyCommand (*model, int cellColumn = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (
model->index (recordIndex, column), mPositionOverride.pos[i])); CSMWorld::Columns::ColumnId_Cell));
} int refNumColumn = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (
CSMWorld::Columns::ColumnId_RefNum));
if (cellIndex != originalIndex)
{
/// \todo figure out worldspace (not important until multiple worldspaces are supported)
std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId ("");
int column = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> ( commands.push (new CSMWorld::ModifyCommand (*model,
CSMWorld::Columns::ColumnId_Cell)); model->index (recordIndex, cellColumn), QString::fromUtf8 (cellId.c_str())));
commands.push (new CSMWorld::ModifyCommand( *model,
model->index (recordIndex, refNumColumn), 0));
}
}
if (CSMWorld::CellCoordinates::isExteriorCell(collection.getRecord (recordIndex).get().mCell)) for (int i=0; i<3; ++i)
{ {
std::pair<int, int> cellIndex = collection.getRecord (recordIndex).get().getCellIndex(); int column = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (
CSMWorld::Columns::ColumnId_PositionXPos+i));
/// \todo figure out worldspace (not important until multiple worldspaces are supported)
std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId ("");
commands.push (new CSMWorld::ModifyCommand (*model, commands.push (new CSMWorld::ModifyCommand (*model,
model->index (recordIndex, column), QString::fromUtf8 (cellId.c_str()))); model->index (recordIndex, column), mPositionOverride.pos[i]));
} }
} }

@ -150,10 +150,8 @@ std::string CSVWorld::SceneSubView::getTitle() const
void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::UniversalId& id) void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::UniversalId& id)
{ {
setUniversalId(id); setUniversalId(id);
std::ostringstream stream;
stream << "Scene: " << getUniversalId().getId();
mTitle = stream.str(); mTitle = "Scene: " + getUniversalId().getId();
setWindowTitle (QString::fromUtf8 (mTitle.c_str())); setWindowTitle (QString::fromUtf8 (mTitle.c_str()));
emit updateTitle(); emit updateTitle();
} }

@ -7,6 +7,8 @@
#include <QSplitter> #include <QSplitter>
#include <QTimer> #include <QTimer>
#include <components/debug/debuglog.hpp>
#include "../../model/doc/document.hpp" #include "../../model/doc/document.hpp"
#include "../../model/world/universalid.hpp" #include "../../model/world/universalid.hpp"
#include "../../model/world/data.hpp" #include "../../model/world/data.hpp"
@ -210,18 +212,28 @@ void CSVWorld::ScriptSubView::useHint (const std::string& hint)
unsigned line = 0, column = 0; unsigned line = 0, column = 0;
char c; char c;
std::istringstream stream (hint.c_str()+1); std::istringstream stream (hint.c_str()+1);
switch(hint[0]){ switch(hint[0])
{
case 'R': case 'R':
case 'r': case 'r':
{ {
QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn);
QString source = mModel->data (index).toString(); QString source = mModel->data (index).toString();
unsigned stringSize = source.length();
unsigned pos, dummy; unsigned pos, dummy;
if (!(stream >> c >> dummy >> pos) ) if (!(stream >> c >> dummy >> pos) )
return; return;
for (unsigned i = 0; i <= pos; ++i){ if (pos > stringSize)
if (source[i] == '\n'){ {
Log(Debug::Warning) << "CSVWorld::ScriptSubView: requested position is higher than actual string length";
pos = stringSize;
}
for (unsigned i = 0; i <= pos; ++i)
{
if (source[i] == '\n')
{
++line; ++line;
column = i+1; column = i+1;
} }
@ -231,7 +243,7 @@ void CSVWorld::ScriptSubView::useHint (const std::string& hint)
} }
case 'l': case 'l':
if (!(stream >> c >> line >> column)) if (!(stream >> c >> line >> column))
return; return;
} }
QTextCursor cursor = mEditor->textCursor(); QTextCursor cursor = mEditor->textCursor();

@ -1,5 +1,6 @@
#include "util.hpp" #include "util.hpp"
#include <limits>
#include <stdexcept> #include <stdexcept>
#include <QUndoStack> #include <QUndoStack>
@ -214,7 +215,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO
case CSMWorld::ColumnBase::Display_UnsignedInteger8: case CSMWorld::ColumnBase::Display_UnsignedInteger8:
{ {
DialogueSpinBox *sb = new DialogueSpinBox(parent); DialogueSpinBox *sb = new DialogueSpinBox(parent);
sb->setRange(0, UCHAR_MAX); sb->setRange(0, std::numeric_limits<unsigned char>::max());
return sb; return sb;
} }
@ -225,7 +226,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO
case CSMWorld::ColumnBase::Display_Float: case CSMWorld::ColumnBase::Display_Float:
{ {
DialogueDoubleSpinBox *dsb = new DialogueDoubleSpinBox(parent); DialogueDoubleSpinBox *dsb = new DialogueDoubleSpinBox(parent);
dsb->setRange(-FLT_MAX, FLT_MAX); dsb->setRange(-std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
dsb->setSingleStep(0.01f); dsb->setSingleStep(0.01f);
dsb->setDecimals(3); dsb->setDecimals(3);
return dsb; return dsb;
@ -234,7 +235,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO
case CSMWorld::ColumnBase::Display_Double: case CSMWorld::ColumnBase::Display_Double:
{ {
DialogueDoubleSpinBox *dsb = new DialogueDoubleSpinBox(parent); DialogueDoubleSpinBox *dsb = new DialogueDoubleSpinBox(parent);
dsb->setRange(-FLT_MAX, FLT_MAX); dsb->setRange(-std::numeric_limits<double>::max(), std::numeric_limits<double>::max());
dsb->setSingleStep(0.01f); dsb->setSingleStep(0.01f);
dsb->setDecimals(6); dsb->setDecimals(6);
return dsb; return dsb;

@ -20,14 +20,14 @@ void releaseArgv();
int Java_org_libsdl_app_SDLActivity_getMouseX(JNIEnv *env, jclass cls, jobject obj) { int Java_org_libsdl_app_SDLActivity_getMouseX(JNIEnv *env, jclass cls, jobject obj) {
int ret = 0; int ret = 0;
SDL_GetMouseState(&ret, nullptr); SDL_GetMouseState(&ret, NULL);
return ret; return ret;
} }
int Java_org_libsdl_app_SDLActivity_getMouseY(JNIEnv *env, jclass cls, jobject obj) { int Java_org_libsdl_app_SDLActivity_getMouseY(JNIEnv *env, jclass cls, jobject obj) {
int ret = 0; int ret = 0;
SDL_GetMouseState(nullptr, &ret); SDL_GetMouseState(NULL, &ret);
return ret; return ret;
} }
@ -35,6 +35,17 @@ int Java_org_libsdl_app_SDLActivity_isMouseShown(JNIEnv *env, jclass cls, jobjec
return SDL_ShowCursor(SDL_QUERY); return SDL_ShowCursor(SDL_QUERY);
} }
extern SDL_Window *Android_Window;
int SDL_SendMouseMotion(SDL_Window * window, int mouseID, int relative, int x, int y);
void Java_org_libsdl_app_SDLActivity_sendRelativeMouseMotion(JNIEnv *env, jclass cls, int x, int y) {
SDL_SendMouseMotion(Android_Window, 0, 1, x, y);
}
int SDL_SendMouseButton(SDL_Window * window, int mouseID, Uint8 state, Uint8 button);
void Java_org_libsdl_app_SDLActivity_sendMouseButton(JNIEnv *env, jclass cls, int state, int button) {
SDL_SendMouseButton(Android_Window, 0, state, button);
}
int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) { int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) {
setenv("OPENMW_DECOMPRESS_TEXTURES", "1", 1); setenv("OPENMW_DECOMPRESS_TEXTURES", "1", 1);

@ -154,14 +154,23 @@ bool OMW::Engine::frame(float frametime)
mEnvironment.getStateManager()->endGame(); mEnvironment.getStateManager()->endGame();
} }
// update physics
osg::Timer_t beforePhysicsTick = osg::Timer::instance()->tick();
if (mEnvironment.getStateManager()->getState()!=
MWBase::StateManager::State_NoGame)
{
mEnvironment.getWorld()->updatePhysics(frametime, guiActive);
}
osg::Timer_t afterPhysicsTick = osg::Timer::instance()->tick();
// update world // update world
osg::Timer_t beforePhysicsTick = osg::Timer::instance()->tick();; osg::Timer_t beforeWorldTick = osg::Timer::instance()->tick();
if (mEnvironment.getStateManager()->getState()!= if (mEnvironment.getStateManager()->getState()!=
MWBase::StateManager::State_NoGame) MWBase::StateManager::State_NoGame)
{ {
mEnvironment.getWorld()->update(frametime, guiActive); mEnvironment.getWorld()->update(frametime, guiActive);
} }
osg::Timer_t afterPhysicsTick = osg::Timer::instance()->tick(); osg::Timer_t afterWorldTick = osg::Timer::instance()->tick();
// update GUI // update GUI
mEnvironment.getWindowManager()->onFrame(frametime); mEnvironment.getWindowManager()->onFrame(frametime);
@ -180,6 +189,10 @@ bool OMW::Engine::frame(float frametime)
stats->setAttribute(frameNumber, "physics_time_taken", osg::Timer::instance()->delta_s(beforePhysicsTick, afterPhysicsTick)); stats->setAttribute(frameNumber, "physics_time_taken", osg::Timer::instance()->delta_s(beforePhysicsTick, afterPhysicsTick));
stats->setAttribute(frameNumber, "physics_time_end", osg::Timer::instance()->delta_s(mStartTick, afterPhysicsTick)); stats->setAttribute(frameNumber, "physics_time_end", osg::Timer::instance()->delta_s(mStartTick, afterPhysicsTick));
stats->setAttribute(frameNumber, "world_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforeWorldTick));
stats->setAttribute(frameNumber, "world_time_taken", osg::Timer::instance()->delta_s(beforeWorldTick, afterWorldTick));
stats->setAttribute(frameNumber, "world_time_end", osg::Timer::instance()->delta_s(mStartTick, afterWorldTick));
if (stats->collectStats("resource")) if (stats->collectStats("resource"))
{ {
mResourceSystem->reportStats(frameNumber, stats); mResourceSystem->reportStats(frameNumber, stats);
@ -667,10 +680,12 @@ void OMW::Engine::go()
statshandler->addUserStatsLine("Script", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f), statshandler->addUserStatsLine("Script", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
"script_time_taken", 1000.0, true, false, "script_time_begin", "script_time_end", 10000); "script_time_taken", 1000.0, true, false, "script_time_begin", "script_time_end", 10000);
statshandler->addUserStatsLine("Mechanics", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f), statshandler->addUserStatsLine("Mech", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
"mechanics_time_taken", 1000.0, true, false, "mechanics_time_begin", "mechanics_time_end", 10000); "mechanics_time_taken", 1000.0, true, false, "mechanics_time_begin", "mechanics_time_end", 10000);
statshandler->addUserStatsLine("Physics", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f), statshandler->addUserStatsLine("Phys", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
"physics_time_taken", 1000.0, true, false, "physics_time_begin", "physics_time_end", 10000); "physics_time_taken", 1000.0, true, false, "physics_time_begin", "physics_time_end", 10000);
statshandler->addUserStatsLine("World", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
"world_time_taken", 1000.0, true, false, "world_time_begin", "world_time_end", 10000);
mViewer->addEventHandler(statshandler); mViewer->addEventHandler(statshandler);

@ -371,6 +371,7 @@ namespace MWBase
/// \return pointer to created record /// \return pointer to created record
virtual void update (float duration, bool paused) = 0; virtual void update (float duration, bool paused) = 0;
virtual void updatePhysics (float duration, bool paused) = 0;
virtual void updateWindowManager () = 0; virtual void updateWindowManager () = 0;

@ -1172,6 +1172,17 @@ namespace MWClass
const ESM::Race* race = const ESM::Race* race =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace); MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
// Race weight should not affect 1st-person meshes, otherwise it will change hand proportions and can break aiming.
if (ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())
{
if (ref->mBase->isMale())
scale *= race->mData.mHeight.mMale;
else
scale *= race->mData.mHeight.mFemale;
return;
}
if (ref->mBase->isMale()) if (ref->mBase->isMale())
{ {
scale.x() *= race->mData.mWeight.mMale; scale.x() *= race->mData.mWeight.mMale;
@ -1184,7 +1195,6 @@ namespace MWClass
scale.y() *= race->mData.mWeight.mFemale; scale.y() *= race->mData.mWeight.mFemale;
scale.z() *= race->mData.mHeight.mFemale; scale.z() *= race->mData.mHeight.mFemale;
} }
} }
int Npc::getServices(const MWWorld::ConstPtr &actor) const int Npc::getServices(const MWWorld::ConstPtr &actor) const

@ -133,22 +133,23 @@ namespace MWGui
public: public:
CopyFramebufferToTextureCallback(osg::Texture2D* texture) CopyFramebufferToTextureCallback(osg::Texture2D* texture)
: mTexture(texture) : mTexture(texture)
, oneshot(true)
{ {
} }
virtual void operator () (osg::RenderInfo& renderInfo) const virtual void operator () (osg::RenderInfo& renderInfo) const
{ {
if (!oneshot)
return;
oneshot = false;
int w = renderInfo.getCurrentCamera()->getViewport()->width(); int w = renderInfo.getCurrentCamera()->getViewport()->width();
int h = renderInfo.getCurrentCamera()->getViewport()->height(); int h = renderInfo.getCurrentCamera()->getViewport()->height();
mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h); mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h);
// Callback removes itself when done
if (renderInfo.getCurrentCamera())
renderInfo.getCurrentCamera()->setInitialDrawCallback(nullptr);
} }
private: private:
osg::ref_ptr<osg::Texture2D> mTexture; osg::ref_ptr<osg::Texture2D> mTexture;
mutable bool oneshot;
}; };
class DontComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback class DontComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback
@ -308,6 +309,8 @@ namespace MWGui
mGuiTexture.reset(new osgMyGUI::OSGTexture(mTexture)); mGuiTexture.reset(new osgMyGUI::OSGTexture(mTexture));
} }
// Notice that the next time this is called, the current CopyFramebufferToTextureCallback will be deleted
// so there's no memory leak as at most one object of type CopyFramebufferToTextureCallback is allocated at a time.
mViewer->getCamera()->setInitialDrawCallback(new CopyFramebufferToTextureCallback(mTexture)); mViewer->getCamera()->setInitialDrawCallback(new CopyFramebufferToTextureCallback(mTexture));
mBackgroundImage->setBackgroundImage(""); mBackgroundImage->setBackgroundImage("");

@ -125,9 +125,7 @@ namespace MWGui
for (int i=0; ids[i]; ++i) for (int i=0; ids[i]; ++i)
if (ids[i]==id) if (ids[i]==id)
{ {
std::ostringstream valueString; setText (id, std::to_string(value.getModified()));
valueString << value.getModified();
setText (id, valueString.str());
MyGUI::TextBox* box; MyGUI::TextBox* box;
getWidget(box, id); getWidget(box, id);

@ -599,9 +599,7 @@ namespace MWGui
std::string ToolTips::toString(const int value) std::string ToolTips::toString(const int value)
{ {
std::ostringstream stream; return std::to_string(value);
stream << value;
return stream.str();
} }
std::string ToolTips::getWeightString(const float weight, const std::string& prefix) std::string ToolTips::getWeightString(const float weight, const std::string& prefix)

@ -87,10 +87,7 @@ namespace MWGui
else else
toAdd->setUserString("interior","n"); toAdd->setUserString("interior","n");
std::ostringstream oss; toAdd->setUserString("price", std::to_string(price));
oss << price;
toAdd->setUserString("price",oss.str());
toAdd->setCaptionWithReplacing("#{sCell=" + name + "} - " + MyGUI::utility::toString(price)+"#{sgp}"); toAdd->setCaptionWithReplacing("#{sCell=" + name + "} - " + MyGUI::utility::toString(price)+"#{sgp}");
toAdd->setSize(mDestinationsView->getWidth(),lineHeight); toAdd->setSize(mDestinationsView->getWidth(),lineHeight);
toAdd->eventMouseWheel += MyGUI::newDelegate(this, &TravelWindow::onMouseWheel); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &TravelWindow::onMouseWheel);

@ -860,8 +860,13 @@ namespace MWMechanics
bool hasSummonEffect = false; bool hasSummonEffect = false;
for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it)
{
if (isSummoningEffect(it->first.mId)) if (isSummoningEffect(it->first.mId))
{
hasSummonEffect = true; hasSummonEffect = true;
break;
}
}
if (!creatureStats.getSummonedCreatureMap().empty() || !creatureStats.getSummonedCreatureGraveyard().empty() || hasSummonEffect) if (!creatureStats.getSummonedCreatureMap().empty() || !creatureStats.getSummonedCreatureGraveyard().empty() || hasSummonEffect)
{ {

@ -1,7 +1,5 @@
#include "aiwander.hpp" #include "aiwander.hpp"
#include <cfloat>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/esm/aisequence.hpp> #include <components/esm/aisequence.hpp>

@ -154,9 +154,8 @@ void MWMechanics::Alchemy::updateEffects()
if (magicEffect->mData.mBaseCost<=0) if (magicEffect->mData.mBaseCost<=0)
{ {
std::ostringstream os; const std::string os = "invalid base cost for magic effect " + std::to_string(iter->mId);
os << "invalid base cost for magic effect " << iter->mId; throw std::runtime_error (os);
throw std::runtime_error (os.str());
} }
float fPotionT1MagMul = float fPotionT1MagMul =

@ -240,19 +240,6 @@ namespace MWMechanics
return true; return true;
} }
ESM::Skill::SkillEnum mapSchoolToSkill(int school)
{
std::map<int, ESM::Skill::SkillEnum> schoolSkillMap; // maps spell school to skill id
schoolSkillMap[0] = ESM::Skill::Alteration;
schoolSkillMap[1] = ESM::Skill::Conjuration;
schoolSkillMap[3] = ESM::Skill::Illusion;
schoolSkillMap[2] = ESM::Skill::Destruction;
schoolSkillMap[4] = ESM::Skill::Mysticism;
schoolSkillMap[5] = ESM::Skill::Restoration;
assert(schoolSkillMap.find(school) != schoolSkillMap.end());
return schoolSkillMap[school];
}
void calcWeakestSchool (const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm) void calcWeakestSchool (const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm)
{ {
// Morrowind for some reason uses a formula slightly different from magicka cost calculation // Morrowind for some reason uses a formula slightly different from magicka cost calculation
@ -288,7 +275,7 @@ namespace MWMechanics
if (effect.mRange == ESM::RT_Target) if (effect.mRange == ESM::RT_Target)
x *= 1.5f; x *= 1.5f;
float s = 2.f * actorSkills[mapSchoolToSkill(magicEffect->mData.mSchool)]; float s = 2.f * actorSkills[spellSchoolToSkill(magicEffect->mData.mSchool)];
if (s - x < minChance) if (s - x < minChance)
{ {
minChance = s - x; minChance = s - x;
@ -308,7 +295,7 @@ namespace MWMechanics
float skillTerm = 0; float skillTerm = 0;
if (effectiveSchool != -1) if (effectiveSchool != -1)
skillTerm = 2.f * actorSkills[mapSchoolToSkill(effectiveSchool)]; skillTerm = 2.f * actorSkills[spellSchoolToSkill(effectiveSchool)];
else else
calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this

@ -1,9 +1,6 @@
#ifndef OPENMW_AUTOCALCSPELL_H #ifndef OPENMW_AUTOCALCSPELL_H
#define OPENMW_AUTOCALCSPELL_H #define OPENMW_AUTOCALCSPELL_H
#include <cfloat>
#include <set>
#include <components/esm/loadspel.hpp> #include <components/esm/loadspel.hpp>
#include <components/esm/loadskil.hpp> #include <components/esm/loadskil.hpp>
#include <components/esm/loadrace.hpp> #include <components/esm/loadrace.hpp>
@ -22,8 +19,6 @@ std::vector<std::string> autoCalcPlayerSpells(const int* actorSkills, const int*
bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes); bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes);
ESM::Skill::SkillEnum mapSchoolToSkill(int school);
void calcWeakestSchool(const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm); void calcWeakestSchool(const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm);
float calcAutoCastChance(const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, int effectiveSchool); float calcAutoCastChance(const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, int effectiveSchool);

@ -60,13 +60,6 @@ void wrap(float& rad)
rad = std::fmod(rad-osg::PI, 2.0f*osg::PI)+osg::PI; rad = std::fmod(rad-osg::PI, 2.0f*osg::PI)+osg::PI;
} }
std::string toString(int num)
{
std::ostringstream stream;
stream << num;
return stream.str();
}
std::string getBestAttack (const ESM::Weapon* weapon) std::string getBestAttack (const ESM::Weapon* weapon)
{ {
int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2;
@ -235,13 +228,13 @@ public:
std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const
{ {
int numAnims=0; int numAnims=0;
while (mAnimation->hasAnimation(prefix + toString(numAnims+1))) while (mAnimation->hasAnimation(prefix + std::to_string(numAnims+1)))
++numAnims; ++numAnims;
int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims]
if (num) if (num)
*num = roll; *num = roll;
return prefix + toString(roll); return prefix + std::to_string(roll);
} }
void CharacterController::refreshHitRecoilAnims(CharacterState& idle) void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
@ -783,7 +776,7 @@ void CharacterController::playDeath(float startpoint, CharacterState death)
mCurrentDeath = "deathknockout"; mCurrentDeath = "deathknockout";
break; break;
default: default:
mCurrentDeath = "death" + toString(death - CharState_Death1 + 1); mCurrentDeath = "death" + std::to_string(death - CharState_Death1 + 1);
} }
mDeathState = death; mDeathState = death;

@ -1,8 +1,5 @@
#include "mechanicsmanagerimp.hpp" #include "mechanicsmanagerimp.hpp"
#include <limits.h>
#include <set>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/esm/esmwriter.hpp> #include <components/esm/esmwriter.hpp>

@ -152,20 +152,18 @@ float MWMechanics::NpcStats::getSkillProgressRequirement (int skillIndex, const
float typeFactor = gmst.find ("fMiscSkillBonus")->mValue.getFloat(); float typeFactor = gmst.find ("fMiscSkillBonus")->mValue.getFloat();
for (int i=0; i<5; ++i) for (int i=0; i<5; ++i)
{
if (class_.mData.mSkills[i][0]==skillIndex) if (class_.mData.mSkills[i][0]==skillIndex)
{ {
typeFactor = gmst.find ("fMinorSkillBonus")->mValue.getFloat(); typeFactor = gmst.find ("fMinorSkillBonus")->mValue.getFloat();
break; break;
} }
else if (class_.mData.mSkills[i][1]==skillIndex)
for (int i=0; i<5; ++i)
if (class_.mData.mSkills[i][1]==skillIndex)
{ {
typeFactor = gmst.find ("fMajorSkillBonus")->mValue.getFloat(); typeFactor = gmst.find ("fMajorSkillBonus")->mValue.getFloat();
break; break;
} }
}
progressRequirement *= typeFactor; progressRequirement *= typeFactor;
@ -233,15 +231,14 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas
if (class_.mData.mSkills[k][0] == skillIndex) if (class_.mData.mSkills[k][0] == skillIndex)
{ {
mLevelProgress += gmst.find("iLevelUpMinorMult")->mValue.getInteger(); mLevelProgress += gmst.find("iLevelUpMinorMult")->mValue.getInteger();
increase = gmst.find("iLevelUpMajorMultAttribute")->mValue.getInteger(); increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger();
break;
} }
} else if (class_.mData.mSkills[k][1] == skillIndex)
for (int k=0; k<5; ++k)
{
if (class_.mData.mSkills[k][1] == skillIndex)
{ {
mLevelProgress += gmst.find("iLevelUpMajorMult")->mValue.getInteger(); mLevelProgress += gmst.find("iLevelUpMajorMult")->mValue.getInteger();
increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger(); increase = gmst.find("iLevelUpMajorMultAttribute")->mValue.getInteger();
break;
} }
} }

@ -1,6 +1,5 @@
#include "spellcasting.hpp" #include "spellcasting.hpp"
#include <cfloat>
#include <limits> #include <limits>
#include <iomanip> #include <iomanip>

@ -1317,6 +1317,7 @@ namespace MWPhysics
float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f));
bool flying = world->isFlying(iter->first); bool flying = world->isFlying(iter->first);
bool swimming = world->isSwimming(iter->first);
bool wasOnGround = physicActor->getOnGround(); bool wasOnGround = physicActor->getOnGround();
osg::Vec3f position = physicActor->getPosition(); osg::Vec3f position = physicActor->getPosition();
@ -1339,8 +1340,9 @@ namespace MWPhysics
float heightDiff = position.z() - oldHeight; float heightDiff = position.z() - oldHeight;
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
if ((wasOnGround && physicActor->getOnGround()) || flying || world->isSwimming(iter->first) || slowFall < 1) bool isStillOnGround = (numSteps > 0 && wasOnGround && physicActor->getOnGround());
stats.land(iter->first == player); if (isStillOnGround || flying || swimming || slowFall < 1)
stats.land(iter->first == player && (flying || swimming));
else if (heightDiff < 0) else if (heightDiff < 0)
stats.addToFallHeight(-heightDiff); stats.addToFallHeight(-heightDiff);

@ -2,8 +2,6 @@
#include <osg/Stats> #include <osg/Stats>
#include <sstream>
#include <components/resource/objectcache.hpp> #include <components/resource/objectcache.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -21,9 +19,7 @@ LandManager::LandManager(int loadFlags)
osg::ref_ptr<ESMTerrain::LandObject> LandManager::getLand(int x, int y) osg::ref_ptr<ESMTerrain::LandObject> LandManager::getLand(int x, int y)
{ {
std::ostringstream id; std::string idstr = std::to_string(x) + " " + std::to_string(y);
id << x << " " << y;
std::string idstr = id.str();
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(idstr); osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(idstr);
if (obj) if (obj)

@ -30,6 +30,11 @@ namespace MWRender
void disable(); void disable();
bool isEnabled() const
{
return mEnabled;
}
private: private:
osg::ref_ptr<osg::Group> mRootNode; osg::ref_ptr<osg::Group> mRootNode;
bool mEnabled; bool mEnabled;

@ -317,8 +317,9 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
mViewMode = viewMode; mViewMode = viewMode;
rebuild(); MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale()); // apply race height after view change
rebuild();
setRenderBin(); setRenderBin();
} }

@ -1,7 +1,5 @@
#include "objects.hpp" #include "objects.hpp"
#include <cmath>
#include <osg/Group> #include <osg/Group>
#include <osg/UserDataContainer> #include <osg/UserDataContainer>

@ -625,28 +625,8 @@ namespace MWRender
mWater->update(dt); mWater->update(dt);
} }
const auto navMeshes = mNavigator.getNavMeshes(); updateNavMesh();
auto it = navMeshes.begin();
for (std::size_t i = 0; it != navMeshes.end() && i < mNavMeshNumber; ++i)
++it;
if (it == navMeshes.end())
{
mNavMesh->reset();
}
else
{
try
{
const auto locked = it->second.lockConst();
mNavMesh->update(locked->getValue(), mNavMeshNumber, locked->getGeneration(),
locked->getNavMeshRevision(), mNavigator.getSettings());
}
catch (const std::exception& e)
{
Log(Debug::Error) << "NavMesh render update exception: " << e.what();
}
}
mCamera->update(dt, paused); mCamera->update(dt, paused);
osg::Vec3f focal, cameraPos; osg::Vec3f focal, cameraPos;
@ -1428,4 +1408,33 @@ namespace MWRender
{ {
mNavMeshNumber = value; mNavMeshNumber = value;
} }
void RenderingManager::updateNavMesh()
{
if (!mNavMesh->isEnabled())
return;
const auto navMeshes = mNavigator.getNavMeshes();
auto it = navMeshes.begin();
for (std::size_t i = 0; it != navMeshes.end() && i < mNavMeshNumber; ++i)
++it;
if (it == navMeshes.end())
{
mNavMesh->reset();
}
else
{
try
{
const auto locked = it->second.lockConst();
mNavMesh->update(locked->getValue(), mNavMeshNumber, locked->getGeneration(),
locked->getNavMeshRevision(), mNavigator.getSettings());
}
catch (const std::exception& e)
{
Log(Debug::Error) << "NavMesh render update exception: " << e.what();
}
}
}
} }

@ -241,6 +241,8 @@ namespace MWRender
void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h);
void updateNavMesh();
osg::ref_ptr<osgUtil::IntersectionVisitor> getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); osg::ref_ptr<osgUtil::IntersectionVisitor> getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors);
osg::ref_ptr<osgUtil::IntersectionVisitor> mIntersectionVisitor; osg::ref_ptr<osgUtil::IntersectionVisitor> mIntersectionVisitor;

@ -431,6 +431,12 @@ namespace MWScript
std::string effect = runtime.getStringLiteral(runtime[0].mInteger); std::string effect = runtime.getStringLiteral(runtime[0].mInteger);
runtime.pop(); runtime.pop();
if (!ptr.getClass().isActor())
{
runtime.push(0);
return;
}
char *end; char *end;
long key = strtol(effect.c_str(), &end, 10); long key = strtol(effect.c_str(), &end, 10);
if(key < 0 || key > 32767 || *end != '\0') if(key < 0 || key > 32767 || *end != '\0')
@ -659,6 +665,12 @@ namespace MWScript
std::string id = runtime.getStringLiteral(runtime[0].mInteger); std::string id = runtime.getStringLiteral(runtime[0].mInteger);
runtime.pop(); runtime.pop();
if (!ptr.getClass().isActor())
{
runtime.push(0);
return;
}
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
runtime.push(stats.getActiveSpells().isSpellActive(id) || stats.getSpells().isSpellActive(id)); runtime.push(stats.getActiveSpells().isSpellActive(id) || stats.getSpells().isSpellActive(id));
} }

@ -536,7 +536,7 @@ namespace MWScript
Interpreter::Type_Integer value = 0; Interpreter::Type_Integer value = 0;
if (ptr.getClass().getCreatureStats(ptr).getSpells().hasSpell(id)) if (ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getSpells().hasSpell(id))
value = 1; value = 1;
runtime.push (value); runtime.push (value);

@ -4,6 +4,7 @@
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <array> #include <array>
#include <atomic>
#include <stdint.h> #include <stdint.h>
@ -279,7 +280,7 @@ private:
std::unique_ptr<Sound_Loudness> mLoudnessAnalyzer; std::unique_ptr<Sound_Loudness> mLoudnessAnalyzer;
volatile bool mIsFinished; std::atomic<bool> mIsFinished;
void updateAll(bool local); void updateAll(bool local);
@ -313,7 +314,7 @@ struct OpenAL_Output::StreamThread : public OpenThreads::Thread {
typedef std::vector<OpenAL_SoundStream*> StreamVec; typedef std::vector<OpenAL_SoundStream*> StreamVec;
StreamVec mStreams; StreamVec mStreams;
volatile bool mQuitNow; std::atomic<bool> mQuitNow;
OpenThreads::Mutex mMutex; OpenThreads::Mutex mMutex;
OpenThreads::Condition mCondVar; OpenThreads::Condition mCondVar;

@ -58,10 +58,8 @@ void MWState::Character::addSlot (const ESM::SavedGame& profile)
int i=0; int i=0;
while (boost::filesystem::exists(slot.mPath)) while (boost::filesystem::exists(slot.mPath))
{ {
std::ostringstream test; const std::string test = stream.str() + " - " + std::to_string(++i);
test << stream.str(); slot.mPath = mPath / (test + ext);
test << " - " << ++i;
slot.mPath = mPath / (test.str() + ext);
} }
slot.mProfile = profile; slot.mProfile = profile;

@ -1,5 +1,8 @@
#include "cellpreloader.hpp" #include "cellpreloader.hpp"
#include <atomic>
#include <limits>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -159,7 +162,7 @@ namespace MWWorld
MWRender::LandManager* mLandManager; MWRender::LandManager* mLandManager;
bool mPreloadInstances; bool mPreloadInstances;
volatile bool mAbort; std::atomic<bool> mAbort;
osg::ref_ptr<Terrain::View> mTerrainView; osg::ref_ptr<Terrain::View> mTerrainView;
@ -249,7 +252,7 @@ namespace MWWorld
{ {
// throw out oldest cell to make room // throw out oldest cell to make room
PreloadMap::iterator oldestCell = mPreloadCells.begin(); PreloadMap::iterator oldestCell = mPreloadCells.begin();
double oldestTimestamp = DBL_MAX; double oldestTimestamp = std::numeric_limits<double>::max();
double threshold = 1.0; // seconds double threshold = 1.0; // seconds
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it) for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it)
{ {
@ -392,7 +395,7 @@ namespace MWWorld
} }
private: private:
volatile bool mAbort; std::atomic<bool> mAbort;
std::vector<osg::ref_ptr<Terrain::View> > mTerrainViews; std::vector<osg::ref_ptr<Terrain::View> > mTerrainViews;
Terrain::World* mWorld; Terrain::World* mWorld;
std::vector<osg::Vec3f> mPreloadPositions; std::vector<osg::Vec3f> mPreloadPositions;

@ -140,8 +140,6 @@ void ESMStore::setUp(bool validateRecords)
mMagicEffects.setUp(); mMagicEffects.setUp();
mAttributes.setUp(); mAttributes.setUp();
mDialogs.setUp(); mDialogs.setUp();
mStatics.setUp();
mDoors.setUp();
if (validateRecords) if (validateRecords)
validate(); validate();

@ -170,19 +170,19 @@ namespace MWWorld
/// Insert a custom record (i.e. with a generated ID that will not clash will pre-existing records) /// Insert a custom record (i.e. with a generated ID that will not clash will pre-existing records)
template <class T> template <class T>
const T *insert(const T &x) { const T *insert(const T &x)
std::ostringstream id; {
id << "$dynamic" << mDynamicCount++; const std::string id = "$dynamic" + std::to_string(mDynamicCount++);
Store<T> &store = const_cast<Store<T> &>(get<T>()); Store<T> &store = const_cast<Store<T> &>(get<T>());
if (store.search(id.str()) != 0) { if (store.search(id) != 0)
std::ostringstream msg; {
msg << "Try to override existing record '" << id.str() << "'"; const std::string msg = "Try to override existing record '" + id + "'";
throw std::runtime_error(msg.str()); throw std::runtime_error(msg);
} }
T record = x; T record = x;
record.mId = id.str(); record.mId = id;
T *ptr = store.insert(record); T *ptr = store.insert(record);
for (iterator it = mStores.begin(); it != mStores.end(); ++it) { for (iterator it = mStores.begin(); it != mStores.end(); ++it) {
@ -208,15 +208,15 @@ namespace MWWorld
} }
template <class T> template <class T>
const T *insertStatic(const T &x) { const T *insertStatic(const T &x)
std::ostringstream id; {
id << "$dynamic" << mDynamicCount++; const std::string id = "$dynamic" + std::to_string(mDynamicCount++);
Store<T> &store = const_cast<Store<T> &>(get<T>()); Store<T> &store = const_cast<Store<T> &>(get<T>());
if (store.search(id.str()) != 0) { if (store.search(id) != 0)
std::ostringstream msg; {
msg << "Try to override existing record '" << id.str() << "'"; const std::string msg = "Try to override existing record '" + id + "'";
throw std::runtime_error(msg.str()); throw std::runtime_error(msg);
} }
T record = x; T record = x;
@ -247,20 +247,22 @@ namespace MWWorld
} }
template <> template <>
inline const ESM::NPC *ESMStore::insert<ESM::NPC>(const ESM::NPC &npc) { inline const ESM::NPC *ESMStore::insert<ESM::NPC>(const ESM::NPC &npc)
std::ostringstream id; {
id << "$dynamic" << mDynamicCount++; const std::string id = "$dynamic" + std::to_string(mDynamicCount++);
if (Misc::StringUtils::ciEqual(npc.mId, "player")) { if (Misc::StringUtils::ciEqual(npc.mId, "player"))
{
return mNpcs.insert(npc); return mNpcs.insert(npc);
} else if (mNpcs.search(id.str()) != 0) { }
std::ostringstream msg; else if (mNpcs.search(id) != 0)
msg << "Try to override existing record '" << id.str() << "'"; {
throw std::runtime_error(msg.str()); const std::string msg = "Try to override existing record '" + id + "'";
throw std::runtime_error(msg);
} }
ESM::NPC record = npc; ESM::NPC record = npc;
record.mId = id.str(); record.mId = id;
ESM::NPC *ptr = mNpcs.insert(record); ESM::NPC *ptr = mNpcs.insert(record);
mIds[ptr->mId] = ESM::REC_NPC_; mIds[ptr->mId] = ESM::REC_NPC_;

@ -414,14 +414,6 @@ namespace MWWorld
const ESM::BirthSign* sign = world.getStore().get<ESM::BirthSign>().search (player.mBirthsign); const ESM::BirthSign* sign = world.getStore().get<ESM::BirthSign>().search (player.mBirthsign);
if (!sign) if (!sign)
throw std::runtime_error ("invalid player state record (birthsign does not exist)"); throw std::runtime_error ("invalid player state record (birthsign does not exist)");
// To handle the case where a birth sign was edited in between play sessions (does not yet handle removing the old spells)
// Also needed for ess-imported savegames which do not specify the birtsign spells in the player's spell list.
for (std::vector<std::string>::const_iterator iter (sign->mPowers.mList.begin());
iter!=sign->mPowers.mList.end(); ++iter)
{
getPlayer().getClass().getCreatureStats(getPlayer()).getSpells().add (*iter);
}
} }
mCurrentCrimeId = player.mCurrentCrimeId; mCurrentCrimeId = player.mCurrentCrimeId;

@ -109,11 +109,10 @@ namespace
if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects
{ {
std::ostringstream ID; const std::string ID = "VFX_Multiple" + std::to_string(effects->mList.size());
ID << "VFX_Multiple" << effects->mList.size();
std::vector<std::string>::iterator it; std::vector<std::string>::iterator it;
it = projectileIDs.begin(); it = projectileIDs.begin();
it = projectileIDs.insert(it, ID.str()); it = projectileIDs.insert(it, ID);
} }
return projectileEffects; return projectileEffects;
} }

@ -9,7 +9,6 @@
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <stdexcept> #include <stdexcept>
#include <sstream>
namespace namespace
{ {
@ -102,10 +101,10 @@ namespace MWWorld
const T *IndexedStore<T>::find(int index) const const T *IndexedStore<T>::find(int index) const
{ {
const T *ptr = search(index); const T *ptr = search(index);
if (ptr == 0) { if (ptr == 0)
std::ostringstream msg; {
msg << T::getRecordType() << " with index " << index << " not found"; const std::string msg = T::getRecordType() + " with index " + std::to_string(index) + " not found";
throw std::runtime_error(msg.str()); throw std::runtime_error(msg);
} }
return ptr; return ptr;
} }
@ -171,10 +170,10 @@ namespace MWWorld
const T *Store<T>::find(const std::string &id) const const T *Store<T>::find(const std::string &id) const
{ {
const T *ptr = search(id); const T *ptr = search(id);
if (ptr == 0) { if (ptr == 0)
std::ostringstream msg; {
msg << T::getRecordType() << " '" << id << "' not found"; const std::string msg = T::getRecordType() + " '" + id + "' not found";
throw std::runtime_error(msg.str()); throw std::runtime_error(msg);
} }
return ptr; return ptr;
} }
@ -184,14 +183,13 @@ namespace MWWorld
const T *ptr = searchRandom(id); const T *ptr = searchRandom(id);
if(ptr == 0) if(ptr == 0)
{ {
std::ostringstream msg; const std::string msg = T::getRecordType() + " starting with '" + id + "' not found";
msg << T::getRecordType() << " starting with '"<<id<<"' not found"; throw std::runtime_error(msg);
throw std::runtime_error(msg.str());
} }
return ptr; return ptr;
} }
template<typename T> template<typename T>
RecordId Store<T>::load(ESM::ESMReader &esm) RecordId Store<T>::load(ESM::ESMReader &esm)
{ {
T record; T record;
bool isDeleted = false; bool isDeleted = false;
@ -364,10 +362,10 @@ namespace MWWorld
const ESM::LandTexture *Store<ESM::LandTexture>::find(size_t index, size_t plugin) const const ESM::LandTexture *Store<ESM::LandTexture>::find(size_t index, size_t plugin) const
{ {
const ESM::LandTexture *ptr = search(index, plugin); const ESM::LandTexture *ptr = search(index, plugin);
if (ptr == 0) { if (ptr == 0)
std::ostringstream msg; {
msg << "Land texture with index " << index << " not found"; const std::string msg = "Land texture with index " + std::to_string(index) + " not found";
throw std::runtime_error(msg.str()); throw std::runtime_error(msg);
} }
return ptr; return ptr;
} }
@ -456,10 +454,10 @@ namespace MWWorld
const ESM::Land *Store<ESM::Land>::find(int x, int y) const const ESM::Land *Store<ESM::Land>::find(int x, int y) const
{ {
const ESM::Land *ptr = search(x, y); const ESM::Land *ptr = search(x, y);
if (ptr == 0) { if (ptr == 0)
std::ostringstream msg; {
msg << "Land at (" << x << ", " << y << ") not found"; const std::string msg = "Land at (" + std::to_string(x) + ", " + std::to_string(y) + ") not found";
throw std::runtime_error(msg.str()); throw std::runtime_error(msg);
} }
return ptr; return ptr;
} }
@ -591,20 +589,20 @@ namespace MWWorld
const ESM::Cell *Store<ESM::Cell>::find(const std::string &id) const const ESM::Cell *Store<ESM::Cell>::find(const std::string &id) const
{ {
const ESM::Cell *ptr = search(id); const ESM::Cell *ptr = search(id);
if (ptr == 0) { if (ptr == 0)
std::ostringstream msg; {
msg << "Cell '" << id << "' not found"; const std::string msg = "Cell '" + id + "' not found";
throw std::runtime_error(msg.str()); throw std::runtime_error(msg);
} }
return ptr; return ptr;
} }
const ESM::Cell *Store<ESM::Cell>::find(int x, int y) const const ESM::Cell *Store<ESM::Cell>::find(int x, int y) const
{ {
const ESM::Cell *ptr = search(x, y); const ESM::Cell *ptr = search(x, y);
if (ptr == 0) { if (ptr == 0)
std::ostringstream msg; {
msg << "Exterior at (" << x << ", " << y << ") not found"; const std::string msg = "Exterior at (" + std::to_string(x) + ", " + std::to_string(y) + ") not found";
throw std::runtime_error(msg.str()); throw std::runtime_error(msg);
} }
return ptr; return ptr;
} }
@ -784,13 +782,10 @@ namespace MWWorld
} }
ESM::Cell *Store<ESM::Cell>::insert(const ESM::Cell &cell) ESM::Cell *Store<ESM::Cell>::insert(const ESM::Cell &cell)
{ {
if (search(cell) != 0) { if (search(cell) != 0)
std::ostringstream msg; {
msg << "Failed to create "; const std::string cellType = (cell.isExterior()) ? "exterior" : "interior";
msg << ((cell.isExterior()) ? "exterior" : "interior"); throw std::runtime_error("Failed to create " + cellType + " cell");
msg << " cell";
throw std::runtime_error(msg.str());
} }
ESM::Cell *ptr; ESM::Cell *ptr;
if (cell.isExterior()) { if (cell.isExterior()) {
@ -931,9 +926,8 @@ namespace MWWorld
const ESM::Pathgrid* pathgrid = search(x,y); const ESM::Pathgrid* pathgrid = search(x,y);
if (!pathgrid) if (!pathgrid)
{ {
std::ostringstream msg; const std::string msg = "Pathgrid in cell '" + std::to_string(x) + " " + std::to_string(y) + "' not found";
msg << "Pathgrid in cell '" << x << " " << y << "' not found"; throw std::runtime_error(msg);
throw std::runtime_error(msg.str());
} }
return pathgrid; return pathgrid;
} }
@ -942,9 +936,8 @@ namespace MWWorld
const ESM::Pathgrid* pathgrid = search(name); const ESM::Pathgrid* pathgrid = search(name);
if (!pathgrid) if (!pathgrid)
{ {
std::ostringstream msg; const std::string msg = "Pathgrid in cell '" + name + "' not found";
msg << "Pathgrid in cell '" << name << "' not found"; throw std::runtime_error(msg);
throw std::runtime_error(msg.str());
} }
return pathgrid; return pathgrid;
} }
@ -998,23 +991,22 @@ namespace MWWorld
const ESM::Attribute *Store<ESM::Attribute>::find(size_t index) const const ESM::Attribute *Store<ESM::Attribute>::find(size_t index) const
{ {
const ESM::Attribute *ptr = search(index); const ESM::Attribute *ptr = search(index);
if (ptr == 0) { if (ptr == 0)
std::ostringstream msg; {
msg << "Attribute with index " << index << " not found"; const std::string msg = "Attribute with index " + std::to_string(index) + " not found";
throw std::runtime_error(msg.str()); throw std::runtime_error(msg);
} }
return ptr; return ptr;
} }
void Store<ESM::Attribute>::setUp() void Store<ESM::Attribute>::setUp()
{ {
for (int i = 0; i < ESM::Attribute::Length; ++i) { for (int i = 0; i < ESM::Attribute::Length; ++i)
mStatic.push_back( {
ESM::Attribute( ESM::Attribute newAttribute;
ESM::Attribute::sAttributeIds[i], newAttribute.mId = ESM::Attribute::sAttributeIds[i];
ESM::Attribute::sGmstAttributeIds[i], newAttribute.mName = ESM::Attribute::sGmstAttributeIds[i];
ESM::Attribute::sGmstAttributeDescIds[i] newAttribute.mDescription = ESM::Attribute::sGmstAttributeDescIds[i];
) mStatic.push_back(newAttribute);
);
} }
} }
size_t Store<ESM::Attribute>::getSize() const size_t Store<ESM::Attribute>::getSize() const
@ -1054,54 +1046,6 @@ namespace MWWorld
} }
} }
template<>
void Store<ESM::Static>::setUp()
{
// Load default marker definitions, if game files do not have them for some reason
std::pair<std::string, std::string> markers[] = {
std::make_pair("divinemarker", "marker_divine.nif"),
std::make_pair("doormarker", "marker_arrow.nif"),
std::make_pair("northmarker", "marker_north.nif"),
std::make_pair("templemarker", "marker_temple.nif"),
std::make_pair("travelmarker", "marker_travel.nif")
};
for (const std::pair<std::string, std::string> marker : markers)
{
if (search(marker.first) == 0)
{
ESM::Static newMarker = ESM::Static(marker.first, marker.second);
std::pair<typename Static::iterator, bool> ret = mStatic.insert(std::make_pair(marker.first, newMarker));
if (ret.first != mStatic.end())
{
mShared.push_back(&ret.first->second);
}
}
}
}
template<>
void Store<ESM::Door>::setUp()
{
// Load default Door type marker definitions
std::pair<std::string, std::string> markers[] = {
std::make_pair("prisonmarker", "marker_prison.nif")
};
for (const std::pair<std::string, std::string> marker : markers)
{
if (search(marker.first) == 0)
{
ESM::Door newMarker = ESM::Door(marker.first, std::string(), marker.second, std::string(), std::string(), std::string());
std::pair<typename Static::iterator, bool> ret = mStatic.insert(std::make_pair(marker.first, newMarker));
if (ret.first != mStatic.end())
{
mShared.push_back(&ret.first->second);
}
}
}
}
template <> template <>
inline RecordId Store<ESM::Dialogue>::load(ESM::ESMReader &esm) { inline RecordId Store<ESM::Dialogue>::load(ESM::ESMReader &esm) {
// The original letter case of a dialogue ID is saved, because it's printed // The original letter case of a dialogue ID is saved, because it's printed

@ -387,10 +387,8 @@ namespace MWWorld
void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const
{ {
// Active cells could have a dirty fog of war, sync it to the CellStore first // Active cells could have a dirty fog of war, sync it to the CellStore first
for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); for (CellStore* cellstore : mWorldScene->getActiveCells())
iter!=mWorldScene->getActiveCells().end(); ++iter)
{ {
CellStore* cellstore = *iter;
MWBase::Environment::get().getWindowManager()->writeFog(cellstore); MWBase::Environment::get().getWindowManager()->writeFog(cellstore);
} }
@ -494,6 +492,17 @@ namespace MWWorld
gmst["iWereWolfBounty"] = ESM::Variant(1000); gmst["iWereWolfBounty"] = ESM::Variant(1000);
gmst["fCombatDistanceWerewolfMod"] = ESM::Variant(0.3f); gmst["fCombatDistanceWerewolfMod"] = ESM::Variant(0.3f);
for (const std::pair<std::string, ESM::Variant> &params : gmst)
{
if (!mStore.get<ESM::GameSetting>().search(params.first))
{
ESM::GameSetting record;
record.mId = params.first;
record.mValue = params.second;
mStore.insertStatic(record);
}
}
std::map<std::string, ESM::Variant> globals; std::map<std::string, ESM::Variant> globals;
// vanilla Morrowind does not define dayspassed. // vanilla Morrowind does not define dayspassed.
globals["dayspassed"] = ESM::Variant(1); // but the addons start counting at 1 :( globals["dayspassed"] = ESM::Variant(1); // but the addons start counting at 1 :(
@ -513,25 +522,47 @@ namespace MWWorld
globals["crimegoldturnin"] = ESM::Variant(0); globals["crimegoldturnin"] = ESM::Variant(0);
globals["pchasturnin"] = ESM::Variant(0); globals["pchasturnin"] = ESM::Variant(0);
for (std::map<std::string, ESM::Variant>::iterator it = gmst.begin(); it != gmst.end(); ++it) for (const std::pair<std::string, ESM::Variant> &params : globals)
{ {
if (!mStore.get<ESM::GameSetting>().search(it->first)) if (!mStore.get<ESM::Global>().search(params.first))
{ {
ESM::GameSetting setting; ESM::Global record;
setting.mId = it->first; record.mId = params.first;
setting.mValue = it->second; record.mValue = params.second;
mStore.insertStatic(setting); mStore.insertStatic(record);
} }
} }
for (std::map<std::string, ESM::Variant>::iterator it = globals.begin(); it != globals.end(); ++it) std::map<std::string, std::string> statics;
// Total conversions from SureAI lack marker records
statics["divinemarker"] = "marker_divine.nif";
statics["doormarker"] = "marker_arrow.nif";
statics["northmarker"] = "marker_north.nif";
statics["templemarker"] = "marker_temple.nif";
statics["travelmarker"] = "marker_travel.nif";
for (const std::pair<std::string, std::string> &params : statics)
{ {
if (!mStore.get<ESM::Global>().search(it->first)) if (!mStore.get<ESM::Static>().search(params.first))
{ {
ESM::Global setting; ESM::Static record;
setting.mId = it->first; record.mId = params.first;
setting.mValue = it->second; record.mModel = params.second;
mStore.insertStatic(setting); mStore.insertStatic(record);
}
}
std::map<std::string, std::string> doors;
doors["prisonmarker"] = "marker_prison.nif";
for (const std::pair<std::string, std::string> &params : doors)
{
if (!mStore.get<ESM::Door>().search(params.first))
{
ESM::Door record;
record.mId = params.first;
record.mModel = params.second;
mStore.insertStatic(record);
} }
} }
} }
@ -546,22 +577,19 @@ namespace MWWorld
{ {
// first try named cells // first try named cells
const ESM::Cell *cell = mStore.get<ESM::Cell>().searchExtByName (cellName); const ESM::Cell *cell = mStore.get<ESM::Cell>().searchExtByName (cellName);
if (cell != 0) { if (cell)
return cell; return cell;
}
// didn't work -> now check for regions // didn't work -> now check for regions
const MWWorld::Store<ESM::Region> &regions = mStore.get<ESM::Region>(); for (const ESM::Region &region : mStore.get<ESM::Region>())
MWWorld::Store<ESM::Region>::iterator it = regions.begin();
for (; it != regions.end(); ++it)
{ {
if (Misc::StringUtils::ciEqual(cellName, it->mName)) if (Misc::StringUtils::ciEqual(cellName, region.mName))
{ {
return mStore.get<ESM::Cell>().searchExtByRegion(it->mId); return mStore.get<ESM::Cell>().searchExtByRegion(region.mId);
} }
} }
return 0; return nullptr;
} }
const Fallback::Map *World::getFallback() const const Fallback::Map *World::getFallback() const
@ -670,10 +698,10 @@ namespace MWWorld
if (!cell->getCell()->isExterior() || !cell->getCell()->mName.empty()) if (!cell->getCell()->isExterior() || !cell->getCell()->mName.empty())
return cell->getCell()->mName; return cell->getCell()->mName;
if (const ESM::Region* region = getStore().get<ESM::Region>().search (cell->getCell()->mRegion)) if (const ESM::Region* region = mStore.get<ESM::Region>().search (cell->getCell()->mRegion))
return region->mName; return region->mName;
return getStore().get<ESM::GameSetting>().find ("sDefaultCellname")->mValue.getString(); return mStore.get<ESM::GameSetting>().find ("sDefaultCellname")->mValue.getString();
} }
void World::removeRefScript (MWWorld::RefData *ref) void World::removeRefScript (MWWorld::RefData *ref)
@ -692,11 +720,9 @@ namespace MWWorld
std::string lowerCaseName = Misc::StringUtils::lowerCase(name); std::string lowerCaseName = Misc::StringUtils::lowerCase(name);
for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); for (CellStore* cellstore : mWorldScene->getActiveCells())
iter!=mWorldScene->getActiveCells().end(); ++iter)
{ {
// TODO: caching still doesn't work efficiently here (only works for the one CellStore that the reference is in) // TODO: caching still doesn't work efficiently here (only works for the one CellStore that the reference is in)
CellStore* cellstore = *iter;
Ptr ptr = mCells.getPtr (lowerCaseName, *cellstore, false); Ptr ptr = mCells.getPtr (lowerCaseName, *cellstore, false);
if (!ptr.isEmpty()) if (!ptr.isEmpty())
@ -710,10 +736,8 @@ namespace MWWorld
return ret; return ret;
} }
for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); for (CellStore* cellstore : mWorldScene->getActiveCells())
iter!=mWorldScene->getActiveCells().end(); ++iter)
{ {
CellStore* cellstore = *iter;
Ptr ptr = cellstore->searchInContainer(lowerCaseName); Ptr ptr = cellstore->searchInContainer(lowerCaseName);
if (!ptr.isEmpty()) if (!ptr.isEmpty())
return ptr; return ptr;
@ -770,15 +794,14 @@ namespace MWWorld
if (ptr.getContainerStore() == &player.getClass().getContainerStore(player)) if (ptr.getContainerStore() == &player.getClass().getContainerStore(player))
return player; return player;
const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); for (CellStore* cellstore : mWorldScene->getActiveCells())
for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt)
{ {
FindContainerVisitor visitor(ptr); FindContainerVisitor visitor(ptr);
(*cellIt)->forEachType<ESM::Container>(visitor); cellstore->forEachType<ESM::Container>(visitor);
if (visitor.mResult.isEmpty()) if (visitor.mResult.isEmpty())
(*cellIt)->forEachType<ESM::Creature>(visitor); cellstore->forEachType<ESM::Creature>(visitor);
if (visitor.mResult.isEmpty()) if (visitor.mResult.isEmpty())
(*cellIt)->forEachType<ESM::NPC>(visitor); cellstore->forEachType<ESM::NPC>(visitor);
if (!visitor.mResult.isEmpty()) if (!visitor.mResult.isEmpty())
return visitor.mResult; return visitor.mResult;
@ -982,7 +1005,7 @@ namespace MWWorld
"sMonthHeartfire", "sMonthFrostfall", "sMonthSunsdusk", "sMonthEveningstar" "sMonthHeartfire", "sMonthFrostfall", "sMonthSunsdusk", "sMonthEveningstar"
}; };
return getStore().get<ESM::GameSetting>().find (monthNames[month])->mValue.getString(); return mStore.get<ESM::GameSetting>().find (monthNames[month])->mValue.getString();
} }
TimeStamp World::getTimeStamp() const TimeStamp World::getTimeStamp() const
@ -1071,7 +1094,7 @@ namespace MWWorld
if (mActivationDistanceOverride >= 0) if (mActivationDistanceOverride >= 0)
return static_cast<float>(mActivationDistanceOverride); return static_cast<float>(mActivationDistanceOverride);
static const int iMaxActivateDist = getStore().get<ESM::GameSetting>().find("iMaxActivateDist")->mValue.getInteger(); static const int iMaxActivateDist = mStore.get<ESM::GameSetting>().find("iMaxActivateDist")->mValue.getInteger();
return static_cast<float>(iMaxActivateDist); return static_cast<float>(iMaxActivateDist);
} }
@ -1291,7 +1314,10 @@ namespace MWWorld
{ {
mRendering->moveObject(newPtr, vec); mRendering->moveObject(newPtr, vec);
if (movePhysics) if (movePhysics)
{
mPhysics->updatePosition(newPtr); mPhysics->updatePosition(newPtr);
mPhysics->updatePtr(ptr, newPtr);
}
} }
if (isPlayer) if (isPlayer)
{ {
@ -1784,7 +1810,6 @@ namespace MWWorld
if (!paused) if (!paused)
{ {
doPhysics (duration);
updateNavigator(); updateNavigator();
} }
@ -1804,6 +1829,14 @@ namespace MWWorld
} }
} }
void World::updatePhysics (float duration, bool paused)
{
if (!paused)
{
doPhysics (duration);
}
}
void World::updatePlayer() void World::updatePlayer()
{ {
MWWorld::Ptr player = getPlayerPtr(); MWWorld::Ptr player = getPlayerPtr();
@ -1836,7 +1869,7 @@ namespace MWWorld
bool swimming = isSwimming(player); bool swimming = isSwimming(player);
bool flying = isFlying(player); bool flying = isFlying(player);
static const float i1stPersonSneakDelta = getStore().get<ESM::GameSetting>().find("i1stPersonSneakDelta")->mValue.getFloat(); static const float i1stPersonSneakDelta = mStore.get<ESM::GameSetting>().find("i1stPersonSneakDelta")->mValue.getFloat();
if (sneaking && !swimming && !flying) if (sneaking && !swimming && !flying)
mRendering->getCamera()->setSneakOffset(i1stPersonSneakDelta); mRendering->getCamera()->setSneakOffset(i1stPersonSneakDelta);
else else
@ -2405,9 +2438,8 @@ namespace MWWorld
player.getClass().getInventoryStore(player).setInvListener(anim, player); player.getClass().getInventoryStore(player).setInvListener(anim, player);
player.getClass().getInventoryStore(player).setContListener(anim); player.getClass().getInventoryStore(player).setContListener(anim);
scaleObject(getPlayerPtr(), 1.f); // apply race height scaleObject(player, player.getCellRef().getScale()); // apply race height
rotateObject(player, 0.f, 0.f, 0.f, true);
rotateObject(getPlayerPtr(), 0.f, 0.f, 0.f, true);
MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr()); MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr());
MWBase::Environment::get().getMechanicsManager()->watchActor(getPlayerPtr()); MWBase::Environment::get().getMechanicsManager()->watchActor(getPlayerPtr());
@ -2535,17 +2567,16 @@ namespace MWWorld
std::vector<MWWorld::Ptr> actors; std::vector<MWWorld::Ptr> actors;
mPhysics->getActorsStandingOn(object, actors); mPhysics->getActorsStandingOn(object, actors);
for (std::vector<MWWorld::Ptr>::iterator it = actors.begin(); it != actors.end(); ++it) for (const Ptr &actor : actors)
{ {
MWWorld::Ptr actor = *it;
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
if (stats.isDead()) if (stats.isDead())
continue; continue;
mPhysics->markAsNonSolid (object); mPhysics->markAsNonSolid (object);
if (actor == getPlayerPtr() && MWBase::Environment::get().getWorld()->getGodModeState()) if (actor == getPlayerPtr() && mGodMode)
return; continue;
MWMechanics::DynamicStat<float> health = stats.getHealth(); MWMechanics::DynamicStat<float> health = stats.getHealth();
health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration());
@ -2567,19 +2598,18 @@ namespace MWWorld
if (MWBase::Environment::get().getWindowManager()->isGuiMode()) if (MWBase::Environment::get().getWindowManager()->isGuiMode())
return; return;
std::vector<MWWorld::Ptr> actors; std::vector<Ptr> actors;
mPhysics->getActorsCollidingWith(object, actors); mPhysics->getActorsCollidingWith(object, actors);
for (std::vector<MWWorld::Ptr>::iterator it = actors.begin(); it != actors.end(); ++it) for (const Ptr &actor : actors)
{ {
MWWorld::Ptr actor = *it;
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
if (stats.isDead()) if (stats.isDead())
continue; continue;
mPhysics->markAsNonSolid (object); mPhysics->markAsNonSolid (object);
if (actor == getPlayerPtr() && MWBase::Environment::get().getWorld()->getGodModeState()) if (actor == getPlayerPtr() && mGodMode)
return; continue;
MWMechanics::DynamicStat<float> health = stats.getHealth(); MWMechanics::DynamicStat<float> health = stats.getHealth();
health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration());
@ -2649,11 +2679,10 @@ namespace MWWorld
void World::getContainersOwnedBy (const MWWorld::ConstPtr& owner, std::vector<MWWorld::Ptr>& out) void World::getContainersOwnedBy (const MWWorld::ConstPtr& owner, std::vector<MWWorld::Ptr>& out)
{ {
const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); for (CellStore* cellstore : mWorldScene->getActiveCells())
for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt)
{ {
GetContainersOwnedByVisitor visitor (owner, out); GetContainersOwnedByVisitor visitor (owner, out);
(*cellIt)->forEachType<ESM::Container>(visitor); cellstore->forEachType<ESM::Container>(visitor);
} }
} }
@ -2671,15 +2700,14 @@ namespace MWWorld
void World::getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector<MWWorld::Ptr>& out) void World::getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector<MWWorld::Ptr>& out)
{ {
const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); for (CellStore* cellstore : mWorldScene->getActiveCells())
for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt)
{ {
ListObjectsVisitor visitor; ListObjectsVisitor visitor;
(*cellIt)->forEach(visitor); cellstore->forEach(visitor);
for (std::vector<MWWorld::Ptr>::iterator it = visitor.mObjects.begin(); it != visitor.mObjects.end(); ++it) for (const Ptr &object : visitor.mObjects)
if (Misc::StringUtils::ciEqual(it->getCellRef().getOwner(), npc.getCellRef().getRefId())) if (Misc::StringUtils::ciEqual(object.getCellRef().getOwner(), npc.getCellRef().getRefId()))
out.push_back(*it); out.push_back(object);
} }
} }
@ -2720,28 +2748,21 @@ namespace MWWorld
bool World::findInteriorPosition(const std::string &name, ESM::Position &pos) bool World::findInteriorPosition(const std::string &name, ESM::Position &pos)
{ {
typedef MWWorld::CellRefList<ESM::Door>::List DoorList;
typedef MWWorld::CellRefList<ESM::Static>::List StaticList;
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
pos.pos[0] = pos.pos[1] = pos.pos[2] = 0; pos.pos[0] = pos.pos[1] = pos.pos[2] = 0;
MWWorld::CellStore *cellStore = getInterior(name); MWWorld::CellStore *cellStore = getInterior(name);
if (0 == cellStore) { if (!cellStore)
return false; return false;
}
std::vector<const MWWorld::CellRef *> sortedDoors; std::vector<const MWWorld::CellRef *> sortedDoors;
const DoorList &doors = cellStore->getReadOnlyDoors().mList; for (const MWWorld::LiveCellRef<ESM::Door>& door : cellStore->getReadOnlyDoors().mList)
for (DoorList::const_iterator it = doors.begin(); it != doors.end(); ++it)
{ {
if (!it->mRef.getTeleport()) if (!door.mRef.getTeleport())
{
continue; continue;
}
sortedDoors.push_back(&it->mRef); sortedDoors.push_back(&door.mRef);
} }
// Sort teleporting doors alphabetically, first by ID, then by destination cell to make search consistent // Sort teleporting doors alphabetically, first by ID, then by destination cell to make search consistent
@ -2753,44 +2774,43 @@ namespace MWWorld
return lhs->getDestCell() < rhs->getDestCell(); return lhs->getDestCell() < rhs->getDestCell();
}); });
for (std::vector<const MWWorld::CellRef *>::const_iterator it = sortedDoors.begin(); it != sortedDoors.end(); ++it) for (const MWWorld::CellRef* door : sortedDoors)
{ {
MWWorld::CellStore *source = 0; MWWorld::CellStore *source = nullptr;
// door to exterior // door to exterior
if ((*it)->getDestCell().empty()) if (door->getDestCell().empty())
{ {
int x, y; int x, y;
ESM::Position doorDest = (*it)->getDoorDest(); ESM::Position doorDest = door->getDoorDest();
positionToIndex(doorDest.pos[0], doorDest.pos[1], x, y); positionToIndex(doorDest.pos[0], doorDest.pos[1], x, y);
source = getExterior(x, y); source = getExterior(x, y);
} }
// door to interior // door to interior
else else
{ {
source = getInterior((*it)->getDestCell()); source = getInterior(door->getDestCell());
} }
if (0 != source) if (source)
{ {
// Find door leading to our current teleport door // Find door leading to our current teleport door
// and use its destination to position inside cell. // and use its destination to position inside cell.
const DoorList &destinationDoors = source->getReadOnlyDoors().mList; for (const MWWorld::LiveCellRef<ESM::Door>& destDoor : source->getReadOnlyDoors().mList)
for (DoorList::const_iterator jt = destinationDoors.begin(); jt != destinationDoors.end(); ++jt)
{ {
if ((*it)->getTeleport() && if (Misc::StringUtils::ciEqual(name, destDoor.mRef.getDestCell()))
Misc::StringUtils::ciEqual(name, jt->mRef.getDestCell()))
{ {
/// \note Using _any_ door pointed to the interior, /// \note Using _any_ door pointed to the interior,
/// not the one pointed to current door. /// not the one pointed to current door.
pos = jt->mRef.getDoorDest(); pos = destDoor.mRef.getDoorDest();
return true; return true;
} }
} }
} }
} }
// Fall back to the first static location. // Fall back to the first static location.
const StaticList &statics = cellStore->getReadOnlyStatics().mList; const MWWorld::CellRefList<ESM::Static>::List &statics = cellStore->getReadOnlyStatics().mList;
if ( statics.begin() != statics.end() ) { if (!statics.empty())
{
pos = statics.begin()->mRef.getPosition(); pos = statics.begin()->mRef.getPosition();
return true; return true;
} }
@ -2885,22 +2905,21 @@ namespace MWWorld
void World::loadContentFiles(const Files::Collections& fileCollections, void World::loadContentFiles(const Files::Collections& fileCollections,
const std::vector<std::string>& content, ContentLoader& contentLoader) const std::vector<std::string>& content, ContentLoader& contentLoader)
{ {
std::vector<std::string>::const_iterator it(content.begin()); int idx = 0;
std::vector<std::string>::const_iterator end(content.end()); for (const std::string &file : content)
for (int idx = 0; it != end; ++it, ++idx)
{ {
boost::filesystem::path filename(*it); boost::filesystem::path filename(file);
const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string());
if (col.doesExist(*it)) if (col.doesExist(file))
{ {
contentLoader.load(col.getPath(*it), idx); contentLoader.load(col.getPath(file), idx);
} }
else else
{ {
std::stringstream msg; std::string message = "Failed loading " + file + ": the content file does not exist";
msg << "Failed loading " << *it << ": the content file does not exist"; throw std::runtime_error(message);
throw std::runtime_error(msg.str());
} }
idx++;
} }
} }
@ -2916,10 +2935,10 @@ namespace MWWorld
if (!selectedSpell.empty()) if (!selectedSpell.empty())
{ {
const ESM::Spell* spell = getStore().get<ESM::Spell>().find(selectedSpell); const ESM::Spell* spell = mStore.get<ESM::Spell>().find(selectedSpell);
// Check mana // Check mana
bool godmode = (isPlayer && getGodModeState()); bool godmode = (isPlayer && mGodMode);
MWMechanics::DynamicStat<float> magicka = stats.getMagicka(); MWMechanics::DynamicStat<float> magicka = stats.getMagicka();
if (magicka.getCurrent() < spell->mData.mCost && !godmode) if (magicka.getCurrent() < spell->mData.mCost && !godmode)
{ {
@ -2957,7 +2976,7 @@ namespace MWWorld
if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell) if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell)
stats.getAiSequence().getCombatTargets(targetActors); stats.getAiSequence().getCombatTargets(targetActors);
const float fCombatDistance = getStore().get<ESM::GameSetting>().find("fCombatDistance")->mValue.getFloat(); const float fCombatDistance = mStore.get<ESM::GameSetting>().find("fCombatDistance")->mValue.getFloat();
osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3();
@ -2975,14 +2994,13 @@ namespace MWWorld
// For scripted spells we should not use hit contact // For scripted spells we should not use hit contact
if (manualSpell) if (manualSpell)
{ {
// Actors that are targeted by this actor's Follow or Escort packages also side with them
if (actor != MWMechanics::getPlayer()) if (actor != MWMechanics::getPlayer())
{ {
for (std::list<MWMechanics::AiPackage*>::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) for (const MWMechanics::AiPackage* package : stats.getAiSequence())
{ {
if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCast) if (package->getTypeId() == MWMechanics::AiPackage::TypeIdCast)
{ {
target = (*it)->getTarget(); target = package->getTarget();
break; break;
} }
} }
@ -3040,7 +3058,7 @@ namespace MWWorld
if (!selectedSpell.empty()) if (!selectedSpell.empty())
{ {
const ESM::Spell* spell = getStore().get<ESM::Spell>().find(selectedSpell); const ESM::Spell* spell = mStore.get<ESM::Spell>().find(selectedSpell);
cast.cast(spell); cast.cast(spell);
} }
else if (actor.getClass().hasInventoryStore(actor)) else if (actor.getClass().hasInventoryStore(actor))
@ -3151,17 +3169,14 @@ namespace MWWorld
while ( !nextCells.empty() ) { while ( !nextCells.empty() ) {
currentCells = nextCells; currentCells = nextCells;
nextCells.clear(); nextCells.clear();
for( std::set< std::string >::const_iterator i = currentCells.begin(); i != currentCells.end(); ++i ) { for (const std::string &currentCell : currentCells)
MWWorld::CellStore *next = getInterior( *i ); {
MWWorld::CellStore *next = getInterior(currentCell);
if ( !next ) continue; if ( !next ) continue;
const MWWorld::CellRefList<ESM::Door>& doors = next->getReadOnlyDoors();
const CellRefList<ESM::Door>::List& refList = doors.mList;
// Check if any door in the cell leads to an exterior directly // Check if any door in the cell leads to an exterior directly
for (CellRefList<ESM::Door>::List::const_iterator it = refList.begin(); it != refList.end(); ++it) for (const MWWorld::LiveCellRef<ESM::Door>& ref : next->getReadOnlyDoors().mList)
{ {
const MWWorld::LiveCellRef<ESM::Door>& ref = *it;
if (!ref.mRef.getTeleport()) continue; if (!ref.mRef.getTeleport()) continue;
if (ref.mRef.getDestCell().empty()) if (ref.mRef.getDestCell().empty())
@ -3178,7 +3193,7 @@ namespace MWWorld
} }
} }
checkedCells.insert( *i ); checkedCells.insert(currentCell);
} }
} }
@ -3204,9 +3219,9 @@ namespace MWWorld
while ( !nextCells.empty() ) { while ( !nextCells.empty() ) {
currentCells = nextCells; currentCells = nextCells;
nextCells.clear(); nextCells.clear();
for( std::set< std::string >::const_iterator i = currentCells.begin(); i != currentCells.end(); ++i ) { for (const std::string &cell : currentCells) {
MWWorld::CellStore *next = getInterior( *i ); MWWorld::CellStore *next = getInterior(cell);
checkedCells.insert( *i ); checkedCells.insert(cell);
if ( !next ) continue; if ( !next ) continue;
closestMarker = next->searchConst( id ); closestMarker = next->searchConst( id );
@ -3215,14 +3230,9 @@ namespace MWWorld
return closestMarker; return closestMarker;
} }
const MWWorld::CellRefList<ESM::Door>& doors = next->getReadOnlyDoors();
const CellRefList<ESM::Door>::List& doorList = doors.mList;
// Check if any door in the cell leads to an exterior directly // Check if any door in the cell leads to an exterior directly
for (CellRefList<ESM::Door>::List::const_iterator it = doorList.begin(); it != doorList.end(); ++it) for (const MWWorld::LiveCellRef<ESM::Door>& ref : next->getReadOnlyDoors().mList)
{ {
const MWWorld::LiveCellRef<ESM::Door>& ref = *it;
if (!ref.mRef.getTeleport()) continue; if (!ref.mRef.getTeleport()) continue;
if (ref.mRef.getDestCell().empty()) if (ref.mRef.getDestCell().empty())
@ -3248,15 +3258,14 @@ namespace MWWorld
std::vector<MWWorld::Ptr> markers; std::vector<MWWorld::Ptr> markers;
mCells.getExteriorPtrs(id, markers); mCells.getExteriorPtrs(id, markers);
for (std::vector<MWWorld::Ptr>::iterator it2 = markers.begin(); it2 != markers.end(); ++it2) for (const Ptr& marker : markers)
{ {
ESM::Position pos = it2->getRefData().getPosition(); osg::Vec3f markerPos = marker.getRefData().getPosition().asVec3();
osg::Vec3f markerPos = pos.asVec3();
float distance = (worldPos - markerPos).length2(); float distance = (worldPos - markerPos).length2();
if (distance < closestDistance) if (distance < closestDistance)
{ {
closestDistance = distance; closestDistance = distance;
closestMarker = *it2; closestMarker = marker;
} }
} }
@ -3387,10 +3396,8 @@ namespace MWWorld
AddDetectedReferenceVisitor visitor (out, ptr, type, dist*dist); AddDetectedReferenceVisitor visitor (out, ptr, type, dist*dist);
const Scene::CellStoreCollection& active = mWorldScene->getActiveCells(); for (CellStore* cellStore : mWorldScene->getActiveCells())
for (Scene::CellStoreCollection::const_iterator it = active.begin(); it != active.end(); ++it)
{ {
MWWorld::CellStore* cellStore = *it;
cellStore->forEach(visitor); cellStore->forEach(visitor);
} }
} }
@ -3425,8 +3432,8 @@ namespace MWWorld
int bounty = player.getClass().getNpcStats(player).getBounty(); int bounty = player.getClass().getNpcStats(player).getBounty();
int playerGold = player.getClass().getContainerStore(player).count(ContainerStore::sGoldId); int playerGold = player.getClass().getContainerStore(player).count(ContainerStore::sGoldId);
float fCrimeGoldDiscountMult = getStore().get<ESM::GameSetting>().find("fCrimeGoldDiscountMult")->mValue.getFloat(); static float fCrimeGoldDiscountMult = mStore.get<ESM::GameSetting>().find("fCrimeGoldDiscountMult")->mValue.getFloat();
float fCrimeGoldTurnInMult = getStore().get<ESM::GameSetting>().find("fCrimeGoldTurnInMult")->mValue.getFloat(); static float fCrimeGoldTurnInMult = mStore.get<ESM::GameSetting>().find("fCrimeGoldTurnInMult")->mValue.getFloat();
int discount = static_cast<int>(bounty * fCrimeGoldDiscountMult); int discount = static_cast<int>(bounty * fCrimeGoldDiscountMult);
int turnIn = static_cast<int>(bounty * fCrimeGoldTurnInMult); int turnIn = static_cast<int>(bounty * fCrimeGoldTurnInMult);
@ -3491,7 +3498,7 @@ namespace MWWorld
mPlayer->recordCrimeId(); mPlayer->recordCrimeId();
confiscateStolenItems(player); confiscateStolenItems(player);
int iDaysinPrisonMod = getStore().get<ESM::GameSetting>().find("iDaysinPrisonMod")->mValue.getInteger(); static int iDaysinPrisonMod = mStore.get<ESM::GameSetting>().find("iDaysinPrisonMod")->mValue.getInteger();
mDaysInPrison = std::max(1, bounty / iDaysinPrisonMod); mDaysInPrison = std::max(1, bounty / iDaysinPrisonMod);
return; return;
@ -3547,18 +3554,18 @@ namespace MWWorld
void World::spawnRandomCreature(const std::string &creatureList) void World::spawnRandomCreature(const std::string &creatureList)
{ {
const ESM::CreatureLevList* list = getStore().get<ESM::CreatureLevList>().find(creatureList); const ESM::CreatureLevList* list = mStore.get<ESM::CreatureLevList>().find(creatureList);
int iNumberCreatures = getStore().get<ESM::GameSetting>().find("iNumberCreatures")->mValue.getInteger(); static int iNumberCreatures = mStore.get<ESM::GameSetting>().find("iNumberCreatures")->mValue.getInteger();
int numCreatures = 1 + Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures] int numCreatures = 1 + Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures]
for (int i=0; i<numCreatures; ++i) for (int i=0; i<numCreatures; ++i)
{ {
std::string selectedCreature = MWMechanics::getLevelledItem(list, true); std::string selectedCreature = MWMechanics::getLevelledItem(list, true);
if (selectedCreature.empty()) if (selectedCreature.empty())
return; continue;
MWWorld::ManualRef ref(getStore(), selectedCreature, 1); MWWorld::ManualRef ref(mStore, selectedCreature, 1);
safePlaceObject(ref.getPtr(), getPlayerPtr(), getPlayerPtr().getCell(), 0, 220.f); safePlaceObject(ref.getPtr(), getPlayerPtr(), getPlayerPtr().getCell(), 0, 220.f);
} }
@ -3569,27 +3576,8 @@ namespace MWWorld
if (ptr == getPlayerPtr() && Settings::Manager::getBool("hit fader", "GUI")) if (ptr == getPlayerPtr() && Settings::Manager::getBool("hit fader", "GUI"))
return; return;
int type = ptr.getClass().getBloodTexture(ptr); std::string texture = getFallback()->getFallbackString("Blood_Texture_" + std::to_string(ptr.getClass().getBloodTexture(ptr)));
std::string texture; std::string model = "meshes\\" + getFallback()->getFallbackString("Blood_Model_" + std::to_string(Misc::Rng::rollDice(3))); // [0, 2]
switch (type)
{
case 2:
texture = getFallback()->getFallbackString("Blood_Texture_2");
break;
case 1:
texture = getFallback()->getFallbackString("Blood_Texture_1");
break;
case 0:
default:
texture = getFallback()->getFallbackString("Blood_Texture_0");
break;
}
std::stringstream modelName;
modelName << "Blood_Model_";
int roll = Misc::Rng::rollDice(3); // [0, 2]
modelName << roll;
std::string model = "meshes\\" + getFallback()->getFallbackString(modelName.str());
mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false); mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false);
} }
@ -3606,7 +3594,7 @@ namespace MWWorld
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = effects.mList.begin(); for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = effects.mList.begin();
effectIt != effects.mList.end(); ++effectIt) effectIt != effects.mList.end(); ++effectIt)
{ {
const ESM::MagicEffect* effect = getStore().get<ESM::MagicEffect>().find(effectIt->mEffectID); const ESM::MagicEffect* effect = mStore.get<ESM::MagicEffect>().find(effectIt->mEffectID);
if (effectIt->mRange != rangeType || (effectIt->mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor())) if (effectIt->mRange != rangeType || (effectIt->mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor()))
continue; // Not right range type, or not area effect and hit an actor continue; // Not right range type, or not area effect and hit an actor
@ -3620,9 +3608,9 @@ namespace MWWorld
// Spawn the explosion orb effect // Spawn the explosion orb effect
const ESM::Static* areaStatic; const ESM::Static* areaStatic;
if (!effect->mArea.empty()) if (!effect->mArea.empty())
areaStatic = getStore().get<ESM::Static>().find (effect->mArea); areaStatic = mStore.get<ESM::Static>().find (effect->mArea);
else else
areaStatic = getStore().get<ESM::Static>().find ("VFX_DefaultArea"); areaStatic = mStore.get<ESM::Static>().find ("VFX_DefaultArea");
std::string texture = effect->mParticle; std::string texture = effect->mParticle;
@ -3650,13 +3638,13 @@ namespace MWWorld
std::vector<MWWorld::Ptr> objects; std::vector<MWWorld::Ptr> objects;
MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(
origin, feetToGameUnits(static_cast<float>(effectIt->mArea)), objects); origin, feetToGameUnits(static_cast<float>(effectIt->mArea)), objects);
for (std::vector<MWWorld::Ptr>::iterator affected = objects.begin(); affected != objects.end(); ++affected) for (const Ptr& affected : objects)
{ {
// Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing range. // Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing range.
if (affected->getClass().isActor() && !isActorCollisionEnabled(*affected)) if (affected.getClass().isActor() && !isActorCollisionEnabled(affected))
continue; continue;
toApply[*affected].push_back(*effectIt); toApply[affected].push_back(*effectIt);
} }
} }
@ -3716,10 +3704,8 @@ namespace MWWorld
}; };
void World::resetActors() void World::resetActors()
{ {
for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); for (CellStore* cellstore : mWorldScene->getActiveCells())
iter!=mWorldScene->getActiveCells().end(); ++iter)
{ {
CellStore* cellstore = *iter;
ResetActorsVisitor visitor; ResetActorsVisitor visitor;
cellstore->forEach(visitor); cellstore->forEach(visitor);
} }

@ -480,6 +480,7 @@ namespace MWWorld
/// \return pointer to created record /// \return pointer to created record
void update (float duration, bool paused) override; void update (float duration, bool paused) override;
void updatePhysics (float duration, bool paused) override;
void updateWindowManager () override; void updateWindowManager () override;

@ -40,4 +40,13 @@ if (GTEST_FOUND AND GMOCK_FOUND)
add_definitions(--coverage) add_definitions(--coverage)
target_link_libraries(openmw_test_suite gcov) target_link_libraries(openmw_test_suite gcov)
endif() endif()
if (MSVC)
if (CMAKE_CL_64)
# Debug version of openmw_unit_tests needs increased number of sections beyond 2^16
# just like openmw and openmw-cs
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj")
endif (CMAKE_CL_64)
endif (MSVC)
endif() endif()

@ -1,6 +1,12 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#ifdef WIN32
//we cannot use GTEST_API_ before main if we're building standalone exe application,
//and we're linking GoogleTest / GoogleMock as DLLs and not linking gtest_main / gmock_main
int main(int argc, char **argv) {
#else
GTEST_API_ int main(int argc, char **argv) { GTEST_API_ int main(int argc, char **argv) {
#endif
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS(); return RUN_ALL_TESTS();
} }

@ -856,7 +856,7 @@ QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QSt
if (info.isDir()) { if (info.isDir()) {
if (directories) if (directories)
{ {
if (info.fileName() == fileName) { if (!info.fileName().compare(fileName, Qt::CaseInsensitive)) {
result.append(info.absoluteFilePath()); result.append(info.absoluteFilePath());
} else { } else {
if (recursive) if (recursive)
@ -872,11 +872,11 @@ QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QSt
switch (flags) { switch (flags) {
case Qt::MatchExactly: case Qt::MatchExactly:
if (info.fileName() == fileName) if (!info.fileName().compare(fileName, Qt::CaseInsensitive))
result.append(info.absoluteFilePath()); result.append(info.absoluteFilePath());
break; break;
case Qt::MatchEndsWith: case Qt::MatchEndsWith:
if (info.fileName().endsWith(fileName)) if (info.fileName().endsWith(fileName), Qt::CaseInsensitive)
result.append(info.absoluteFilePath()); result.append(info.absoluteFilePath());
break; break;
} }

@ -33,7 +33,7 @@ add_component_dir (settings
) )
add_component_dir (bsa add_component_dir (bsa
bsa_file bsa_file compressedbsafile memorystream
) )
add_component_dir (vfs add_component_dir (vfs
@ -218,6 +218,7 @@ target_link_libraries(components
${Boost_FILESYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY}
${Boost_THREAD_LIBRARY} ${Boost_THREAD_LIBRARY}
${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_IOSTREAMS_LIBRARY}
${OSG_LIBRARIES} ${OSG_LIBRARIES}
${OPENTHREADS_LIBRARIES} ${OPENTHREADS_LIBRARIES}
${OSGPARTICLE_LIBRARIES} ${OSGPARTICLE_LIBRARIES}
@ -232,6 +233,7 @@ target_link_libraries(components
${SDL2_LIBRARIES} ${SDL2_LIBRARIES}
${OPENGL_gl_LIBRARY} ${OPENGL_gl_LIBRARY}
${MyGUI_LIBRARIES} ${MyGUI_LIBRARIES}
${BSAOPTHASH_LIBRARIES}
RecastNavigation::DebugUtils RecastNavigation::DebugUtils
RecastNavigation::Detour RecastNavigation::Detour
RecastNavigation::Recast RecastNavigation::Recast
@ -239,7 +241,8 @@ target_link_libraries(components
if (WIN32) if (WIN32)
target_link_libraries(components target_link_libraries(components
${Boost_LOCALE_LIBRARY}) ${Boost_LOCALE_LIBRARY}
${Boost_ZLIB_LIBRARY})
endif() endif()
if (USE_QT) if (USE_QT)

@ -35,7 +35,7 @@ using namespace Bsa;
/// Error handling /// Error handling
void BSAFile::fail(const string &msg) void BSAFile::fail(const string &msg)
{ {
throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + filename); throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + mFilename);
} }
/// Read header information from the input source /// Read header information from the input source
@ -71,10 +71,10 @@ void BSAFile::readHeader()
* the beginning of this buffer. * the beginning of this buffer.
* *
*/ */
assert(!isLoaded); assert(!mIsLoaded);
namespace bfs = boost::filesystem; namespace bfs = boost::filesystem;
bfs::ifstream input(bfs::path(filename), std::ios_base::binary); bfs::ifstream input(bfs::path(mFilename), std::ios_base::binary);
// Total archive size // Total archive size
std::streamoff fsize = 0; std::streamoff fsize = 0;
@ -117,8 +117,8 @@ void BSAFile::readHeader()
input.read(reinterpret_cast<char*>(&offsets[0]), 12*filenum); input.read(reinterpret_cast<char*>(&offsets[0]), 12*filenum);
// Read the string table // Read the string table
stringBuf.resize(dirsize-12*filenum); mStringBuf.resize(dirsize-12*filenum);
input.read(&stringBuf[0], stringBuf.size()); input.read(&mStringBuf[0], mStringBuf.size());
// Check our position // Check our position
assert(input.tellg() == std::streampos(12+dirsize)); assert(input.tellg() == std::streampos(12+dirsize));
@ -129,40 +129,40 @@ void BSAFile::readHeader()
size_t fileDataOffset = 12 + dirsize + 8*filenum; size_t fileDataOffset = 12 + dirsize + 8*filenum;
// Set up the the FileStruct table // Set up the the FileStruct table
files.resize(filenum); mFiles.resize(filenum);
for(size_t i=0;i<filenum;i++) for(size_t i=0;i<filenum;i++)
{ {
FileStruct &fs = files[i]; FileStruct &fs = mFiles[i];
fs.fileSize = offsets[i*2]; fs.fileSize = offsets[i*2];
fs.offset = offsets[i*2+1] + fileDataOffset; fs.offset = offsets[i*2+1] + fileDataOffset;
fs.name = &stringBuf[offsets[2*filenum+i]]; fs.name = &mStringBuf[offsets[2*filenum+i]];
if(fs.offset + fs.fileSize > fsize) if(fs.offset + fs.fileSize > fsize)
fail("Archive contains offsets outside itself"); fail("Archive contains offsets outside itself");
// Add the file name to the lookup // Add the file name to the lookup
lookup[fs.name] = i; mLookup[fs.name] = i;
} }
isLoaded = true; mIsLoaded = true;
} }
/// Get the index of a given file name, or -1 if not found /// Get the index of a given file name, or -1 if not found
int BSAFile::getIndex(const char *str) const int BSAFile::getIndex(const char *str) const
{ {
Lookup::const_iterator it = lookup.find(str); Lookup::const_iterator it = mLookup.find(str);
if(it == lookup.end()) if(it == mLookup.end())
return -1; return -1;
int res = it->second; int res = it->second;
assert(res >= 0 && (size_t)res < files.size()); assert(res >= 0 && (size_t)res < mFiles.size());
return res; return res;
} }
/// Open an archive file. /// Open an archive file.
void BSAFile::open(const string &file) void BSAFile::open(const string &file)
{ {
filename = file; mFilename = file;
readHeader(); readHeader();
} }
@ -173,12 +173,12 @@ Files::IStreamPtr BSAFile::getFile(const char *file)
if(i == -1) if(i == -1)
fail("File not found: " + string(file)); fail("File not found: " + string(file));
const FileStruct &fs = files[i]; const FileStruct &fs = mFiles[i];
return Files::openConstrainedFileStream (filename.c_str (), fs.offset, fs.fileSize); return Files::openConstrainedFileStream (mFilename.c_str (), fs.offset, fs.fileSize);
} }
Files::IStreamPtr BSAFile::getFile(const FileStruct *file) Files::IStreamPtr BSAFile::getFile(const FileStruct *file)
{ {
return Files::openConstrainedFileStream (filename.c_str (), file->offset, file->fileSize); return Files::openConstrainedFileStream (mFilename.c_str (), file->offset, file->fileSize);
} }

@ -56,18 +56,18 @@ public:
}; };
typedef std::vector<FileStruct> FileList; typedef std::vector<FileStruct> FileList;
private: protected:
/// Table of files in this archive /// Table of files in this archive
FileList files; FileList mFiles;
/// Filename string buffer /// Filename string buffer
std::vector<char> stringBuf; std::vector<char> mStringBuf;
/// True when an archive has been loaded /// True when an archive has been loaded
bool isLoaded; bool mIsLoaded;
/// Used for error messages /// Used for error messages
std::string filename; std::string mFilename;
/// Case insensitive string comparison /// Case insensitive string comparison
struct iltstr struct iltstr
@ -81,13 +81,16 @@ private:
checks are case insensitive. checks are case insensitive.
*/ */
typedef std::map<const char*, int, iltstr> Lookup; typedef std::map<const char*, int, iltstr> Lookup;
Lookup lookup; Lookup mLookup;
/// Error handling /// Error handling
void fail(const std::string &msg); void fail(const std::string &msg);
/// Read header information from the input source /// Read header information from the input source
void readHeader(); virtual void readHeader();
/// Read header information from the input source
/// Get the index of a given file name, or -1 if not found /// Get the index of a given file name, or -1 if not found
/// @note Thread safe. /// @note Thread safe.
@ -100,7 +103,10 @@ public:
*/ */
BSAFile() BSAFile()
: isLoaded(false) : mIsLoaded(false)
{ }
virtual ~BSAFile()
{ } { }
/// Open an archive file. /// Open an archive file.
@ -112,24 +118,24 @@ public:
*/ */
/// Check if a file exists /// Check if a file exists
bool exists(const char *file) const virtual bool exists(const char *file) const
{ return getIndex(file) != -1; } { return getIndex(file) != -1; }
/** Open a file contained in the archive. Throws an exception if the /** Open a file contained in the archive. Throws an exception if the
file doesn't exist. file doesn't exist.
* @note Thread safe. * @note Thread safe.
*/ */
Files::IStreamPtr getFile(const char *file); virtual Files::IStreamPtr getFile(const char *file);
/** Open a file contained in the archive. /** Open a file contained in the archive.
* @note Thread safe. * @note Thread safe.
*/ */
Files::IStreamPtr getFile(const FileStruct* file); virtual Files::IStreamPtr getFile(const FileStruct* file);
/// Get a list of all files /// Get a list of all files
/// @note Thread safe. /// @note Thread safe.
const FileList &getList() const const FileList &getList() const
{ return files; } { return mFiles; }
}; };
} }

@ -0,0 +1,453 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008-2010 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.sourceforge.net/
This file (compressedbsafile.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
Compressed BSA stuff added by cc9cii 2018
*/
#include "compressedbsafile.hpp"
#include <stdexcept>
#include <cassert>
#include <boost/scoped_array.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/device/array.hpp>
#include <components/bsa/memorystream.hpp>
namespace Bsa
{
//special marker for invalid records,
//equal to max uint32_t value
const uint32_t CompressedBSAFile::sInvalidOffset = std::numeric_limits<uint32_t>::max();
//bit marking compression on file size
const uint32_t CompressedBSAFile::sCompressedFlag = 1u << 30u;
CompressedBSAFile::FileRecord::FileRecord() : size(0), offset(sInvalidOffset)
{ }
bool CompressedBSAFile::FileRecord::isValid() const
{
return offset != sInvalidOffset;
}
bool CompressedBSAFile::FileRecord::isCompressed(bool bsaCompressedByDefault) const
{
bool recordCompressionFlagEnabled = ((size & sCompressedFlag) == sCompressedFlag);
//record is compressed when:
//- bsaCompressedByDefault flag is set and 30th bit is NOT set, or
//- bsaCompressedByDefault flag is NOT set and 30th bit is set
//record is NOT compressed when:
//- bsaCompressedByDefault flag is NOT set and 30th bit is NOT set, or
//- bsaCompressedByDefault flag is set and 30th bit is set
return (bsaCompressedByDefault != recordCompressionFlagEnabled);
}
std::uint32_t CompressedBSAFile::FileRecord::getSizeWithoutCompressionFlag() const {
return size & (~sCompressedFlag);
}
void CompressedBSAFile::getBZString(std::string& str, std::istream& filestream)
{
char size = 0;
filestream.read(&size, 1);
boost::scoped_array<char> buf(new char[size]);
filestream.read(buf.get(), size);
if (buf[size - 1] != 0)
{
str.assign(buf.get(), size);
if (str.size() != ((size_t)size)) {
fail("getBZString string size mismatch");
}
}
else
{
str.assign(buf.get(), size - 1); // don't copy null terminator
if (str.size() != ((size_t)size - 1)) {
fail("getBZString string size mismatch (null terminator)");
}
}
}
CompressedBSAFile::CompressedBSAFile()
: mCompressedByDefault(false), mEmbeddedFileNames(false)
{ }
CompressedBSAFile::~CompressedBSAFile()
{ }
/// Read header information from the input source
void CompressedBSAFile::readHeader()
{
assert(!mIsLoaded);
namespace bfs = boost::filesystem;
bfs::ifstream input(bfs::path(mFilename), std::ios_base::binary);
// Total archive size
std::streamoff fsize = 0;
if(input.seekg(0, std::ios_base::end))
{
fsize = input.tellg();
input.seekg(0);
}
if(fsize < 36) // header is 36 bytes
fail("File too small to be a valid BSA archive");
// Get essential header numbers
//size_t dirsize, filenum;
std::uint32_t archiveFlags, folderCount, totalFileNameLength;
{
// First 36 bytes
std::uint32_t header[9];
input.read(reinterpret_cast<char*>(header), 36);
if(header[0] != 0x00415342 /*"BSA\x00"*/ || (header[1] != 0x67 /*TES4*/ && header[1] != 0x68 /*TES5*/))
fail("Unrecognized TES4 BSA header");
// header[2] is offset, should be 36 = 0x24 which is the size of the header
// Oblivion - Meshes.bsa
//
// 0111 1000 0111 = 0x0787
// ^^^ ^ ^^^
// ||| | ||+-- has names for dirs (mandatory?)
// ||| | |+--- has names for files (mandatory?)
// ||| | +---- files are compressed by default
// ||| |
// ||| +---------- unknown (TES5: retain strings during startup)
// ||+------------ unknown (TES5: embedded file names)
// |+------------- unknown
// +-------------- unknown
//
archiveFlags = header[3];
folderCount = header[4];
// header[5] - fileCount
// totalFolderNameLength = header[6];
totalFileNameLength = header[7];
// header[8]; // fileFlags : an opportunity to optimize here
mCompressedByDefault = (archiveFlags & 0x4) != 0;
mEmbeddedFileNames = header[1] == 0x68 /*TES5*/ && (archiveFlags & 0x100) != 0;
}
// folder records
std::uint64_t hash;
FolderRecord fr;
for (std::uint32_t i = 0; i < folderCount; ++i)
{
input.read(reinterpret_cast<char*>(&hash), 8);
input.read(reinterpret_cast<char*>(&fr.count), 4); // not sure purpose of count
input.read(reinterpret_cast<char*>(&fr.offset), 4); // not sure purpose of offset
std::map<std::uint64_t, FolderRecord>::const_iterator lb = mFolders.lower_bound(hash);
if (lb != mFolders.end() && !(mFolders.key_comp()(hash, lb->first)))
fail("Archive found duplicate folder name hash");
else
mFolders.insert(lb, std::pair<std::uint64_t, FolderRecord>(hash, fr));
}
// file record blocks
std::uint64_t fileHash;
FileRecord file;
std::string folder("");
std::uint64_t folderHash;
if ((archiveFlags & 0x1) == 0)
folderCount = 1; // TODO: not tested - unit test necessary
mFiles.clear();
std::vector<std::string> fullPaths;
for (std::uint32_t i = 0; i < folderCount; ++i)
{
if ((archiveFlags & 0x1) != 0)
getBZString(folder, input);
folderHash = generateHash(folder, std::string());
std::map<std::uint64_t, FolderRecord>::iterator iter = mFolders.find(folderHash);
if (iter == mFolders.end())
fail("Archive folder name hash not found");
for (std::uint32_t j = 0; j < iter->second.count; ++j)
{
input.read(reinterpret_cast<char*>(&fileHash), 8);
input.read(reinterpret_cast<char*>(&file.size), 4);
input.read(reinterpret_cast<char*>(&file.offset), 4);
std::map<std::uint64_t, FileRecord>::const_iterator lb = iter->second.files.lower_bound(fileHash);
if (lb != iter->second.files.end() && !(iter->second.files.key_comp()(fileHash, lb->first)))
fail("Archive found duplicate file name hash");
iter->second.files.insert(lb, std::pair<std::uint64_t, FileRecord>(fileHash, file));
FileStruct fileStruct;
fileStruct.fileSize = file.getSizeWithoutCompressionFlag();
fileStruct.offset = file.offset;
fileStruct.name = nullptr;
mFiles.push_back(fileStruct);
fullPaths.push_back(folder);
}
}
// file record blocks
if ((archiveFlags & 0x2) != 0)
{
mStringBuf.resize(totalFileNameLength);
input.read(&mStringBuf[0], mStringBuf.size()); // TODO: maybe useful in building a lookup map?
}
size_t mStringBuffOffset = 0;
size_t totalStringsSize = 0;
for (std::uint32_t fileIndex = 0; fileIndex < mFiles.size(); ++fileIndex) {
if (mStringBuffOffset >= totalFileNameLength) {
fail("Corrupted names record in BSA file");
}
//The vector guarantees that its elements occupy contiguous memory
mFiles[fileIndex].name = reinterpret_cast<char*>(mStringBuf.data() + mStringBuffOffset);
fullPaths.at(fileIndex) += "\\" + std::string(mStringBuf.data() + mStringBuffOffset);
while (mStringBuffOffset < totalFileNameLength) {
if (mStringBuf[mStringBuffOffset] != '\0') {
mStringBuffOffset++;
}
else {
mStringBuffOffset++;
break;
}
}
//we want to keep one more 0 character at the end of each string
totalStringsSize += fullPaths.at(fileIndex).length() + 1u;
}
mStringBuf.resize(totalStringsSize);
mStringBuffOffset = 0;
for (std::uint32_t fileIndex = 0u; fileIndex < mFiles.size(); fileIndex++) {
size_t stringLength = fullPaths.at(fileIndex).length();
std::copy(fullPaths.at(fileIndex).c_str(),
//plus 1 because we also want to copy 0 at the end of the string
fullPaths.at(fileIndex).c_str() + stringLength + 1u,
mStringBuf.data() + mStringBuffOffset);
mFiles[fileIndex].name = reinterpret_cast<char*>(mStringBuf.data() + mStringBuffOffset);
mLookup[reinterpret_cast<char*>(mStringBuf.data() + mStringBuffOffset)] = fileIndex;
mStringBuffOffset += stringLength + 1u;
}
if (mStringBuffOffset != mStringBuf.size()) {
fail("Could not resolve names of files in BSA file");
}
convertCompressedSizesToUncompressed();
mIsLoaded = true;
}
CompressedBSAFile::FileRecord CompressedBSAFile::getFileRecord(const std::string& str) const
{
// Force-convert the path into something both Windows and UNIX can handle first
// to make sure Boost doesn't think the entire path is the filename on Linux
// and subsequently purge it to determine the file folder.
std::string path = str;
std::replace(path.begin(), path.end(), '\\', '/');
boost::filesystem::path p(path);
std::string stem = p.stem().string();
std::string ext = p.extension().string();
p.remove_filename();
std::string folder = p.string();
std::uint64_t folderHash = generateHash(folder, std::string());
std::map<std::uint64_t, FolderRecord>::const_iterator it = mFolders.find(folderHash);
if (it == mFolders.end())
return FileRecord(); // folder not found, return default which has offset of sInvalidOffset
std::uint64_t fileHash = generateHash(stem, ext);
std::map<std::uint64_t, FileRecord>::const_iterator iter = it->second.files.find(fileHash);
if (iter == it->second.files.end())
return FileRecord(); // file not found, return default which has offset of sInvalidOffset
return iter->second;
}
Files::IStreamPtr CompressedBSAFile::getFile(const FileStruct* file)
{
FileRecord fileRec = getFileRecord(file->name);
if (!fileRec.isValid()) {
fail("File not found: " + std::string(file->name));
}
return getFile(fileRec);
}
Files::IStreamPtr CompressedBSAFile::getFile(const char* file)
{
FileRecord fileRec = getFileRecord(file);
if (!fileRec.isValid()) {
fail("File not found: " + std::string(file));
}
return getFile(fileRec);
}
Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord)
{
if (fileRecord.isCompressed(mCompressedByDefault)) {
Files::IStreamPtr streamPtr = Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, fileRecord.getSizeWithoutCompressionFlag());
std::istream* fileStream = streamPtr.get();
if (mEmbeddedFileNames) {
std::string embeddedFileName;
getBZString(embeddedFileName, *fileStream);
}
uint32_t uncompressedSize = 0u;
fileStream->read(reinterpret_cast<char*>(&uncompressedSize), sizeof(uncompressedSize));
boost::iostreams::filtering_streambuf<boost::iostreams::input> inputStreamBuf;
inputStreamBuf.push(boost::iostreams::zlib_decompressor());
inputStreamBuf.push(*fileStream);
std::shared_ptr<Bsa::MemoryInputStream> memoryStreamPtr = std::make_shared<MemoryInputStream>(uncompressedSize);
boost::iostreams::basic_array_sink<char> sr(memoryStreamPtr->getRawData(), uncompressedSize);
boost::iostreams::copy(inputStreamBuf, sr);
return std::shared_ptr<std::istream>(memoryStreamPtr, (std::istream*)memoryStreamPtr.get());
}
return Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, fileRecord.size);
}
BsaVersion CompressedBSAFile::detectVersion(std::string filePath)
{
namespace bfs = boost::filesystem;
bfs::ifstream input(bfs::path(filePath), std::ios_base::binary);
// Total archive size
std::streamoff fsize = 0;
if (input.seekg(0, std::ios_base::end))
{
fsize = input.tellg();
input.seekg(0);
}
if (fsize < 12) {
return BSAVER_UNKNOWN;
}
// Get essential header numbers
// First 12 bytes
uint32_t head[3];
input.read(reinterpret_cast<char*>(head), 12);
if (head[0] == static_cast<uint32_t>(BSAVER_UNCOMPRESSED)) {
return BSAVER_UNCOMPRESSED;
}
if (head[0] == static_cast<uint32_t>(BSAVER_COMPRESSED)) {
return BSAVER_COMPRESSED;
}
return BSAVER_UNKNOWN;
}
//mFiles used by OpenMW expects uncompressed sizes
void CompressedBSAFile::convertCompressedSizesToUncompressed()
{
for (auto iter = mFiles.begin(); iter != mFiles.end(); ++iter)
{
const FileRecord& fileRecord = getFileRecord(iter->name);
if (!fileRecord.isValid())
{
fail("Could not find file " + std::string(iter->name) + " in BSA");
}
if (!fileRecord.isCompressed(mCompressedByDefault))
{
//no need to fix fileSize in mFiles - uncompressed size already set
continue;
}
Files::IStreamPtr dataBegin = Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, fileRecord.getSizeWithoutCompressionFlag());
if (mEmbeddedFileNames)
{
std::string embeddedFileName;
getBZString(embeddedFileName, *(dataBegin.get()));
}
dataBegin->read(reinterpret_cast<char*>(&(iter->fileSize)), sizeof(iter->fileSize));
}
}
std::uint64_t CompressedBSAFile::generateHash(std::string stem, std::string extension) const
{
size_t len = stem.length();
if (len == 0) return 0;
std::uint64_t hash = 0;
unsigned int hash2 = 0;
Misc::StringUtils::lowerCaseInPlace(stem);
if (extension.empty()) // It's a folder.
std::replace(stem.begin(), stem.end(), '/', '\\');
else
{
Misc::StringUtils::lowerCaseInPlace(extension);
for (const char &c : extension)
hash = hash * 0x1003f + c;
}
for (size_t i = 1; i < len-2 && len > 3; i++)
hash2 = hash2 * 0x1003f + stem[i];
hash = (hash + hash2) << 32;
hash2 = (stem[0] << 24) | (len << 16);
if (len >= 3) hash2 |= stem[len-2] << 8;
if (len >= 2) hash2 |= stem[len-1];
if (!extension.empty())
{
if (extension == ".kf") hash2 |= 0x80;
else if (extension == ".nif") hash2 |= 0x8000;
else if (extension == ".dds") hash2 |= 0x8080;
else if (extension == ".wav") hash2 |= 0x80000000;
}
return hash + hash2;
}
} //namespace Bsa

@ -0,0 +1,99 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008-2010 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.sourceforge.net/
This file (compressedbsafile.hpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
Compressed BSA stuff added by cc9cii 2018
*/
#ifndef BSA_COMPRESSED_BSA_FILE_H
#define BSA_COMPRESSED_BSA_FILE_H
#include <components/bsa/bsa_file.hpp>
namespace Bsa
{
enum BsaVersion
{
BSAVER_UNKNOWN = 0x0,
BSAVER_UNCOMPRESSED = 0x100,
BSAVER_COMPRESSED = 0x415342 //B, S, A
};
class CompressedBSAFile : public BSAFile
{
private:
//special marker for invalid records,
//equal to max uint32_t value
static const uint32_t sInvalidOffset;
//bit marking compression on file size
static const uint32_t sCompressedFlag;
struct FileRecord
{
std::uint32_t size;
std::uint32_t offset;
FileRecord();
bool isCompressed(bool bsaCompressedByDefault) const;
bool isValid() const;
std::uint32_t getSizeWithoutCompressionFlag() const;
};
//if files in BSA without 30th bit enabled are compressed
bool mCompressedByDefault;
//if each file record begins with BZ string with file name
bool mEmbeddedFileNames;
struct FolderRecord
{
std::uint32_t count;
std::uint32_t offset;
std::map<std::uint64_t, FileRecord> files;
};
std::map<std::uint64_t, FolderRecord> mFolders;
FileRecord getFileRecord(const std::string& str) const;
void getBZString(std::string& str, std::istream& filestream);
//mFiles used by OpenMW will contain uncompressed file sizes
void convertCompressedSizesToUncompressed();
/// \brief Normalizes given filename or folder and generates format-compatible hash. See https://en.uesp.net/wiki/Tes4Mod:Hash_Calculation.
std::uint64_t generateHash(std::string stem, std::string extension) const;
Files::IStreamPtr getFile(const FileRecord& fileRecord);
public:
CompressedBSAFile();
virtual ~CompressedBSAFile();
//checks version of BSA from file header
static BsaVersion detectVersion(std::string filePath);
/// Read header information from the input source
virtual void readHeader();
Files::IStreamPtr getFile(const char* filePath);
Files::IStreamPtr getFile(const FileStruct* fileStruct);
};
}
#endif

@ -0,0 +1,48 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008-2010 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.sourceforge.net/
This file (memorystream.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
Compressed BSA upgrade added by Azdul 2019
*/
#include "memorystream.hpp"
namespace Bsa
{
MemoryInputStreamBuf::MemoryInputStreamBuf(size_t bufferSize) : mBufferPtr(bufferSize)
{
this->setg(mBufferPtr.data(), mBufferPtr.data(), mBufferPtr.data() + bufferSize);
}
char* MemoryInputStreamBuf::getRawData() {
return mBufferPtr.data();
}
MemoryInputStream::MemoryInputStream(size_t bufferSize) :
MemoryInputStreamBuf(bufferSize),
std::istream(static_cast<std::streambuf*>(this)) {
}
char* MemoryInputStream::getRawData() {
return MemoryInputStreamBuf::getRawData();
}
}

@ -0,0 +1,62 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008-2010 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.sourceforge.net/
This file (memorystream.hpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
Compressed BSA upgrade added by Azdul 2019
*/
#ifndef BSA_MEMORY_STREAM_H
#define BSA_MEMORY_STREAM_H
#include <vector>
#include <iostream>
namespace Bsa
{
/**
Class used internally by MemoryInputStream.
*/
class MemoryInputStreamBuf : public std::streambuf {
public:
MemoryInputStreamBuf(size_t bufferSize);
char* getRawData();
private:
//correct call to delete [] on C++ 11
std::vector<char> mBufferPtr;
};
/**
Class replaces Ogre memory streams without introducing any new external dependencies
beyond standard library.
Allows to pass memory buffer as Files::IStreamPtr.
Memory buffer is freed once the class instance is destroyed.
*/
class MemoryInputStream : virtual MemoryInputStreamBuf, std::istream {
public:
MemoryInputStream(size_t bufferSize);
char* getRawData();
};
}
#endif

@ -94,14 +94,16 @@ namespace Compiler
bool FileParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) bool FileParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
{ {
if (code==Scanner::S_newline) // Ignore any junk special characters
if (mState == BeginState)
{ {
if (mState==BeginState) if (code != Scanner::S_newline)
{ reportWarning ("Ignoring stray special character before begin statement", loc);
// ignore empty lines return true;
return true; }
}
if (code==Scanner::S_newline)
{
if (mState==BeginCompleteState) if (mState==BeginCompleteState)
{ {
// parse the script body // parse the script body

@ -338,6 +338,9 @@ bool Config::GameSettings::writeFileWithComments(QFile &file)
if (!comments.empty() && index != -1 && settingRegex.captureCount() >= 2 && if (!comments.empty() && index != -1 && settingRegex.captureCount() >= 2 &&
mUserSettings.find(settingRegex.cap(1)) != mUserSettings.end()) mUserSettings.find(settingRegex.cap(1)) != mUserSettings.end())
{ {
if (commentStart == fileCopy.end())
throw std::runtime_error("Config::GameSettings: failed to parse settings - iterator is past of end of settings file");
for (std::vector<QString>::const_iterator it = comments.begin(); it != comments.end(); ++it) for (std::vector<QString>::const_iterator it = comments.begin(); it != comments.end(); ++it)
{ {
*commentStart = *it; *commentStart = *it;

@ -474,9 +474,13 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path)
void ContentSelectorModel::ContentModel::clearFiles() void ContentSelectorModel::ContentModel::clearFiles()
{ {
beginRemoveRows(QModelIndex(), 0, mFiles.count()-1); const int filesCount = mFiles.count();
mFiles.clear();
endRemoveRows(); if (filesCount > 0) {
beginRemoveRows(QModelIndex(), 0, filesCount - 1);
mFiles.clear();
endRemoveRows();
}
} }
QStringList ContentSelectorModel::ContentModel::gameFiles() const QStringList ContentSelectorModel::ContentModel::gameFiles() const

@ -17,6 +17,7 @@
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <boost/filesystem/fstream.hpp> #include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/operations.hpp>
namespace bfs = boost::filesystem; namespace bfs = boost::filesystem;
@ -34,9 +35,13 @@ namespace bfs = boost::filesystem;
#if defined(__APPLE__) #if defined(__APPLE__)
#include <sys/sysctl.h> #include <sys/sysctl.h>
#include <libproc.h>
#endif #endif
#define UNUSED(x) (void)(x) #if defined(__FreeBSD__)
#include <sys/sysctl.h>
#include <sys/user.h>
#endif
static const char crash_switch[] = "--cc-handle-crash"; static const char crash_switch[] = "--cc-handle-crash";
@ -413,6 +418,39 @@ static void crash_handler(const char *logfile)
exit(0); exit(0);
} }
static void getExecPath(char **argv)
{
#if defined (__FreeBSD__)
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
size_t size = sizeof(argv0);
if (sysctl(mib, 4, argv0, &size, nullptr, 0) == 0)
return;
#endif
#if defined (__APPLE__)
if(proc_pidpath(getpid(), argv0, sizeof(argv0)) > 0)
return;
#endif
int cwdlen;
const char *statusPaths[] = {"/proc/self/exe", "/proc/self/file", "/proc/curproc/exe", "/proc/curproc/file"};
memset(argv0, 0, sizeof(argv0));
for(const char *path : statusPaths)
{
if (readlink(path, argv0, sizeof(argv0)) != -1)
return;
}
if(argv[0][0] == '/')
snprintf(argv0, sizeof(argv0), "%s", argv[0]);
else if (getcwd(argv0, sizeof(argv0)) != NULL)
{
cwdlen = strlen(argv0);
snprintf(argv0+cwdlen, sizeof(argv0)-cwdlen, "/%s", argv[0]);
}
}
int crashCatcherInstallHandlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*)) int crashCatcherInstallHandlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*))
{ {
struct sigaction sa; struct sigaction sa;
@ -424,20 +462,7 @@ int crashCatcherInstallHandlers(int argc, char **argv, int num_signals, int *sig
cc_user_info = user_info; cc_user_info = user_info;
if(argv[0][0] == '/') getExecPath(argv);
snprintf(argv0, sizeof(argv0), "%s", argv[0]);
else
{
{
/* we don't want to disable "ignoring return value" warnings, so we make
* a special exception here. */
char * unused;
unused = getcwd(argv0, sizeof(argv0));
UNUSED(unused);
}
retval = strlen(argv0);
snprintf(argv0+retval, sizeof(argv0)-retval, "/%s", argv[0]);
}
/* Set an alternate signal stack so SIGSEGVs caused by stack overflows /* Set an alternate signal stack so SIGSEGVs caused by stack overflows
* still run */ * still run */
@ -467,20 +492,24 @@ int crashCatcherInstallHandlers(int argc, char **argv, int num_signals, int *sig
static bool is_debugger_present() static bool is_debugger_present()
{ {
#if !defined (__APPLE__) #if defined (__linux__)
bfs::ifstream file((bfs::path("/proc/self/status"))); bfs::path procstatus = bfs::path("/proc/self/status");
while (!file.eof()) if (bfs::exists(procstatus))
{ {
std::string word; bfs::ifstream file((procstatus));
file >> word; while (!file.eof())
if (word == "TracerPid:")
{ {
std::string word;
file >> word; file >> word;
return word != "0"; if (word == "TracerPid:")
{
file >> word;
return word != "0";
}
} }
} }
return false; return false;
#else #elif defined(__APPLE__)
int junk; int junk;
int mib[4]; int mib[4];
struct kinfo_proc info; struct kinfo_proc info;
@ -508,12 +537,24 @@ static bool is_debugger_present()
// We're being debugged if the P_TRACED flag is set. // We're being debugged if the P_TRACED flag is set.
return (info.kp_proc.p_flag & P_TRACED) != 0; return (info.kp_proc.p_flag & P_TRACED) != 0;
#elif defined(__FreeBSD__)
struct kinfo_proc info;
size_t size = sizeof(info);
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() };
if (sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) == 0)
return (info.ki_flag & P_TRACED) != 0;
else
perror("Failed to retrieve process info");
return false;
#else
return false;
#endif #endif
} }
void crashCatcherInstall(int argc, char **argv, const std::string &crashLogPath) void crashCatcherInstall(int argc, char **argv, const std::string &crashLogPath)
{ {
if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_present()) if ((argc == 2 && strcmp(argv[1], crash_switch) == 0) || !is_debugger_present())
{ {
int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT };
if (crashCatcherInstallHandlers(argc, argv, 5, s, crashLogPath.c_str(), nullptr) == -1) if (crashCatcherInstallHandlers(argc, argv, 5, s, crashLogPath.c_str(), nullptr) == -1)

@ -32,13 +32,6 @@ struct Attribute
static const std::string sGmstAttributeIds[Length]; static const std::string sGmstAttributeIds[Length];
static const std::string sGmstAttributeDescIds[Length]; static const std::string sGmstAttributeDescIds[Length];
static const std::string sAttributeIcons[Length]; static const std::string sAttributeIcons[Length];
Attribute(AttributeID id, const std::string &name, const std::string &description)
: mId(id)
, mName(name)
, mDescription(description)
{
}
}; };
} }
#endif #endif

@ -1,7 +1,6 @@
#include "loadcell.hpp" #include "loadcell.hpp"
#include <string> #include <string>
#include <sstream>
#include <list> #include <list>
#include <boost/concept_check.hpp> #include <boost/concept_check.hpp>
@ -209,9 +208,7 @@ namespace ESM
} }
else else
{ {
std::ostringstream stream; return std::to_string(mData.mX) + ", " + std::to_string(mData.mY);
stream << mData.mX << ", " << mData.mY;
return stream.str();
} }
} }

@ -23,6 +23,7 @@ namespace ESM {
mScale = 1.f; mScale = 1.f;
mHasAI = false; mHasAI = false;
mAiData.blank();
bool hasName = false; bool hasName = false;
bool hasNpdt = false; bool hasNpdt = false;
@ -160,7 +161,6 @@ namespace ESM {
mSpells.mList.clear(); mSpells.mList.clear();
mHasAI = false; mHasAI = false;
mAiData.blank(); mAiData.blank();
mAiData.mServices = 0;
mAiPackage.mList.clear(); mAiPackage.mList.clear();
mTransport.mList.clear(); mTransport.mList.clear();
} }

@ -22,21 +22,6 @@ struct Door
void blank(); void blank();
///< Set record to default state (does not touch the ID). ///< Set record to default state (does not touch the ID).
Door(const std::string id, const std::string name, const std::string &model,
const std::string script, const std::string opensound, const std::string closesound)
: mId(id)
, mName(name)
, mModel(model)
, mScript(script)
, mOpenSound(opensound)
, mCloseSound(closesound)
{
}
Door()
{
}
}; };
} }
#endif #endif

@ -1,5 +1,6 @@
#include "loadland.hpp" #include "loadland.hpp"
#include <limits>
#include <utility> #include <utility>
#include "esmreader.hpp" #include "esmreader.hpp"
@ -200,9 +201,9 @@ namespace ESM
mLandData->mTextures[i] = 0; mLandData->mTextures[i] = 0;
for (int i = 0; i < LAND_NUM_VERTS; ++i) for (int i = 0; i < LAND_NUM_VERTS; ++i)
{ {
mLandData->mColours[i*3+0] = -1; mLandData->mColours[i*3+0] = 255;
mLandData->mColours[i*3+1] = -1; mLandData->mColours[i*3+1] = 255;
mLandData->mColours[i*3+2] = -1; mLandData->mColours[i*3+2] = 255;
} }
mLandData->mUnk1 = 0; mLandData->mUnk1 = 0;
mLandData->mUnk2 = 0; mLandData->mUnk2 = 0;
@ -252,8 +253,8 @@ namespace ESM
if (reader.isNextSub("VHGT")) { if (reader.isNextSub("VHGT")) {
VHGT vhgt; VHGT vhgt;
if (condLoad(reader, flags, target->mDataLoaded, DATA_VHGT, &vhgt, sizeof(vhgt))) { if (condLoad(reader, flags, target->mDataLoaded, DATA_VHGT, &vhgt, sizeof(vhgt))) {
target->mMinHeight = FLT_MAX; target->mMinHeight = std::numeric_limits<float>::max();
target->mMaxHeight = -FLT_MAX; target->mMaxHeight = -std::numeric_limits<float>::max();
float rowOffset = vhgt.mHeightOffset; float rowOffset = vhgt.mHeightOffset;
for (int y = 0; y < LAND_SIZE; y++) { for (int y = 0; y < LAND_SIZE; y++) {
rowOffset += vhgt.mHeightData[y * LAND_SIZE]; rowOffset += vhgt.mHeightData[y * LAND_SIZE];

@ -18,6 +18,7 @@ namespace ESM
mInventory.mList.clear(); mInventory.mList.clear();
mTransport.mList.clear(); mTransport.mList.clear();
mAiPackage.mList.clear(); mAiPackage.mList.clear();
mAiData.blank();
mHasAI = false; mHasAI = false;
bool hasName = false; bool hasName = false;

@ -33,16 +33,6 @@ struct Static
void blank(); void blank();
///< Set record to default state (does not touch the ID). ///< Set record to default state (does not touch the ID).
Static(const std::string id, const std::string &model)
: mId(id)
, mModel(model)
{
}
Static()
{
}
}; };
} }
#endif #endif

@ -1,70 +1,95 @@
#include "fallback.hpp" #include "fallback.hpp"
#include <boost/lexical_cast.hpp> #include <components/debug/debuglog.hpp>
#include <boost/lexical_cast.hpp>
namespace Fallback namespace Fallback
{ {
bool stob(std::string const& s) {
return s != "0";
}
Map::Map(const std::map<std::string,std::string>& fallback):mFallbackMap(fallback) Map::Map(const std::map<std::string,std::string>& fallback):mFallbackMap(fallback)
{} {}
std::string Map::getFallbackString(const std::string& fall) const std::string Map::getFallbackString(const std::string& fall) const
{ {
std::map<std::string,std::string>::const_iterator it; std::map<std::string,std::string>::const_iterator it;
if((it = mFallbackMap.find(fall)) == mFallbackMap.end()) if ((it = mFallbackMap.find(fall)) == mFallbackMap.end())
{ {
return ""; return std::string();
} }
return it->second; return it->second;
} }
float Map::getFallbackFloat(const std::string& fall) const float Map::getFallbackFloat(const std::string& fall) const
{ {
std::string fallback=getFallbackString(fall); std::string fallback = getFallbackString(fall);
if (fallback.empty()) if (!fallback.empty())
return 0; {
else try
return boost::lexical_cast<float>(fallback); {
// We have to rely on Boost because std::stof from C++11
// uses the current locale for separators which we don't want and often silently ignores parsing errors.
return boost::lexical_cast<float>(fallback);
}
catch (boost::bad_lexical_cast&)
{
Log(Debug::Error) << "Error: '" << fall << "' setting value (" << fallback << ") is not a valid number, using 0 as a fallback";
}
}
return 0;
} }
int Map::getFallbackInt(const std::string& fall) const int Map::getFallbackInt(const std::string& fall) const
{ {
std::string fallback=getFallbackString(fall); std::string fallback = getFallbackString(fall);
if (fallback.empty()) if (!fallback.empty())
return 0; {
else try
return std::stoi(fallback); {
return std::stoi(fallback);
}
catch (const std::invalid_argument&)
{
Log(Debug::Error) << "Error: '" << fall << "' setting value (" << fallback << ") is not a valid number, using 0 as a fallback";
}
catch (const std::out_of_range&)
{
Log(Debug::Error) << "Error: '" << fall << "' setting value (" << fallback << ") is out of range, using 0 as a fallback";
}
}
return 0;
} }
bool Map::getFallbackBool(const std::string& fall) const bool Map::getFallbackBool(const std::string& fall) const
{ {
std::string fallback=getFallbackString(fall); std::string fallback = getFallbackString(fall);
if (fallback.empty()) return !fallback.empty() && fallback != "0";
return false;
else
return stob(fallback);
} }
osg::Vec4f Map::getFallbackColour(const std::string& fall) const osg::Vec4f Map::getFallbackColour(const std::string& fall) const
{ {
std::string sum=getFallbackString(fall); std::string sum = getFallbackString(fall);
if (sum.empty()) if (!sum.empty())
return osg::Vec4f(0.5f,0.5f,0.5f,1.f);
else
{ {
std::string ret[3]; try
unsigned int j=0; {
for(unsigned int i=0;i<sum.length();++i){ std::string ret[3];
if(sum[i]==',') j++; unsigned int j = 0;
else if (sum[i] != ' ') ret[j]+=sum[i]; for (unsigned int i = 0; i < sum.length(); ++i)
{
if(sum[i]==',') j++;
else if (sum[i] != ' ') ret[j]+=sum[i];
}
return osg::Vec4f(std::stoi(ret[0])/255.f,std::stoi(ret[1])/255.f,std::stoi(ret[2])/255.f, 1.f);
}
catch (const std::invalid_argument&)
{
Log(Debug::Error) << "Error: '" << fall << "' setting value (" << sum << ") is not a valid color, using middle gray as a fallback";
} }
return osg::Vec4f(std::stoi(ret[0])/255.f,std::stoi(ret[1])/255.f,std::stoi(ret[2])/255.f, 1.f);
} }
return osg::Vec4f(0.5f,0.5f,0.5f,1.f);
} }
} }

@ -1,7 +1,6 @@
#include "interpreter.hpp" #include "interpreter.hpp"
#include <cassert> #include <cassert>
#include <sstream>
#include <stdexcept> #include <stdexcept>
#include "opcodes.hpp" #include "opcodes.hpp"
@ -116,20 +115,14 @@ namespace Interpreter
void Interpreter::abortUnknownCode (int segment, int opcode) void Interpreter::abortUnknownCode (int segment, int opcode)
{ {
std::ostringstream error; const std::string error = "unknown opcode " + std::to_string(opcode) + " in segment " + std::to_string(segment);
throw std::runtime_error (error);
error << "unknown opcode " << opcode << " in segment " << segment;
throw std::runtime_error (error.str());
} }
void Interpreter::abortUnknownSegment (Type_Code code) void Interpreter::abortUnknownSegment (Type_Code code)
{ {
std::ostringstream error; const std::string error = "opcode outside of the allocated segment range: " + std::to_string(code);
throw std::runtime_error (error);
error << "opcode outside of the allocated segment range: " << code;
throw std::runtime_error (error.str());
} }
void Interpreter::begin() void Interpreter::begin()

@ -5,6 +5,7 @@
#include <osg/Geometry> #include <osg/Geometry>
#include <osg/Array> #include <osg/Array>
#include <osg/LOD> #include <osg/LOD>
#include <osg/Switch>
#include <osg/TexGen> #include <osg/TexGen>
#include <osg/ValueObject> #include <osg/ValueObject>
@ -335,6 +336,13 @@ namespace NifOsg
return lod; return lod;
} }
osg::ref_ptr<osg::Switch> handleSwitchNode(const Nif::NiSwitchNode* niSwitchNode)
{
osg::ref_ptr<osg::Switch> switchNode (new osg::Switch);
switchNode->setNewChildDefaultValue(false);
return switchNode;
}
osg::ref_ptr<osg::Image> handleSourceTexture(const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager) osg::ref_ptr<osg::Image> handleSourceTexture(const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager)
{ {
if (!st) if (!st)
@ -593,6 +601,23 @@ namespace NifOsg
node = lod; node = lod;
} }
if (nifNode->recType == Nif::RC_NiSwitchNode)
{
const Nif::NiSwitchNode* niSwitchNode = static_cast<const Nif::NiSwitchNode*>(nifNode);
osg::ref_ptr<osg::Switch> switchNode = handleSwitchNode(niSwitchNode);
node->addChild(switchNode);
const Nif::NodeList &children = niSwitchNode->children;
for(size_t i = 0;i < children.length();++i)
{
if(!children[i].empty())
handleNode(children[i].getPtr(), switchNode, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, isAnimated, textKeys, rootNode);
}
// show only first child by default
switchNode->setSingleChildOn(0);
return switchNode;
}
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(nifNode); const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(nifNode);
if(ninode) if(ninode)
{ {
@ -822,7 +847,9 @@ namespace NifOsg
if (particle.vertex < int(particledata->colors.size())) if (particle.vertex < int(particledata->colors.size()))
partcolor = particledata->colors.at(particle.vertex); partcolor = particledata->colors.at(particle.vertex);
float size = particledata->sizes.at(particle.vertex) * partctrl->size; float size = partctrl->size;
if (particle.vertex < int(particledata->sizes.size()))
size *= particledata->sizes.at(particle.vertex);
created->setSizeRange(osgParticle::rangef(size, size)); created->setSizeRange(osgParticle::rangef(size, size));
box.expandBy(osg::BoundingSphere(position, size)); box.expandBy(osg::BoundingSphere(position, size));
@ -1092,13 +1119,12 @@ namespace NifOsg
const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights; const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights;
for(size_t j = 0;j < weights.size();j++) for(size_t j = 0;j < weights.size();j++)
{ {
std::pair<unsigned short, float> indexWeight = std::make_pair(weights[j].vertex, weights[j].weight); influence.mWeights.emplace_back(weights[j].vertex, weights[j].weight);
influence.mWeights.insert(indexWeight);
} }
influence.mInvBindMatrix = data->bones[i].trafo.toMatrix(); influence.mInvBindMatrix = data->bones[i].trafo.toMatrix();
influence.mBoundSphere = osg::BoundingSpheref(data->bones[i].boundSphereCenter, data->bones[i].boundSphereRadius); influence.mBoundSphere = osg::BoundingSpheref(data->bones[i].boundSphereCenter, data->bones[i].boundSphereRadius);
map->mMap.insert(std::make_pair(boneName, influence)); map->mData.emplace_back(boneName, influence);
} }
rig->setInfluenceMap(map); rig->setInfluenceMap(map);

@ -2,6 +2,8 @@
#include <cassert> #include <cassert>
#include <osg/Version>
namespace SceneUtil namespace SceneUtil
{ {
@ -176,6 +178,10 @@ void MorphGeometry::cull(osg::NodeVisitor *nv)
positionDst->dirty(); positionDst->dirty();
#if OSG_MIN_VERSION_REQUIRED(3, 5, 6)
geom.dirtyGLObjects();
#endif
nv->pushOntoNodePath(&geom); nv->pushOntoNodePath(&geom);
nv->apply(geom); nv->apply(geom);
nv->popFromNodePath(); nv->popFromNodePath();

@ -807,6 +807,13 @@ void Optimizer::RemoveRedundantNodesVisitor::apply(osg::LOD& lod)
traverse(*lod.getChild(i)); traverse(*lod.getChild(i));
} }
void Optimizer::RemoveRedundantNodesVisitor::apply(osg::Switch& switchNode)
{
// We should keep all switch child nodes since they reflect different switch states.
for (unsigned int i=0; i<switchNode.getNumChildren(); ++i)
traverse(*switchNode.getChild(i));
}
void Optimizer::RemoveRedundantNodesVisitor::apply(osg::Group& group) void Optimizer::RemoveRedundantNodesVisitor::apply(osg::Group& group)
{ {
if (typeid(group)==typeid(osg::Group) && if (typeid(group)==typeid(osg::Group) &&
@ -1862,6 +1869,12 @@ void Optimizer::MergeGroupsVisitor::apply(osg::LOD &lod)
traverse(lod); traverse(lod);
} }
void Optimizer::MergeGroupsVisitor::apply(osg::Switch &switchNode)
{
// We should keep all switch child nodes since they reflect different switch states.
traverse(switchNode);
}
void Optimizer::MergeGroupsVisitor::apply(osg::Group &group) void Optimizer::MergeGroupsVisitor::apply(osg::Group &group)
{ {
if (group.getNumChildren() <= 1) if (group.getNumChildren() <= 1)

@ -340,6 +340,7 @@ class Optimizer
virtual void apply(osg::Group& group); virtual void apply(osg::Group& group);
virtual void apply(osg::Transform& transform); virtual void apply(osg::Transform& transform);
virtual void apply(osg::LOD& lod); virtual void apply(osg::LOD& lod);
virtual void apply(osg::Switch& switchNode);
bool isOperationPermissible(osg::Node& node); bool isOperationPermissible(osg::Node& node);
@ -360,6 +361,7 @@ class Optimizer
virtual void apply(osg::Group& group); virtual void apply(osg::Group& group);
virtual void apply(osg::LOD& lod); virtual void apply(osg::LOD& lod);
virtual void apply(osg::Switch& switchNode);
}; };
class MergeGeometryVisitor : public BaseOptimizerVisitor class MergeGeometryVisitor : public BaseOptimizerVisitor

@ -3,6 +3,8 @@
#include <stdexcept> #include <stdexcept>
#include <cstdlib> #include <cstdlib>
#include <osg/Version>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include "skeleton.hpp" #include "skeleton.hpp"
@ -136,35 +138,35 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv)
typedef std::map<unsigned short, std::vector<BoneWeight> > Vertex2BoneMap; typedef std::map<unsigned short, std::vector<BoneWeight> > Vertex2BoneMap;
Vertex2BoneMap vertex2BoneMap; Vertex2BoneMap vertex2BoneMap;
for (std::map<std::string, BoneInfluence>::const_iterator it = mInfluenceMap->mMap.begin(); it != mInfluenceMap->mMap.end(); ++it) mBoneSphereVector.clear();
for (auto& influencePair : mInfluenceMap->mData)
{ {
Bone* bone = mSkeleton->getBone(it->first); Bone* bone = mSkeleton->getBone(influencePair.first);
if (!bone) if (!bone)
{ {
Log(Debug::Error) << "Error: RigGeometry did not find bone " << it->first ; Log(Debug::Error) << "Error: RigGeometry did not find bone " << influencePair.first;
continue; continue;
} }
mBoneSphereMap[bone] = it->second.mBoundSphere; const BoneInfluence& bi = influencePair.second;
mBoneSphereVector.emplace_back(bone, bi.mBoundSphere);
const BoneInfluence& bi = it->second;
const std::map<unsigned short, float>& weights = it->second.mWeights; for (auto& weightPair: bi.mWeights)
for (std::map<unsigned short, float>::const_iterator weightIt = weights.begin(); weightIt != weights.end(); ++weightIt)
{ {
std::vector<BoneWeight>& vec = vertex2BoneMap[weightIt->first]; std::vector<BoneWeight>& vec = vertex2BoneMap[weightPair.first];
BoneWeight b = std::make_pair(std::make_pair(bone, bi.mInvBindMatrix), weightIt->second);
vec.push_back(b); vec.emplace_back(std::make_pair(bone, bi.mInvBindMatrix), weightPair.second);
} }
} }
Bone2VertexMap bone2VertexMap;
for (Vertex2BoneMap::iterator it = vertex2BoneMap.begin(); it != vertex2BoneMap.end(); ++it) for (Vertex2BoneMap::iterator it = vertex2BoneMap.begin(); it != vertex2BoneMap.end(); ++it)
{ {
mBone2VertexMap[it->second].push_back(it->first); bone2VertexMap[it->second].push_back(it->first);
} }
mBone2VertexVector.assign(bone2VertexMap.begin(), bone2VertexMap.end());
return true; return true;
} }
@ -201,7 +203,7 @@ void RigGeometry::cull(osg::NodeVisitor* nv)
osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(geom.getNormalArray()); osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(geom.getNormalArray());
osg::Vec4Array* tangentDst = static_cast<osg::Vec4Array*>(geom.getTexCoordArray(7)); osg::Vec4Array* tangentDst = static_cast<osg::Vec4Array*>(geom.getTexCoordArray(7));
for (auto &pair : mBone2VertexMap) for (auto &pair : mBone2VertexVector)
{ {
osg::Matrixf resultMat (0, 0, 0, 0, osg::Matrixf resultMat (0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
@ -237,6 +239,10 @@ void RigGeometry::cull(osg::NodeVisitor* nv)
if (tangentDst) if (tangentDst)
tangentDst->dirty(); tangentDst->dirty();
#if OSG_MIN_VERSION_REQUIRED(3, 5, 6)
geom.dirtyGLObjects();
#endif
nv->pushOntoNodePath(&geom); nv->pushOntoNodePath(&geom);
nv->apply(geom); nv->apply(geom);
nv->popFromNodePath(); nv->popFromNodePath();
@ -259,10 +265,10 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv)
updateGeomToSkelMatrix(nv->getNodePath()); updateGeomToSkelMatrix(nv->getNodePath());
osg::BoundingBox box; osg::BoundingBox box;
for (BoneSphereMap::const_iterator it = mBoneSphereMap.begin(); it != mBoneSphereMap.end(); ++it) for (auto& boundPair : mBoneSphereVector)
{ {
Bone* bone = it->first; Bone* bone = boundPair.first;
osg::BoundingSpheref bs = it->second; osg::BoundingSpheref bs = boundPair.second;
if (mGeomToSkelMatrix) if (mGeomToSkelMatrix)
transformBoundingSphere(bone->mMatrixInSkeletonSpace * (*mGeomToSkelMatrix), bs); transformBoundingSphere(bone->mMatrixInSkeletonSpace * (*mGeomToSkelMatrix), bs);
else else

@ -31,12 +31,12 @@ namespace SceneUtil
osg::Matrixf mInvBindMatrix; osg::Matrixf mInvBindMatrix;
osg::BoundingSpheref mBoundSphere; osg::BoundingSpheref mBoundSphere;
// <vertex index, weight> // <vertex index, weight>
std::map<unsigned short, float> mWeights; std::vector<std::pair<unsigned short, float>> mWeights;
}; };
struct InfluenceMap : public osg::Referenced struct InfluenceMap : public osg::Referenced
{ {
std::map<std::string, BoneInfluence> mMap; std::vector<std::pair<std::string, BoneInfluence>> mData;
}; };
void setInfluenceMap(osg::ref_ptr<InfluenceMap> influenceMap); void setInfluenceMap(osg::ref_ptr<InfluenceMap> influenceMap);
@ -87,12 +87,13 @@ namespace SceneUtil
typedef std::vector<unsigned short> VertexList; typedef std::vector<unsigned short> VertexList;
typedef std::map<std::vector<BoneWeight>, VertexList> Bone2VertexMap; typedef std::map<std::vector<BoneWeight>, VertexList> Bone2VertexMap;
typedef std::vector<std::pair<std::vector<BoneWeight>, VertexList>> Bone2VertexVector;
Bone2VertexMap mBone2VertexMap; Bone2VertexVector mBone2VertexVector;
typedef std::map<Bone*, osg::BoundingSpheref> BoneSphereMap; typedef std::vector<std::pair<Bone*, osg::BoundingSpheref>> BoneSphereVector;
BoneSphereMap mBoneSphereMap; BoneSphereVector mBoneSphereVector;
unsigned int mLastFrameNumber; unsigned int mLastFrameNumber;
bool mBoundsFirstFrame; bool mBoundsFirstFrame;

@ -9,6 +9,7 @@
#include <osg/Referenced> #include <osg/Referenced>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <atomic>
#include <queue> #include <queue>
namespace SceneUtil namespace SceneUtil
@ -87,7 +88,7 @@ namespace SceneUtil
private: private:
WorkQueue* mWorkQueue; WorkQueue* mWorkQueue;
volatile bool mActive; std::atomic<bool> mActive;
}; };

@ -8,48 +8,6 @@
#include <boost/filesystem/fstream.hpp> #include <boost/filesystem/fstream.hpp>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
namespace
{
bool parseBool(const std::string& string)
{
return (Misc::StringUtils::ciEqual(string, "true"));
}
float parseFloat(const std::string& string)
{
std::stringstream stream;
stream << string;
float ret = 0.f;
stream >> ret;
return ret;
}
int parseInt(const std::string& string)
{
std::stringstream stream;
stream << string;
int ret = 0;
stream >> ret;
return ret;
}
template <typename T>
std::string toString(T val)
{
std::ostringstream stream;
stream << val;
return stream.str();
}
template <>
std::string toString(bool val)
{
return val ? "true" : "false";
}
}
namespace Settings namespace Settings
{ {
@ -393,17 +351,36 @@ std::string Manager::getString(const std::string &setting, const std::string &ca
float Manager::getFloat (const std::string& setting, const std::string& category) float Manager::getFloat (const std::string& setting, const std::string& category)
{ {
return parseFloat( getString(setting, category) ); const std::string value = getString(setting, category);
try
{
return std::stof(value);
}
catch(const std::exception& e)
{
Log(Debug::Warning) << "Cannot parse setting '" << setting << "' (invalid setting value: " << value << ").";
return 0;
}
} }
int Manager::getInt (const std::string& setting, const std::string& category) int Manager::getInt (const std::string& setting, const std::string& category)
{ {
return parseInt( getString(setting, category) ); const std::string value = getString(setting, category);
try
{
return std::stoi(value);
}
catch(const std::exception& e)
{
Log(Debug::Warning) << "Cannot parse setting '" << setting << "' (invalid setting value: " << value << ").";
return 0;
}
} }
bool Manager::getBool (const std::string& setting, const std::string& category) bool Manager::getBool (const std::string& setting, const std::string& category)
{ {
return parseBool( getString(setting, category) ); const std::string& string = getString(setting, category);
return Misc::StringUtils::ciEqual(string, "true");
} }
void Manager::setString(const std::string &setting, const std::string &category, const std::string &value) void Manager::setString(const std::string &setting, const std::string &category, const std::string &value)
@ -424,17 +401,17 @@ void Manager::setString(const std::string &setting, const std::string &category,
void Manager::setInt (const std::string& setting, const std::string& category, const int value) void Manager::setInt (const std::string& setting, const std::string& category, const int value)
{ {
setString(setting, category, toString(value)); setString(setting, category, std::to_string(value));
} }
void Manager::setFloat (const std::string &setting, const std::string &category, const float value) void Manager::setFloat (const std::string &setting, const std::string &category, const float value)
{ {
setString(setting, category, toString(value)); setString(setting, category, std::to_string(value));
} }
void Manager::setBool(const std::string &setting, const std::string &category, const bool value) void Manager::setBool(const std::string &setting, const std::string &category, const bool value)
{ {
setString(setting, category, toString(value)); setString(setting, category, value ? "true" : "false");
} }
const CategorySettingVector Manager::apply() const CategorySettingVector Manager::apply()

@ -1,20 +1,33 @@
#include "bsaarchive.hpp" #include "bsaarchive.hpp"
#include <components/bsa/compressedbsafile.hpp>
#include <memory>
namespace VFS namespace VFS
{ {
BsaArchive::BsaArchive(const std::string &filename) BsaArchive::BsaArchive(const std::string &filename)
{ {
mFile.open(filename); Bsa::BsaVersion bsaVersion = Bsa::CompressedBSAFile::detectVersion(filename);
if (bsaVersion == Bsa::BSAVER_COMPRESSED) {
mFile = std::unique_ptr<Bsa::CompressedBSAFile>(new Bsa::CompressedBSAFile());
}
else {
mFile = std::unique_ptr<Bsa::BSAFile>(new Bsa::BSAFile());
}
const Bsa::BSAFile::FileList &filelist = mFile.getList(); mFile->open(filename);
const Bsa::BSAFile::FileList &filelist = mFile->getList();
for(Bsa::BSAFile::FileList::const_iterator it = filelist.begin();it != filelist.end();++it) for(Bsa::BSAFile::FileList::const_iterator it = filelist.begin();it != filelist.end();++it)
{ {
mResources.push_back(BsaArchiveFile(&*it, &mFile)); mResources.push_back(BsaArchiveFile(&*it, mFile.get()));
} }
} }
BsaArchive::~BsaArchive() {
}
void BsaArchive::listResources(std::map<std::string, File *> &out, char (*normalize_function)(char)) void BsaArchive::listResources(std::map<std::string, File *> &out, char (*normalize_function)(char))
{ {
for (std::vector<BsaArchiveFile>::iterator it = mResources.begin(); it != mResources.end(); ++it) for (std::vector<BsaArchiveFile>::iterator it = mResources.begin(); it != mResources.end(); ++it)

@ -7,7 +7,6 @@
namespace VFS namespace VFS
{ {
class BsaArchiveFile : public File class BsaArchiveFile : public File
{ {
public: public:
@ -23,15 +22,13 @@ namespace VFS
{ {
public: public:
BsaArchive(const std::string& filename); BsaArchive(const std::string& filename);
virtual ~BsaArchive();
virtual void listResources(std::map<std::string, File*>& out, char (*normalize_function) (char)); virtual void listResources(std::map<std::string, File*>& out, char (*normalize_function) (char));
private: private:
Bsa::BSAFile mFile; std::unique_ptr<Bsa::BSAFile> mFile;
std::vector<BsaArchiveFile> mResources; std::vector<BsaArchiveFile> mResources;
}; };
} }
#endif #endif

@ -14,18 +14,10 @@ namespace Gui
virtual void setFontName(const std::string& name) virtual void setFontName(const std::string& name)
{ {
T::setFontName(name); T::setFontName(name);
T::setPropertyOverride ("FontHeight", mFontSize); T::setPropertyOverride ("FontHeight", getFontSize());
} }
protected: protected:
FontWrapper()
{
// Note: we can not use the WindowManager here, so there is a code duplication a bit.
int fontSize = Settings::Manager::getInt("font size", "GUI");
fontSize = std::min(std::max(12, fontSize), 20);
mFontSize = std::to_string(fontSize);
}
virtual void setPropertyOverride(const std::string& _key, const std::string& _value) virtual void setPropertyOverride(const std::string& _key, const std::string& _value)
{ {
T::setPropertyOverride (_key, _value); T::setPropertyOverride (_key, _value);
@ -34,11 +26,22 @@ namespace Gui
// We should restore it. // We should restore it.
if (_key == "FontName") if (_key == "FontName")
{ {
T::setPropertyOverride ("FontHeight", mFontSize); T::setPropertyOverride ("FontHeight", getFontSize());
} }
} }
std::string mFontSize; private:
static int clamp(const int& value, const int& lowBound, const int& highBound)
{
return std::min(std::max(lowBound, value), highBound);
}
std::string getFontSize()
{
// Note: we can not use the WindowManager here, so there is a code duplication a bit.
static const std::string fontSize = std::to_string(clamp(Settings::Manager::getInt("font size", "GUI"), 12, 20));
return fontSize;
}
}; };
} }

@ -4,9 +4,8 @@ Fonts
Morrowind .fnt fonts Morrowind .fnt fonts
-------------------- --------------------
Morrowind uses a custom ``.fnt`` file format. It is not compatible with the Windows Font File ``.fnt`` format, Morrowind uses a custom ``.fnt`` file format. It is not compatible with the Windows Font File ``.fnt`` format.
nor compatible with ``.fnt`` formats from any other Bethesda games. To our knowledge, To our knowledge, the format is undocumented.
the format is undocumented and no tools for viewing or editing these fonts exist.
OpenMW can load this format and convert it on the fly into something usable OpenMW can load this format and convert it on the fly into something usable
(see font loader `source code <https://github.com/OpenMW/openmw/blob/master/components/fontloader/fontloader.cpp#L210>`_). (see font loader `source code <https://github.com/OpenMW/openmw/blob/master/components/fontloader/fontloader.cpp#L210>`_).
@ -16,20 +15,62 @@ In OpenMW 0.32, an --export-fonts command line option was added to write the con
TrueType fonts TrueType fonts
-------------- --------------
Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts. Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts.
This is the recommended way to create new fonts.
0.45.0+ way
-----------
This is the recommended way to install replacement fonts.
- To replace the primary "Magic Cards" font:
#. Download `Pelagiad <https://isaskar.github.io/Pelagiad/>`_ by Isak Larborn (aka Isaskar).
#. Create ``Fonts`` folder at the location of your ``openmw.cfg``.
#. Copy ``openmw_font.xml`` and ``Pelagiad.ttf`` files into the folder.
#. If desired, you can now delete the original ``Magic_Cards.*`` files from your ``Data Files/Fonts`` directory.
#. Pelagiad glyphs may appear to be pretty small, so open ``openmw.cfg`` and adjust the following settings as you deem necessary::
[GUI]
font size = 17
ttf resolution = 96
- You can also replace the Daedric font:
#. Download `Ayembedt <https://github.com/georgd/OpenMW-Fonts>`_ by Georg Duffner.
#. Install ``OMWAyembedt.otf`` into the ``Fonts`` folder.
#. Add the following lines into ``openmw_font.xml``::
<Resource type="ResourceTrueTypeFont" name="Daedric">
<Property key="Source" value="OMWAyembedt.otf"/>
<Property key="Antialias" value="false"/>
<Property key="TabWidth" value="8"/>
<Property key="OffsetHeight" value="0"/>
<Codes>
<Code range="32"/>
<Code range="65 90"/>
<Code range="97 122"/>
</Codes>
</Resource>
#. This font is missing a few glyphs (mostly punctuation), but it has all the primary glyphs. If desired, you can now delete the original ``daedric.*`` files from your ``Data Files/Fonts`` directory.
Any Resolution or Size properties in the file have no effect because the engine settings override them.
The engine automatically takes UI scaling factor into account, so don't account for it when tweaking the settings.
Pre-0.45.0 way
--------------
- To replace the primary "Magic Cards" font: - To replace the primary "Magic Cards" font:
#. Download `Pelagiad <https://isaskar.github.io/Pelagiad/>`_ by Isak Larborn (aka Isaskar). #. Download `Pelagiad <https://isaskar.github.io/Pelagiad/>`_ by Isak Larborn (aka Isaskar).
#. Install the ``openmw_font.xml`` file into ``resources/mygui/openmw_font.xml`` in your OpenMW installation. #. Install the ``openmw_font.xml`` file into ``resources/mygui/openmw_font.xml`` in your OpenMW installation.
#. Copy ``Pelagiad.ttf`` into ``resources/mygui/`` as well. #. Copy ``Pelagiad.ttf`` into ``resources/mygui/`` as well.
#. If desired, you can now delete the original ``Magic_Cards.*`` files from your Data Files/Fonts directory. #. If desired, you can now delete the original ``Magic_Cards.*`` files from your ``Data Files/Fonts`` directory.
- You can also replace the Daedric font: - You can also replace the Daedric font:
#. Download `Ayembedt <https://github.com/georgd/OpenMW-Fonts>`_ by Georg Duffner. #. Download `Ayembedt <https://github.com/georgd/OpenMW-Fonts>`_ by Georg Duffner.
#. Install ``OMWAyembedt.otf`` into ``/resources/mygui/`` folder in your OpenMW installation. #. Install ``OMWAyembedt.otf`` into ``/resources/mygui/`` folder in your OpenMW installation.
#. Add the following lines to openmw_font.xml:: #. Add the following lines to ``openmw_font.xml``::
<Resource type="ResourceTrueTypeFont" name="Daedric"> <Resource type="ResourceTrueTypeFont" name="Daedric">
<Property key="Source" value="OMWAyembedt.otf"/> <Property key="Source" value="OMWAyembedt.otf"/>
@ -45,12 +86,12 @@ This is the recommended way to create new fonts.
</Codes> </Codes>
</Resource> </Resource>
#. This font is missing a few glyphs (mostly punctuation), but is complete in the primary glyphs. If desired, you can now delete the original ``daedric.*`` files from your Data Files/Fonts directory. #. This font is missing a few glyphs (mostly punctuation), but it has all the primary glyphs. If desired, you can now delete the original ``daedric.*`` files from your ``Data Files/Fonts`` directory.
- Another replacement for the Daedric font is `Oblivion <http://www.uesp.net/wiki/File:Obliviontt.zip>`_ by Dongle. - Another replacement for the Daedric font is `Oblivion <http://www.uesp.net/wiki/File:Obliviontt.zip>`_ by Dongle.
#. Install the ``Oblivion.ttf`` file resources/mygui/. #. Install the ``Oblivion.ttf`` file into ``resources/mygui/``.
#. The openmw_fonts.xml entry is:: #. The ``openmw_font.xml`` entry is::
<Resource type="ResourceTrueTypeFont" name="Daedric"> <Resource type="ResourceTrueTypeFont" name="Daedric">
<Property key="Source" value="Oblivion.ttf"/> <Property key="Source" value="Oblivion.ttf"/>
@ -80,7 +121,7 @@ This is the recommended way to create new fonts.
Bitmap fonts Bitmap fonts
------------ ------------
Morrowind ``.fnt`` files are essentially a bitmap font, but using them is discouraged because of no Unicode support. Morrowind ``.fnt`` files are essentially a bitmap font, but using them is discouraged because they don't have Unicode support.
MyGUI has its own format for bitmap fonts. An example can be seen by using the --export-fonts command line option (see above), MyGUI has its own format for bitmap fonts. An example can be seen by using the --export-fonts command line option (see above),
which converts Morrowind ``.fnt`` to a MyGUI bitmap font. which converts Morrowind ``.fnt`` to a MyGUI bitmap font.
This is the recommended format to use if you wish to edit Morrowind's bitmap font or create a new bitmap font. This is the recommended format to use if you wish to edit Morrowind's bitmap font or create a new bitmap font.

@ -237,9 +237,9 @@ Since we want the glory of normal mapping in our OpenMW setup, we will go with t
#. OpenMW detects normal maps if they have the same name as the base diffuse texture, but with a *_n.dds* suffix. In this mod, the normal maps has a suffix of *_nm.dds*. Change all the files that ends with *_nm.dds* to instead end with *_n.dds*. #. OpenMW detects normal maps if they have the same name as the base diffuse texture, but with a *_n.dds* suffix. In this mod, the normal maps has a suffix of *_nm.dds*. Change all the files that ends with *_nm.dds* to instead end with *_n.dds*.
#. Finally, `we are done`_! #. Finally, `we are done`_!
Since these models have one or two textures applied to them, the fix was not that time-consuming. Since these models have one or two textures applied to them, the fix was not that time-consuming. The process continues to work for more complex models that use more textures, but looking through each category for texture effects and normal mapped textures rapidly becomes tedious. Luckily, NifSkope provides a feature to do the same automatically.
It gets worse when you have to fix a model that uses loads of textures. The principle is the same,
it just requires more manual work which is annoying and takes time. Rightclick in NifSkope to access the *Spells* dropdown menu, also available via the top bar, hover over the *Blocks* section, and `choose the action to Remove by ID`_. You can then input the RegEx expression ``^NiTextureEffect`` (directing it to remove any block whose name starts with "NiTextureEffect") to automatically remove all texture effect blocks within the NIF. This also has the helpful side effect of listing `all the blocks within the NIF in the bottom section`_, allowing you to additionally root out any blocks referencing *_nm.dds* textures without having to painstakingly open each category.
.. _`Netch Bump mapped`: https://www.nexusmods.com/morrowind/mods/42851/? .. _`Netch Bump mapped`: https://www.nexusmods.com/morrowind/mods/42851/?
.. _`Hlaalu Bump mapped`: https://www.nexusmods.com/morrowind/mods/42396/? .. _`Hlaalu Bump mapped`: https://www.nexusmods.com/morrowind/mods/42396/?
@ -256,3 +256,5 @@ it just requires more manual work which is annoying and takes time.
.. _Blocks: https://imgur.com/VmQC0WG .. _Blocks: https://imgur.com/VmQC0WG
.. _`no longer have shiny models`: https://imgur.com/vu1k7n1 .. _`no longer have shiny models`: https://imgur.com/vu1k7n1
.. _`we are done`: https://imgur.com/yyZxlTw .. _`we are done`: https://imgur.com/yyZxlTw
.. _`choose the action to Remove by ID`: https://imgur.com/a/qs2t0tC
.. _`all the blocks within the NIF in the bottom section`: https://imgur.com/a/UFFNyWt

@ -2,6 +2,7 @@
#define VIDEOPLAYER_VIDEOSTATE_H #define VIDEOPLAYER_VIDEOSTATE_H
#include <stdint.h> #include <stdint.h>
#include <atomic>
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <string> #include <string>
@ -78,7 +79,7 @@ struct PacketQueue {
{ clear(); } { clear(); }
AVPacketList *first_pkt, *last_pkt; AVPacketList *first_pkt, *last_pkt;
volatile bool flushing; std::atomic<bool> flushing;
int nb_packets; int nb_packets;
int size; int size;
@ -169,12 +170,12 @@ struct VideoState {
std::unique_ptr<ParseThread> parse_thread; std::unique_ptr<ParseThread> parse_thread;
std::unique_ptr<VideoThread> video_thread; std::unique_ptr<VideoThread> video_thread;
volatile bool mSeekRequested; std::atomic<bool> mSeekRequested;
uint64_t mSeekPos; uint64_t mSeekPos;
volatile bool mVideoEnded; std::atomic<bool> mVideoEnded;
volatile bool mPaused; std::atomic<bool> mPaused;
volatile bool mQuit; std::atomic<bool> mQuit;
}; };
} }

Loading…
Cancel
Save