mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-30 02:45:32 +00:00
Add OpenMW commits up to 30 Jan 2019
# Conflicts: # apps/openmw/mwworld/worldimp.cpp
This commit is contained in:
commit
6205ff6b04
95 changed files with 1666 additions and 759 deletions
|
@ -13,10 +13,10 @@ Debian:
|
|||
before_script:
|
||||
- export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR
|
||||
- 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
|
||||
- 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://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/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_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/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
|
||||
|
|
|
@ -21,7 +21,7 @@ addons:
|
|||
# Dev
|
||||
cmake, clang-7, clang-tools-7, gcc-8, g++-8,
|
||||
# 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
|
||||
libavcodec-dev, libavformat-dev, libavutil-dev, libswresample-dev, libswscale-dev,
|
||||
# Audio, Video and Misc. deps
|
||||
|
|
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -3,6 +3,9 @@
|
|||
|
||||
Bug #2987: Editor: some chance and AI data fields can overflow
|
||||
Bug #3623: Fix HiDPI on Windows
|
||||
Bug #3733: Normal maps are inverted on mirrored UVs
|
||||
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 #4701: PrisonMarker record is not hardcoded like other markers
|
||||
|
@ -14,11 +17,21 @@
|
|||
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 #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
|
||||
Bug #4813: Creatures with known file but no "Sound Gen Creature" assigned use default sounds
|
||||
Bug #4820: Spell absorption is broken
|
||||
Bug #4827: NiUVController is handled incorrectly
|
||||
Bug #4828: Potion looping effects VFX are not shown for NPCs
|
||||
Feature #2229: Improve pathfinding AI
|
||||
Feature #3442: Default values for fallbacks from ini file
|
||||
Feature #3610: Option to invert X axis
|
||||
Feature #4673: Weapon sheathing
|
||||
Feature #4730: Native animated containers support
|
||||
Feature #4812: Support NiSwitchNode
|
||||
Task #4686: Upgrade media decoder to a more current FFmpeg API
|
||||
|
||||
0.45.0
|
||||
|
|
|
@ -7,5 +7,5 @@ brew switch cmake 3.12.4
|
|||
brew outdated pkgconfig || brew upgrade pkgconfig
|
||||
brew install qt
|
||||
|
||||
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-55c27b1.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
|
||||
|
|
|
@ -34,6 +34,8 @@ VS_VERSION=""
|
|||
NMAKE=""
|
||||
PLATFORM=""
|
||||
CONFIGURATION=""
|
||||
TEST_FRAMEWORK=""
|
||||
GOOGLE_INSTALL_ROOT=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
ARGSTR=$1
|
||||
|
@ -78,6 +80,9 @@ while [ $# -gt 0 ]; do
|
|||
CONFIGURATION=$1
|
||||
shift ;;
|
||||
|
||||
t )
|
||||
TEST_FRAMEWORK=true ;;
|
||||
|
||||
h )
|
||||
cat <<EOF
|
||||
Usage: $0 [-cdehkpuvV]
|
||||
|
@ -94,6 +99,8 @@ Options:
|
|||
Keep the old build directory, default is to delete it.
|
||||
-p <Win32/Win64>
|
||||
Set the build platform, can also be set with environment variable PLATFORM.
|
||||
-t
|
||||
Build unit tests / Google test
|
||||
-u
|
||||
Configure for unity builds.
|
||||
-v <2013/2015/2017>
|
||||
|
@ -395,6 +402,16 @@ if [ -z $SKIP_DOWNLOAD ]; then
|
|||
download "SDL 2.0.7" \
|
||||
"https://www.libsdl.org/release/SDL2-devel-2.0.7-VC.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
|
||||
|
||||
cd .. #/..
|
||||
|
@ -670,6 +687,52 @@ printf "SDL 2.0.7... "
|
|||
add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll"
|
||||
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
|
||||
cd $DEPS_INSTALL/..
|
||||
echo
|
||||
|
|
|
@ -1,11 +1,26 @@
|
|||
#!/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'`
|
||||
SHORT_COMMIT=`git rev-parse --short ${TRAVIS_COMMIT}`
|
||||
SSH_KEY_PATH="$HOME/.ssh/openmw_deploy"
|
||||
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"
|
||||
|
||||
if ! curl --ssl -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}" --silent | 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}"
|
||||
if ! ssh -p "$OSX_DEPLOY_PORT" -i "$SSH_KEY_PATH" "$OSX_DEPLOY_HOST" sh -c "ls \"$REMOTE_PATH\"" | grep "$SHORT_COMMIT" > /dev/null; then
|
||||
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
|
||||
|
|
|
@ -319,9 +319,9 @@ ELSE()
|
|||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
|
||||
|
||||
set(BOOST_COMPONENTS system filesystem program_options)
|
||||
set(BOOST_COMPONENTS system filesystem program_options iostreams)
|
||||
if(WIN32)
|
||||
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale)
|
||||
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale zlib)
|
||||
endif(WIN32)
|
||||
|
||||
IF(BOOST_STATIC)
|
||||
|
|
|
@ -269,7 +269,7 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages)
|
|||
bool interior = cellRecord.mId.substr (0, 1)!="#";
|
||||
|
||||
// count new references and adjust RefNumCount accordingsly
|
||||
int newRefNum = cellRecord.mRefNumCounter;
|
||||
unsigned int newRefNum = cellRecord.mRefNumCounter;
|
||||
|
||||
if (references!=mState.getSubRecords().end())
|
||||
{
|
||||
|
@ -279,11 +279,17 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages)
|
|||
const CSMWorld::Record<CSMWorld::CellRef>& ref =
|
||||
mDocument.getData().getReferences().getRecord (*iter);
|
||||
|
||||
if (ref.get().mNew ||
|
||||
CSMWorld::CellRef refRecord = ref.get();
|
||||
|
||||
if (refRecord.mNew ||
|
||||
(!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly &&
|
||||
/// \todo consider worldspace
|
||||
CSMWorld::CellCoordinates (ref.get().getCellIndex()).getId("")!=ref.get().mCell))
|
||||
CSMWorld::CellCoordinates (refRecord.getCellIndex()).getId("") != refRecord.mCell))
|
||||
++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;
|
||||
}
|
||||
|
||||
if (refRecord.mNew ||
|
||||
if (refRecord.mNew || refRecord.mRefNum.mIndex == 0 ||
|
||||
(!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly &&
|
||||
refRecord.mCell!=stream.str()))
|
||||
{
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
#include "intsetting.hpp"
|
||||
#include "doublesetting.hpp"
|
||||
|
@ -393,9 +392,7 @@ CSMPrefs::IntSetting& CSMPrefs::State::declareInt (const std::string& key,
|
|||
if (mCurrentCategory==mCategories.end())
|
||||
throw std::logic_error ("no category for setting");
|
||||
|
||||
std::ostringstream stream;
|
||||
stream << default_;
|
||||
setDefault (key, stream.str());
|
||||
setDefault(key, std::to_string(default_));
|
||||
|
||||
default_ = mSettings.getInt (key, mCurrentCategory->second.getKey());
|
||||
|
||||
|
@ -414,9 +411,7 @@ CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble (const std::string& key,
|
|||
if (mCurrentCategory==mCategories.end())
|
||||
throw std::logic_error ("no category for setting");
|
||||
|
||||
std::ostringstream stream;
|
||||
stream << default_;
|
||||
setDefault (key, stream.str());
|
||||
setDefault(key, std::to_string(default_));
|
||||
|
||||
default_ = mSettings.getFloat (key, mCurrentCategory->second.getKey());
|
||||
|
||||
|
|
|
@ -77,14 +77,9 @@ void CSMTools::Search::searchRecordStateCell (const CSMWorld::IdTableBase *model
|
|||
{
|
||||
std::vector<std::string> states =
|
||||
CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification);
|
||||
|
||||
std::ostringstream message;
|
||||
message << states.at (data);
|
||||
|
||||
std::ostringstream hint;
|
||||
hint << "r: " << model->getColumnId (index.column());
|
||||
|
||||
messages.add (id, message.str(), hint.str());
|
||||
const std::string hint = "r: " + std::to_string(model->getColumnId(index.column()));
|
||||
messages.add (id, states.at(data), hint);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "collectionbase.hpp"
|
||||
#include "land.hpp"
|
||||
#include "landtexture.hpp"
|
||||
#include "ref.hpp"
|
||||
|
||||
namespace CSMWorld
|
||||
{
|
||||
|
@ -259,6 +260,11 @@ namespace CSMWorld
|
|||
copy.mState = RecordBase::State_ModifiedOnly;
|
||||
IdAccessorT().setId(copy.get(), destination);
|
||||
|
||||
if (type == UniversalId::Type_Reference) {
|
||||
CSMWorld::CellRef* ptr = (CSMWorld::CellRef*) ©.mModified;
|
||||
ptr->mRefNum.mIndex = 0;
|
||||
}
|
||||
|
||||
int index = getAppendIndex(destination, type);
|
||||
insertRecord(copy, getAppendIndex(destination, type));
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <memory>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include <components/misc/constants.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::ModifyCommand> modifyDataRefNum;
|
||||
|
||||
int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt();
|
||||
|
||||
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.
|
||||
IdTable& model2 = dynamic_cast<IdTable&> (*mDocument.getData().getTableModel (mId));
|
||||
|
||||
int cellColumn = model2.searchColumnIndex (Columns::ColumnId_Cell);
|
||||
|
||||
if (cellColumn!=-1)
|
||||
// Modulate by cell size, update cell id if reference has been moved to a new cell
|
||||
if (std::abs(std::fmod(oldPosition, Constants::CellSizeInUnits))
|
||||
- std::abs(std::fmod(new_.toFloat(), Constants::CellSizeInUnits)) >= 0.5f)
|
||||
{
|
||||
QModelIndex cellIndex = model2.index (row, cellColumn);
|
||||
IdTableProxyModel *proxy = dynamic_cast<IdTableProxyModel *> (model);
|
||||
|
||||
std::string cellId = model2.data (cellIndex).toString().toUtf8().data();
|
||||
int row = proxy ? proxy->mapToSource (index).row() : index.row();
|
||||
|
||||
if (cellId.find ('#')!=std::string::npos)
|
||||
// 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));
|
||||
|
||||
int cellColumn = model2.searchColumnIndex (Columns::ColumnId_Cell);
|
||||
|
||||
if (cellColumn!=-1)
|
||||
{
|
||||
// Need to recalculate the cell
|
||||
modifyCell.reset (new UpdateCellCommand (model2, row));
|
||||
QModelIndex cellIndex = model2.index (row, cellColumn);
|
||||
|
||||
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());
|
||||
macro.push (modifyData.release());
|
||||
macro.push (modifyCell.release());
|
||||
if (modifyDataRefNum.get())
|
||||
macro.push (modifyDataRefNum.release());
|
||||
}
|
||||
else
|
||||
mDocument.getUndoStack().push (modifyData.release());
|
||||
|
|
|
@ -221,7 +221,11 @@ std::string CSMWorld::IdTable::getId(int row) const
|
|||
///This method can return only indexes to the top level table cells
|
||||
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)
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
#include "ref.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "cellcoordinates.hpp"
|
||||
|
||||
CSMWorld::CellRef::CellRef() : mNew (true)
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
#include "refcollection.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include <components/esm/loadcell.hpp>
|
||||
|
||||
|
@ -32,40 +29,31 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
|
|||
|
||||
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::ostringstream stream;
|
||||
stream << "#" << index.first << " " << index.second;
|
||||
ref.mCell = "#" + std::to_string(index.first) + " " + std::to_string(index.second);
|
||||
|
||||
ref.mCell = stream.str();
|
||||
|
||||
if (!base && // don't try to update base records
|
||||
mref.mRefNum.mIndex != 0) // MVRF tag found
|
||||
// Handle non-base moved references
|
||||
if (!base && mref.mRefNum.mIndex != 0)
|
||||
{
|
||||
// there is a requirement for a placeholder where the original object was
|
||||
//
|
||||
// see the forum discussions here for more details:
|
||||
// https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30
|
||||
// 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
|
||||
ref.mOriginalCell = cell2.mId;
|
||||
|
||||
// It is not always possibe to ignore moved references sub-record and
|
||||
// calculate from coordinates. Some mods may place the ref in positions
|
||||
// outside normal bounds, resulting in non sensical cell id's. This often
|
||||
// happens if the moved ref was deleted.
|
||||
//
|
||||
// Use the target cell from the MVRF tag but if different output an error
|
||||
// message
|
||||
// Some mods may move references outside of the bounds, which often happens they are deleted.
|
||||
// This results in nonsensical autocalculated cell IDs, so we must use the record target cell.
|
||||
|
||||
// Log a warning if the record target cell is different
|
||||
if (index.first != mref.mTarget[0] || index.second != mref.mTarget[1])
|
||||
{
|
||||
Log(Debug::Warning) << "Warning: the Position of moved ref "
|
||||
<< ref.mRefID << " does not match the target cell";
|
||||
Log(Debug::Warning) << "Position: #" << index.first << " " << index.second
|
||||
<<", Target #"<< mref.mTarget[0] << " " << mref.mTarget[1];
|
||||
std::string indexCell = ref.mCell;
|
||||
ref.mCell = "#" + std::to_string(mref.mTarget[0]) + " " + std::to_string(mref.mTarget[1]);
|
||||
|
||||
stream.clear();
|
||||
stream << "#" << mref.mTarget[0] << " " << mref.mTarget[1];
|
||||
ref.mCell = stream.str(); // overwrite
|
||||
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId (cellIndex));
|
||||
messages.add(id, "The position of the moved reference " + ref.mRefID + " (cell " + indexCell + ")"
|
||||
" does not match the target cell (" + ref.mCell + ")",
|
||||
std::string(), CSMDoc::Message::Severity_Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +75,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
|
|||
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell,
|
||||
mCells.getId (cellIndex));
|
||||
|
||||
messages.add (id, "Attempt to delete a non-existing reference");
|
||||
messages.add (id, "Attempt to delete a non-existent reference");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -140,7 +128,5 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
|
|||
|
||||
std::string CSMWorld::RefCollection::getNewId()
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << "ref#" << mNextId++;
|
||||
return stream.str();
|
||||
return "ref#" + std::to_string(mNextId++);
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ int CSMWorld::Resources::getIndex (const std::string& id) const
|
|||
std::ostringstream stream;
|
||||
stream << "Invalid resource: " << mBaseDirectory << '/' << id;
|
||||
|
||||
throw std::runtime_error (stream.str().c_str());
|
||||
throw std::runtime_error (stream.str());
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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
|
||||
|
|
|
@ -670,6 +670,33 @@ void CSVRender::Object::apply (CSMWorld::CommandMacro& commands)
|
|||
|
||||
if (mOverrideFlags & Override_Position)
|
||||
{
|
||||
//Do cell check first so positions can be compared
|
||||
const CSMWorld::CellRef& ref = collection.getRecord(recordIndex).get();
|
||||
|
||||
if (CSMWorld::CellCoordinates::isExteriorCell(ref.mCell))
|
||||
{
|
||||
// Find cell index at new position
|
||||
std::pair<int, int> cellIndex = CSMWorld::CellCoordinates::coordinatesToCellIndex(
|
||||
mPositionOverride.pos[0], mPositionOverride.pos[1]);
|
||||
std::pair<int, int> originalIndex = ref.getCellIndex();
|
||||
|
||||
int cellColumn = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (
|
||||
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 ("");
|
||||
|
||||
commands.push (new CSMWorld::ModifyCommand (*model,
|
||||
model->index (recordIndex, cellColumn), QString::fromUtf8 (cellId.c_str())));
|
||||
commands.push (new CSMWorld::ModifyCommand( *model,
|
||||
model->index (recordIndex, refNumColumn), 0));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<3; ++i)
|
||||
{
|
||||
int column = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (
|
||||
|
@ -678,20 +705,6 @@ void CSVRender::Object::apply (CSMWorld::CommandMacro& commands)
|
|||
commands.push (new CSMWorld::ModifyCommand (*model,
|
||||
model->index (recordIndex, column), mPositionOverride.pos[i]));
|
||||
}
|
||||
|
||||
int column = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (
|
||||
CSMWorld::Columns::ColumnId_Cell));
|
||||
|
||||
if (CSMWorld::CellCoordinates::isExteriorCell(collection.getRecord (recordIndex).get().mCell))
|
||||
{
|
||||
std::pair<int, int> cellIndex = collection.getRecord (recordIndex).get().getCellIndex();
|
||||
|
||||
/// \todo figure out worldspace (not important until multiple worldspaces are supported)
|
||||
std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId ("");
|
||||
|
||||
commands.push (new CSMWorld::ModifyCommand (*model,
|
||||
model->index (recordIndex, column), QString::fromUtf8 (cellId.c_str())));
|
||||
}
|
||||
}
|
||||
|
||||
if (mOverrideFlags & Override_Rotation)
|
||||
|
|
|
@ -150,10 +150,8 @@ std::string CSVWorld::SceneSubView::getTitle() const
|
|||
void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::UniversalId& id)
|
||||
{
|
||||
setUniversalId(id);
|
||||
std::ostringstream stream;
|
||||
stream << "Scene: " << getUniversalId().getId();
|
||||
|
||||
mTitle = stream.str();
|
||||
mTitle = "Scene: " + getUniversalId().getId();
|
||||
setWindowTitle (QString::fromUtf8 (mTitle.c_str()));
|
||||
emit updateTitle();
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include <QSplitter>
|
||||
#include <QTimer>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include "../../model/doc/document.hpp"
|
||||
#include "../../model/world/universalid.hpp"
|
||||
#include "../../model/world/data.hpp"
|
||||
|
@ -210,18 +212,28 @@ void CSVWorld::ScriptSubView::useHint (const std::string& hint)
|
|||
unsigned line = 0, column = 0;
|
||||
char c;
|
||||
std::istringstream stream (hint.c_str()+1);
|
||||
switch(hint[0]){
|
||||
switch(hint[0])
|
||||
{
|
||||
case 'R':
|
||||
case 'r':
|
||||
{
|
||||
QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn);
|
||||
QString source = mModel->data (index).toString();
|
||||
unsigned stringSize = source.length();
|
||||
unsigned pos, dummy;
|
||||
if (!(stream >> c >> dummy >> pos) )
|
||||
return;
|
||||
|
||||
for (unsigned i = 0; i <= pos; ++i){
|
||||
if (source[i] == '\n'){
|
||||
if (pos > stringSize)
|
||||
{
|
||||
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;
|
||||
column = i+1;
|
||||
}
|
||||
|
@ -231,7 +243,7 @@ void CSVWorld::ScriptSubView::useHint (const std::string& hint)
|
|||
}
|
||||
case 'l':
|
||||
if (!(stream >> c >> line >> column))
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor cursor = mEditor->textCursor();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "util.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QUndoStack>
|
||||
|
@ -214,7 +215,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO
|
|||
case CSMWorld::ColumnBase::Display_UnsignedInteger8:
|
||||
{
|
||||
DialogueSpinBox *sb = new DialogueSpinBox(parent);
|
||||
sb->setRange(0, UCHAR_MAX);
|
||||
sb->setRange(0, std::numeric_limits<unsigned char>::max());
|
||||
return sb;
|
||||
}
|
||||
|
||||
|
@ -225,7 +226,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO
|
|||
case CSMWorld::ColumnBase::Display_Float:
|
||||
{
|
||||
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->setDecimals(3);
|
||||
return dsb;
|
||||
|
@ -234,7 +235,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO
|
|||
case CSMWorld::ColumnBase::Display_Double:
|
||||
{
|
||||
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->setDecimals(6);
|
||||
return dsb;
|
||||
|
|
|
@ -245,14 +245,23 @@ bool OMW::Engine::frame(float frametime)
|
|||
*/
|
||||
}
|
||||
|
||||
// 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
|
||||
osg::Timer_t beforePhysicsTick = osg::Timer::instance()->tick();;
|
||||
osg::Timer_t beforeWorldTick = osg::Timer::instance()->tick();
|
||||
if (mEnvironment.getStateManager()->getState()!=
|
||||
MWBase::StateManager::State_NoGame)
|
||||
{
|
||||
mEnvironment.getWorld()->update(frametime, guiActive);
|
||||
}
|
||||
osg::Timer_t afterPhysicsTick = osg::Timer::instance()->tick();
|
||||
osg::Timer_t afterWorldTick = osg::Timer::instance()->tick();
|
||||
|
||||
// update GUI
|
||||
mEnvironment.getWindowManager()->onFrame(frametime);
|
||||
|
@ -271,6 +280,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_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"))
|
||||
{
|
||||
mResourceSystem->reportStats(frameNumber, stats);
|
||||
|
@ -819,10 +832,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),
|
||||
"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);
|
||||
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);
|
||||
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);
|
||||
|
||||
|
|
|
@ -523,6 +523,7 @@ namespace MWBase
|
|||
/// \return pointer to created record
|
||||
|
||||
virtual void update (float duration, bool paused) = 0;
|
||||
virtual void updatePhysics (float duration, bool paused) = 0;
|
||||
|
||||
virtual void updateWindowManager () = 0;
|
||||
|
||||
|
|
|
@ -775,34 +775,59 @@ namespace MWClass
|
|||
|
||||
std::string Creature::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const
|
||||
{
|
||||
const MWWorld::Store<ESM::SoundGenerator> &store = MWBase::Environment::get().getWorld()->getStore().get<ESM::SoundGenerator>();
|
||||
|
||||
int type = getSndGenTypeFromName(ptr, name);
|
||||
if(type >= 0)
|
||||
if (type < 0)
|
||||
return std::string();
|
||||
|
||||
std::vector<const ESM::SoundGenerator*> sounds;
|
||||
std::vector<const ESM::SoundGenerator*> fallbacksounds;
|
||||
|
||||
MWWorld::LiveCellRef<ESM::Creature>* ref = ptr.get<ESM::Creature>();
|
||||
|
||||
const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal;
|
||||
|
||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||
auto sound = store.get<ESM::SoundGenerator>().begin();
|
||||
while (sound != store.get<ESM::SoundGenerator>().end())
|
||||
{
|
||||
std::vector<const ESM::SoundGenerator*> sounds;
|
||||
std::vector<const ESM::SoundGenerator*> fallbacksounds;
|
||||
|
||||
MWWorld::LiveCellRef<ESM::Creature>* ref = ptr.get<ESM::Creature>();
|
||||
|
||||
const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal;
|
||||
|
||||
MWWorld::Store<ESM::SoundGenerator>::iterator sound = store.begin();
|
||||
while (sound != store.end())
|
||||
{
|
||||
if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(ourId, sound->mCreature)))
|
||||
sounds.push_back(&*sound);
|
||||
if (type == sound->mType && sound->mCreature.empty())
|
||||
fallbacksounds.push_back(&*sound);
|
||||
++sound;
|
||||
}
|
||||
if (!sounds.empty())
|
||||
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
|
||||
if (!fallbacksounds.empty())
|
||||
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
|
||||
if (type == sound->mType && !sound->mCreature.empty() && Misc::StringUtils::ciEqual(ourId, sound->mCreature))
|
||||
sounds.push_back(&*sound);
|
||||
if (type == sound->mType && sound->mCreature.empty())
|
||||
fallbacksounds.push_back(&*sound);
|
||||
++sound;
|
||||
}
|
||||
|
||||
return "";
|
||||
if (sounds.empty())
|
||||
{
|
||||
const std::string model = getModel(ptr);
|
||||
if (!model.empty())
|
||||
{
|
||||
for (const ESM::Creature &creature : store.get<ESM::Creature>())
|
||||
{
|
||||
if (creature.mId != ourId && creature.mOriginal != ourId && !creature.mModel.empty()
|
||||
&& Misc::StringUtils::ciEqual(model, "meshes\\" + creature.mModel))
|
||||
{
|
||||
const std::string& fallbackId = !creature.mOriginal.empty() ? creature.mOriginal : creature.mId;
|
||||
sound = store.get<ESM::SoundGenerator>().begin();
|
||||
while (sound != store.get<ESM::SoundGenerator>().end())
|
||||
{
|
||||
if (type == sound->mType && !sound->mCreature.empty()
|
||||
&& Misc::StringUtils::ciEqual(fallbackId, sound->mCreature))
|
||||
sounds.push_back(&*sound);
|
||||
++sound;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sounds.empty())
|
||||
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
|
||||
if (!fallbacksounds.empty())
|
||||
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
MWWorld::Ptr Creature::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const
|
||||
|
|
|
@ -1357,6 +1357,17 @@ namespace MWClass
|
|||
const ESM::Race* race =
|
||||
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())
|
||||
{
|
||||
scale.x() *= race->mData.mWeight.mMale;
|
||||
|
@ -1369,7 +1380,6 @@ namespace MWClass
|
|||
scale.y() *= race->mData.mWeight.mFemale;
|
||||
scale.z() *= race->mData.mHeight.mFemale;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int Npc::getServices(const MWWorld::ConstPtr &actor) const
|
||||
|
|
|
@ -476,8 +476,7 @@ namespace MWGui
|
|||
icon.insert(slashPos+1, "b_");
|
||||
icon = MWBase::Environment::get().getWindowManager()->correctIconPath(icon);
|
||||
|
||||
mSpellImage->setItem(MWWorld::Ptr());
|
||||
mSpellImage->setIcon(icon);
|
||||
mSpellImage->setSpellIcon(icon);
|
||||
}
|
||||
|
||||
void HUD::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent)
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace MWGui
|
|||
class DragAndDrop;
|
||||
class SpellIcons;
|
||||
class ItemWidget;
|
||||
class SpellWidget;
|
||||
|
||||
class HUD : public WindowBase, public LocalMapBase
|
||||
{
|
||||
|
@ -64,7 +65,8 @@ namespace MWGui
|
|||
MyGUI::ProgressBar *mHealth, *mMagicka, *mStamina, *mEnemyHealth, *mDrowning;
|
||||
MyGUI::Widget* mHealthFrame;
|
||||
MyGUI::Widget *mWeapBox, *mSpellBox, *mSneakBox;
|
||||
ItemWidget *mWeapImage, *mSpellImage;
|
||||
ItemWidget *mWeapImage;
|
||||
SpellWidget *mSpellImage;
|
||||
MyGUI::ProgressBar *mWeapStatus, *mSpellStatus;
|
||||
MyGUI::Widget *mEffectBox, *mMinimapBox;
|
||||
MyGUI::Button* mMinimapButton;
|
||||
|
|
|
@ -43,6 +43,7 @@ namespace MWGui
|
|||
void ItemWidget::registerComponents()
|
||||
{
|
||||
MyGUI::FactoryManager::getInstance().registerFactory<ItemWidget>("Widget");
|
||||
MyGUI::FactoryManager::getInstance().registerFactory<SpellWidget>("Widget");
|
||||
}
|
||||
|
||||
void ItemWidget::initialiseOverride()
|
||||
|
@ -153,4 +154,14 @@ namespace MWGui
|
|||
setIcon(ptr);
|
||||
}
|
||||
|
||||
void SpellWidget::setSpellIcon(const std::string& icon)
|
||||
{
|
||||
if (mFrame)
|
||||
mFrame->setImageTexture("");
|
||||
if (mItemShadow)
|
||||
mItemShadow->setImageTexture(icon);
|
||||
if (mItem)
|
||||
mItem->setImageTexture(icon);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace MWGui
|
|||
void setIcon (const MWWorld::Ptr& ptr);
|
||||
void setFrame (const std::string& frame, const MyGUI::IntCoord& coord);
|
||||
|
||||
private:
|
||||
protected:
|
||||
virtual void initialiseOverride();
|
||||
|
||||
MyGUI::ImageBox* mItem;
|
||||
|
@ -52,6 +52,14 @@ namespace MWGui
|
|||
std::string mCurrentFrame;
|
||||
};
|
||||
|
||||
class SpellWidget : public ItemWidget
|
||||
{
|
||||
MYGUI_RTTI_DERIVED(SpellWidget)
|
||||
public:
|
||||
|
||||
void setSpellIcon (const std::string& icon);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -58,7 +58,10 @@ namespace MWGui
|
|||
|
||||
void Layout::setTitle(const std::string& title)
|
||||
{
|
||||
static_cast<MyGUI::Window*>(mMainWidget)->setCaptionWithReplacing(title);
|
||||
MyGUI::Window* window = static_cast<MyGUI::Window*>(mMainWidget);
|
||||
|
||||
if (window->getCaption() != title)
|
||||
window->setCaptionWithReplacing(title);
|
||||
}
|
||||
|
||||
MyGUI::Widget* Layout::getWidget(const std::string &_name)
|
||||
|
|
|
@ -125,9 +125,7 @@ namespace MWGui
|
|||
for (int i=0; ids[i]; ++i)
|
||||
if (ids[i]==id)
|
||||
{
|
||||
std::ostringstream valueString;
|
||||
valueString << value.getModified();
|
||||
setText (id, valueString.str());
|
||||
setText (id, std::to_string(value.getModified()));
|
||||
|
||||
MyGUI::TextBox* box;
|
||||
getWidget(box, id);
|
||||
|
|
|
@ -599,9 +599,7 @@ namespace MWGui
|
|||
|
||||
std::string ToolTips::toString(const int value)
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << value;
|
||||
return stream.str();
|
||||
return std::to_string(value);
|
||||
}
|
||||
|
||||
std::string ToolTips::getWeightString(const float weight, const std::string& prefix)
|
||||
|
|
|
@ -87,10 +87,7 @@ namespace MWGui
|
|||
else
|
||||
toAdd->setUserString("interior","n");
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << price;
|
||||
toAdd->setUserString("price",oss.str());
|
||||
|
||||
toAdd->setUserString("price", std::to_string(price));
|
||||
toAdd->setCaptionWithReplacing("#{sCell=" + name + "} - " + MyGUI::utility::toString(price)+"#{sgp}");
|
||||
toAdd->setSize(mDestinationsView->getWidth(),lineHeight);
|
||||
toAdd->eventMouseWheel += MyGUI::newDelegate(this, &TravelWindow::onMouseWheel);
|
||||
|
|
|
@ -922,8 +922,13 @@ namespace MWMechanics
|
|||
|
||||
bool hasSummonEffect = false;
|
||||
for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it)
|
||||
{
|
||||
if (isSummoningEffect(it->first.mId))
|
||||
{
|
||||
hasSummonEffect = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!creatureStats.getSummonedCreatureMap().empty() || !creatureStats.getSummonedCreatureGraveyard().empty() || hasSummonEffect)
|
||||
{
|
||||
|
@ -1524,6 +1529,14 @@ namespace MWMechanics
|
|||
bool cellChanged = world->hasCellChanged();
|
||||
MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports
|
||||
updateActor(actor, duration);
|
||||
|
||||
// Looping magic VFX update
|
||||
// Note: we need to do this before any of the animations are updated.
|
||||
// Reaching the text keys may trigger Hit / Spellcast (and as such, particles),
|
||||
// so updating VFX immediately after that would just remove the particle effects instantly.
|
||||
// There needs to be a magic effect update in between.
|
||||
ctrl->updateContinuousVfx();
|
||||
|
||||
if (!cellChanged && world->hasCellChanged())
|
||||
{
|
||||
return; // for now abort update of the old cell when cell changes by teleportation magic effect
|
||||
|
@ -1609,14 +1622,6 @@ namespace MWMechanics
|
|||
timerUpdateEquippedLight += duration;
|
||||
mTimerDisposeSummonsCorpses += duration;
|
||||
|
||||
// Looping magic VFX update
|
||||
// Note: we need to do this before any of the animations are updated.
|
||||
// Reaching the text keys may trigger Hit / Spellcast (and as such, particles),
|
||||
// so updating VFX immediately after that would just remove the particle effects instantly.
|
||||
// There needs to be a magic effect update in between.
|
||||
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
||||
iter->second->getCharacterController()->updateContinuousVfx();
|
||||
|
||||
// Animation/movement update
|
||||
CharacterController* playerCharacter = nullptr;
|
||||
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#include "aiwander.hpp"
|
||||
|
||||
#include <cfloat>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/esm/aisequence.hpp>
|
||||
|
|
|
@ -167,9 +167,8 @@ void MWMechanics::Alchemy::updateEffects()
|
|||
|
||||
if (magicEffect->mData.mBaseCost<=0)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "invalid base cost for magic effect " << iter->mId;
|
||||
throw std::runtime_error (os.str());
|
||||
const std::string os = "invalid base cost for magic effect " + std::to_string(iter->mId);
|
||||
throw std::runtime_error (os);
|
||||
}
|
||||
|
||||
float fPotionT1MagMul =
|
||||
|
|
|
@ -240,19 +240,6 @@ namespace MWMechanics
|
|||
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)
|
||||
{
|
||||
// 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)
|
||||
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)
|
||||
{
|
||||
minChance = s - x;
|
||||
|
@ -308,7 +295,7 @@ namespace MWMechanics
|
|||
|
||||
float skillTerm = 0;
|
||||
if (effectiveSchool != -1)
|
||||
skillTerm = 2.f * actorSkills[mapSchoolToSkill(effectiveSchool)];
|
||||
skillTerm = 2.f * actorSkills[spellSchoolToSkill(effectiveSchool)];
|
||||
else
|
||||
calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this
|
||||
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
#ifndef OPENMW_AUTOCALCSPELL_H
|
||||
#define OPENMW_AUTOCALCSPELL_H
|
||||
|
||||
#include <cfloat>
|
||||
#include <set>
|
||||
|
||||
#include <components/esm/loadspel.hpp>
|
||||
#include <components/esm/loadskil.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);
|
||||
|
||||
ESM::Skill::SkillEnum mapSchoolToSkill(int school);
|
||||
|
||||
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);
|
||||
|
|
|
@ -76,13 +76,6 @@ void wrap(float& rad)
|
|||
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)
|
||||
{
|
||||
int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2;
|
||||
|
@ -251,13 +244,13 @@ public:
|
|||
std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const
|
||||
{
|
||||
int numAnims=0;
|
||||
while (mAnimation->hasAnimation(prefix + toString(numAnims+1)))
|
||||
while (mAnimation->hasAnimation(prefix + std::to_string(numAnims+1)))
|
||||
++numAnims;
|
||||
|
||||
int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims]
|
||||
if (num)
|
||||
*num = roll;
|
||||
return prefix + toString(roll);
|
||||
return prefix + std::to_string(roll);
|
||||
}
|
||||
|
||||
void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
|
||||
|
@ -799,7 +792,7 @@ void CharacterController::playDeath(float startpoint, CharacterState death)
|
|||
mCurrentDeath = "deathknockout";
|
||||
break;
|
||||
default:
|
||||
mCurrentDeath = "death" + toString(death - CharState_Death1 + 1);
|
||||
mCurrentDeath = "death" + std::to_string(death - CharState_Death1 + 1);
|
||||
}
|
||||
mDeathState = death;
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
#include "mechanicsmanagerimp.hpp"
|
||||
|
||||
#include <limits.h>
|
||||
#include <set>
|
||||
|
||||
#include <components/misc/rng.hpp>
|
||||
|
||||
#include <components/esm/esmwriter.hpp>
|
||||
|
@ -252,7 +249,7 @@ namespace MWMechanics
|
|||
// mWatchedTimeToStartDrowning = -1 for correct drowning state check,
|
||||
// if stats.getTimeToStartDrowning() == 0 already on game start
|
||||
MechanicsManager::MechanicsManager()
|
||||
: mWatchedTimeToStartDrowning(-1), mWatchedStatsEmpty (true), mUpdatePlayer (true), mClassSelected (false),
|
||||
: mWatchedLevel(-1), mWatchedTimeToStartDrowning(-1), mWatchedStatsEmpty (true), mUpdatePlayer (true), mClassSelected (false),
|
||||
mRaceSelected (false), mAI(true)
|
||||
{
|
||||
//buildPlayer no longer here, needs to be done explicitly after all subsystems are up and running
|
||||
|
@ -377,7 +374,11 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
winMgr->setValue("level", stats.getLevel());
|
||||
if(stats.getLevel() != mWatchedLevel)
|
||||
{
|
||||
mWatchedLevel = stats.getLevel();
|
||||
winMgr->setValue("level", mWatchedLevel);
|
||||
}
|
||||
|
||||
mWatchedStatsEmpty = false;
|
||||
|
||||
|
@ -393,10 +394,15 @@ namespace MWMechanics
|
|||
MWWorld::ContainerStoreIterator enchantItem = inv.getSelectedEnchantItem();
|
||||
if (enchantItem != inv.end())
|
||||
winMgr->setSelectedEnchantItem(*enchantItem);
|
||||
else if (!winMgr->getSelectedSpell().empty())
|
||||
winMgr->setSelectedSpell(winMgr->getSelectedSpell(), int(MWMechanics::getSpellSuccessChance(winMgr->getSelectedSpell(), mWatched)));
|
||||
else
|
||||
winMgr->unsetSelectedSpell();
|
||||
{
|
||||
const std::string& spell = winMgr->getSelectedSpell();
|
||||
if (!spell.empty())
|
||||
winMgr->setSelectedSpell(spell, int(MWMechanics::getSpellSuccessChance(spell, mWatched)));
|
||||
else
|
||||
winMgr->unsetSelectedSpell();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (mUpdatePlayer)
|
||||
|
|
|
@ -30,6 +30,8 @@ namespace MWMechanics
|
|||
DynamicStat<float> mWatchedMagicka;
|
||||
DynamicStat<float> mWatchedFatigue;
|
||||
|
||||
int mWatchedLevel;
|
||||
|
||||
float mWatchedTimeToStartDrowning;
|
||||
|
||||
bool mWatchedStatsEmpty;
|
||||
|
|
|
@ -152,20 +152,18 @@ float MWMechanics::NpcStats::getSkillProgressRequirement (int skillIndex, const
|
|||
float typeFactor = gmst.find ("fMiscSkillBonus")->mValue.getFloat();
|
||||
|
||||
for (int i=0; i<5; ++i)
|
||||
{
|
||||
if (class_.mData.mSkills[i][0]==skillIndex)
|
||||
{
|
||||
typeFactor = gmst.find ("fMinorSkillBonus")->mValue.getFloat();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i=0; i<5; ++i)
|
||||
if (class_.mData.mSkills[i][1]==skillIndex)
|
||||
else if (class_.mData.mSkills[i][1]==skillIndex)
|
||||
{
|
||||
typeFactor = gmst.find ("fMajorSkillBonus")->mValue.getFloat();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
progressRequirement *= typeFactor;
|
||||
|
||||
|
@ -233,15 +231,14 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas
|
|||
if (class_.mData.mSkills[k][0] == skillIndex)
|
||||
{
|
||||
mLevelProgress += gmst.find("iLevelUpMinorMult")->mValue.getInteger();
|
||||
increase = gmst.find("iLevelUpMajorMultAttribute")->mValue.getInteger();
|
||||
increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger();
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int k=0; k<5; ++k)
|
||||
{
|
||||
if (class_.mData.mSkills[k][1] == skillIndex)
|
||||
else if (class_.mData.mSkills[k][1] == skillIndex)
|
||||
{
|
||||
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 <cfloat>
|
||||
#include <limits>
|
||||
#include <iomanip>
|
||||
|
||||
|
@ -519,8 +518,11 @@ namespace MWMechanics
|
|||
|
||||
float magnitudeMult = 1;
|
||||
|
||||
if (!absorbed && target.getClass().isActor())
|
||||
if (target.getClass().isActor())
|
||||
{
|
||||
if (absorbed)
|
||||
continue;
|
||||
|
||||
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
||||
// Reflect harmful effects
|
||||
if (isHarmful && !reflected && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable))
|
||||
|
|
|
@ -1339,6 +1339,7 @@ namespace MWPhysics
|
|||
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 swimming = world->isSwimming(iter->first);
|
||||
|
||||
bool wasOnGround = physicActor->getOnGround();
|
||||
osg::Vec3f position = physicActor->getPosition();
|
||||
|
@ -1361,8 +1362,9 @@ namespace MWPhysics
|
|||
float heightDiff = position.z() - oldHeight;
|
||||
|
||||
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
||||
if ((numSteps > 0 && wasOnGround && physicActor->getOnGround()) || flying || world->isSwimming(iter->first) || slowFall < 1)
|
||||
stats.land(iter->first == player);
|
||||
bool isStillOnGround = (numSteps > 0 && wasOnGround && physicActor->getOnGround());
|
||||
if (isStillOnGround || flying || swimming || slowFall < 1)
|
||||
stats.land(iter->first == player && (flying || swimming));
|
||||
else if (heightDiff < 0)
|
||||
stats.addToFallHeight(-heightDiff);
|
||||
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
#include <osg/Stats>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <components/resource/objectcache.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
@ -21,9 +19,7 @@ LandManager::LandManager(int loadFlags)
|
|||
|
||||
osg::ref_ptr<ESMTerrain::LandObject> LandManager::getLand(int x, int y)
|
||||
{
|
||||
std::ostringstream id;
|
||||
id << x << " " << y;
|
||||
std::string idstr = id.str();
|
||||
std::string idstr = std::to_string(x) + " " + std::to_string(y);
|
||||
|
||||
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(idstr);
|
||||
if (obj)
|
||||
|
|
|
@ -30,6 +30,11 @@ namespace MWRender
|
|||
|
||||
void disable();
|
||||
|
||||
bool isEnabled() const
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
private:
|
||||
osg::ref_ptr<osg::Group> mRootNode;
|
||||
bool mEnabled;
|
||||
|
|
|
@ -317,8 +317,9 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
|
|||
mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
|
||||
|
||||
mViewMode = viewMode;
|
||||
rebuild();
|
||||
MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale()); // apply race height after view change
|
||||
|
||||
rebuild();
|
||||
setRenderBin();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#include "objects.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <osg/Group>
|
||||
#include <osg/UserDataContainer>
|
||||
|
||||
|
|
|
@ -599,28 +599,8 @@ namespace MWRender
|
|||
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);
|
||||
|
||||
osg::Vec3f focal, cameraPos;
|
||||
|
@ -1402,4 +1382,33 @@ namespace MWRender
|
|||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -240,6 +240,8 @@ namespace MWRender
|
|||
|
||||
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> mIntersectionVisitor;
|
||||
|
|
|
@ -517,6 +517,12 @@ namespace MWScript
|
|||
std::string effect = runtime.getStringLiteral(runtime[0].mInteger);
|
||||
runtime.pop();
|
||||
|
||||
if (!ptr.getClass().isActor())
|
||||
{
|
||||
runtime.push(0);
|
||||
return;
|
||||
}
|
||||
|
||||
char *end;
|
||||
long key = strtol(effect.c_str(), &end, 10);
|
||||
if(key < 0 || key > 32767 || *end != '\0')
|
||||
|
@ -745,6 +751,12 @@ namespace MWScript
|
|||
std::string id = runtime.getStringLiteral(runtime[0].mInteger);
|
||||
runtime.pop();
|
||||
|
||||
if (!ptr.getClass().isActor())
|
||||
{
|
||||
runtime.push(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
runtime.push(stats.getActiveSpells().isSpellActive(id) || stats.getSpells().isSpellActive(id));
|
||||
}
|
||||
|
|
|
@ -583,7 +583,7 @@ namespace MWScript
|
|||
|
||||
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;
|
||||
|
||||
runtime.push (value);
|
||||
|
|
|
@ -58,10 +58,8 @@ void MWState::Character::addSlot (const ESM::SavedGame& profile)
|
|||
int i=0;
|
||||
while (boost::filesystem::exists(slot.mPath))
|
||||
{
|
||||
std::ostringstream test;
|
||||
test << stream.str();
|
||||
test << " - " << ++i;
|
||||
slot.mPath = mPath / (test.str() + ext);
|
||||
const std::string test = stream.str() + " - " + std::to_string(++i);
|
||||
slot.mPath = mPath / (test + ext);
|
||||
}
|
||||
|
||||
slot.mProfile = profile;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "cellpreloader.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <limits>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
|
@ -251,7 +252,7 @@ namespace MWWorld
|
|||
{
|
||||
// throw out oldest cell to make room
|
||||
PreloadMap::iterator oldestCell = mPreloadCells.begin();
|
||||
double oldestTimestamp = DBL_MAX;
|
||||
double oldestTimestamp = std::numeric_limits<double>::max();
|
||||
double threshold = 1.0; // seconds
|
||||
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it)
|
||||
{
|
||||
|
|
|
@ -140,8 +140,6 @@ void ESMStore::setUp(bool validateRecords)
|
|||
mMagicEffects.setUp();
|
||||
mAttributes.setUp();
|
||||
mDialogs.setUp();
|
||||
mStatics.setUp();
|
||||
mDoors.setUp();
|
||||
|
||||
if (validateRecords)
|
||||
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)
|
||||
template <class T>
|
||||
const T *insert(const T &x) {
|
||||
std::ostringstream id;
|
||||
id << "$dynamic" << mDynamicCount++;
|
||||
const T *insert(const T &x)
|
||||
{
|
||||
const std::string id = "$dynamic" + std::to_string(mDynamicCount++);
|
||||
|
||||
Store<T> &store = const_cast<Store<T> &>(get<T>());
|
||||
if (store.search(id.str()) != 0) {
|
||||
std::ostringstream msg;
|
||||
msg << "Try to override existing record '" << id.str() << "'";
|
||||
throw std::runtime_error(msg.str());
|
||||
if (store.search(id) != 0)
|
||||
{
|
||||
const std::string msg = "Try to override existing record '" + id + "'";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
T record = x;
|
||||
|
||||
record.mId = id.str();
|
||||
record.mId = id;
|
||||
|
||||
T *ptr = store.insert(record);
|
||||
for (iterator it = mStores.begin(); it != mStores.end(); ++it) {
|
||||
|
@ -208,15 +208,15 @@ namespace MWWorld
|
|||
}
|
||||
|
||||
template <class T>
|
||||
const T *insertStatic(const T &x) {
|
||||
std::ostringstream id;
|
||||
id << "$dynamic" << mDynamicCount++;
|
||||
const T *insertStatic(const T &x)
|
||||
{
|
||||
const std::string id = "$dynamic" + std::to_string(mDynamicCount++);
|
||||
|
||||
Store<T> &store = const_cast<Store<T> &>(get<T>());
|
||||
if (store.search(id.str()) != 0) {
|
||||
std::ostringstream msg;
|
||||
msg << "Try to override existing record '" << id.str() << "'";
|
||||
throw std::runtime_error(msg.str());
|
||||
if (store.search(id) != 0)
|
||||
{
|
||||
const std::string msg = "Try to override existing record '" + id + "'";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
T record = x;
|
||||
|
||||
|
@ -247,20 +247,22 @@ namespace MWWorld
|
|||
}
|
||||
|
||||
template <>
|
||||
inline const ESM::NPC *ESMStore::insert<ESM::NPC>(const ESM::NPC &npc) {
|
||||
std::ostringstream id;
|
||||
id << "$dynamic" << mDynamicCount++;
|
||||
inline const ESM::NPC *ESMStore::insert<ESM::NPC>(const ESM::NPC &npc)
|
||||
{
|
||||
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);
|
||||
} else if (mNpcs.search(id.str()) != 0) {
|
||||
std::ostringstream msg;
|
||||
msg << "Try to override existing record '" << id.str() << "'";
|
||||
throw std::runtime_error(msg.str());
|
||||
}
|
||||
else if (mNpcs.search(id) != 0)
|
||||
{
|
||||
const std::string msg = "Try to override existing record '" + id + "'";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
ESM::NPC record = npc;
|
||||
|
||||
record.mId = id.str();
|
||||
record.mId = id;
|
||||
|
||||
ESM::NPC *ptr = mNpcs.insert(record);
|
||||
mIds[ptr->mId] = ESM::REC_NPC_;
|
||||
|
|
|
@ -449,14 +449,6 @@ namespace MWWorld
|
|||
const ESM::BirthSign* sign = world.getStore().get<ESM::BirthSign>().search (player.mBirthsign);
|
||||
if (!sign)
|
||||
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;
|
||||
|
|
|
@ -109,11 +109,10 @@ namespace
|
|||
|
||||
if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects
|
||||
{
|
||||
std::ostringstream ID;
|
||||
ID << "VFX_Multiple" << effects->mList.size();
|
||||
const std::string ID = "VFX_Multiple" + std::to_string(effects->mList.size());
|
||||
std::vector<std::string>::iterator it;
|
||||
it = projectileIDs.begin();
|
||||
it = projectileIDs.insert(it, ID.str());
|
||||
it = projectileIDs.insert(it, ID);
|
||||
}
|
||||
return projectileEffects;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include <components/misc/rng.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -102,10 +101,10 @@ namespace MWWorld
|
|||
const T *IndexedStore<T>::find(int index) const
|
||||
{
|
||||
const T *ptr = search(index);
|
||||
if (ptr == 0) {
|
||||
std::ostringstream msg;
|
||||
msg << T::getRecordType() << " with index " << index << " not found";
|
||||
throw std::runtime_error(msg.str());
|
||||
if (ptr == 0)
|
||||
{
|
||||
const std::string msg = T::getRecordType() + " with index " + std::to_string(index) + " not found";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
@ -171,10 +170,10 @@ namespace MWWorld
|
|||
const T *Store<T>::find(const std::string &id) const
|
||||
{
|
||||
const T *ptr = search(id);
|
||||
if (ptr == 0) {
|
||||
std::ostringstream msg;
|
||||
msg << T::getRecordType() << " '" << id << "' not found";
|
||||
throw std::runtime_error(msg.str());
|
||||
if (ptr == 0)
|
||||
{
|
||||
const std::string msg = T::getRecordType() + " '" + id + "' not found";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
@ -184,14 +183,13 @@ namespace MWWorld
|
|||
const T *ptr = searchRandom(id);
|
||||
if(ptr == 0)
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << T::getRecordType() << " starting with '"<<id<<"' not found";
|
||||
throw std::runtime_error(msg.str());
|
||||
const std::string msg = T::getRecordType() + " starting with '" + id + "' not found";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
template<typename T>
|
||||
RecordId Store<T>::load(ESM::ESMReader &esm)
|
||||
RecordId Store<T>::load(ESM::ESMReader &esm)
|
||||
{
|
||||
T record;
|
||||
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 *ptr = search(index, plugin);
|
||||
if (ptr == 0) {
|
||||
std::ostringstream msg;
|
||||
msg << "Land texture with index " << index << " not found";
|
||||
throw std::runtime_error(msg.str());
|
||||
if (ptr == 0)
|
||||
{
|
||||
const std::string msg = "Land texture with index " + std::to_string(index) + " not found";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
@ -456,10 +454,10 @@ namespace MWWorld
|
|||
const ESM::Land *Store<ESM::Land>::find(int x, int y) const
|
||||
{
|
||||
const ESM::Land *ptr = search(x, y);
|
||||
if (ptr == 0) {
|
||||
std::ostringstream msg;
|
||||
msg << "Land at (" << x << ", " << y << ") not found";
|
||||
throw std::runtime_error(msg.str());
|
||||
if (ptr == 0)
|
||||
{
|
||||
const std::string msg = "Land at (" + std::to_string(x) + ", " + std::to_string(y) + ") not found";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
@ -591,20 +589,20 @@ namespace MWWorld
|
|||
const ESM::Cell *Store<ESM::Cell>::find(const std::string &id) const
|
||||
{
|
||||
const ESM::Cell *ptr = search(id);
|
||||
if (ptr == 0) {
|
||||
std::ostringstream msg;
|
||||
msg << "Cell '" << id << "' not found";
|
||||
throw std::runtime_error(msg.str());
|
||||
if (ptr == 0)
|
||||
{
|
||||
const std::string msg = "Cell '" + id + "' not found";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
const ESM::Cell *Store<ESM::Cell>::find(int x, int y) const
|
||||
{
|
||||
const ESM::Cell *ptr = search(x, y);
|
||||
if (ptr == 0) {
|
||||
std::ostringstream msg;
|
||||
msg << "Exterior at (" << x << ", " << y << ") not found";
|
||||
throw std::runtime_error(msg.str());
|
||||
if (ptr == 0)
|
||||
{
|
||||
const std::string msg = "Exterior at (" + std::to_string(x) + ", " + std::to_string(y) + ") not found";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
@ -784,13 +782,10 @@ namespace MWWorld
|
|||
}
|
||||
ESM::Cell *Store<ESM::Cell>::insert(const ESM::Cell &cell)
|
||||
{
|
||||
if (search(cell) != 0) {
|
||||
std::ostringstream msg;
|
||||
msg << "Failed to create ";
|
||||
msg << ((cell.isExterior()) ? "exterior" : "interior");
|
||||
msg << " cell";
|
||||
|
||||
throw std::runtime_error(msg.str());
|
||||
if (search(cell) != 0)
|
||||
{
|
||||
const std::string cellType = (cell.isExterior()) ? "exterior" : "interior";
|
||||
throw std::runtime_error("Failed to create " + cellType + " cell");
|
||||
}
|
||||
ESM::Cell *ptr;
|
||||
if (cell.isExterior()) {
|
||||
|
@ -931,9 +926,8 @@ namespace MWWorld
|
|||
const ESM::Pathgrid* pathgrid = search(x,y);
|
||||
if (!pathgrid)
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "Pathgrid in cell '" << x << " " << y << "' not found";
|
||||
throw std::runtime_error(msg.str());
|
||||
const std::string msg = "Pathgrid in cell '" + std::to_string(x) + " " + std::to_string(y) + "' not found";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
return pathgrid;
|
||||
}
|
||||
|
@ -942,9 +936,8 @@ namespace MWWorld
|
|||
const ESM::Pathgrid* pathgrid = search(name);
|
||||
if (!pathgrid)
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "Pathgrid in cell '" << name << "' not found";
|
||||
throw std::runtime_error(msg.str());
|
||||
const std::string msg = "Pathgrid in cell '" + name + "' not found";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
return pathgrid;
|
||||
}
|
||||
|
@ -998,10 +991,10 @@ namespace MWWorld
|
|||
const ESM::Attribute *Store<ESM::Attribute>::find(size_t index) const
|
||||
{
|
||||
const ESM::Attribute *ptr = search(index);
|
||||
if (ptr == 0) {
|
||||
std::ostringstream msg;
|
||||
msg << "Attribute with index " << index << " not found";
|
||||
throw std::runtime_error(msg.str());
|
||||
if (ptr == 0)
|
||||
{
|
||||
const std::string msg = "Attribute with index " + std::to_string(index) + " not found";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
@ -1053,58 +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;
|
||||
newMarker.mId = marker.first;
|
||||
newMarker.mModel = 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;
|
||||
newMarker.mId = marker.first;
|
||||
newMarker.mModel = 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 <>
|
||||
inline RecordId Store<ESM::Dialogue>::load(ESM::ESMReader &esm) {
|
||||
// The original letter case of a dialogue ID is saved, because it's printed
|
||||
|
|
|
@ -417,10 +417,8 @@ namespace MWWorld
|
|||
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
|
||||
for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin());
|
||||
iter!=mWorldScene->getActiveCells().end(); ++iter)
|
||||
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
||||
{
|
||||
CellStore* cellstore = *iter;
|
||||
MWBase::Environment::get().getWindowManager()->writeFog(cellstore);
|
||||
}
|
||||
|
||||
|
@ -524,6 +522,17 @@ namespace MWWorld
|
|||
gmst["iWereWolfBounty"] = ESM::Variant(1000);
|
||||
gmst["fCombatDistanceWerewolfMod"] = ESM::Variant(0.3f);
|
||||
|
||||
for (const std::pair<std::string, ESM::Variant> ¶ms : 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;
|
||||
// vanilla Morrowind does not define dayspassed.
|
||||
globals["dayspassed"] = ESM::Variant(1); // but the addons start counting at 1 :(
|
||||
|
@ -543,25 +552,47 @@ namespace MWWorld
|
|||
globals["crimegoldturnin"] = 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> ¶ms : globals)
|
||||
{
|
||||
if (!mStore.get<ESM::GameSetting>().search(it->first))
|
||||
if (!mStore.get<ESM::Global>().search(params.first))
|
||||
{
|
||||
ESM::GameSetting setting;
|
||||
setting.mId = it->first;
|
||||
setting.mValue = it->second;
|
||||
mStore.insertStatic(setting);
|
||||
ESM::Global record;
|
||||
record.mId = params.first;
|
||||
record.mValue = params.second;
|
||||
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> ¶ms : statics)
|
||||
{
|
||||
if (!mStore.get<ESM::Global>().search(it->first))
|
||||
if (!mStore.get<ESM::Static>().search(params.first))
|
||||
{
|
||||
ESM::Global setting;
|
||||
setting.mId = it->first;
|
||||
setting.mValue = it->second;
|
||||
mStore.insertStatic(setting);
|
||||
ESM::Static record;
|
||||
record.mId = params.first;
|
||||
record.mModel = params.second;
|
||||
mStore.insertStatic(record);
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> doors;
|
||||
doors["prisonmarker"] = "marker_prison.nif";
|
||||
|
||||
for (const std::pair<std::string, std::string> ¶ms : doors)
|
||||
{
|
||||
if (!mStore.get<ESM::Door>().search(params.first))
|
||||
{
|
||||
ESM::Door record;
|
||||
record.mId = params.first;
|
||||
record.mModel = params.second;
|
||||
mStore.insertStatic(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -576,22 +607,19 @@ namespace MWWorld
|
|||
{
|
||||
// first try named cells
|
||||
const ESM::Cell *cell = mStore.get<ESM::Cell>().searchExtByName (cellName);
|
||||
if (cell != 0) {
|
||||
if (cell)
|
||||
return cell;
|
||||
}
|
||||
|
||||
// didn't work -> now check for regions
|
||||
const MWWorld::Store<ESM::Region> ®ions = mStore.get<ESM::Region>();
|
||||
MWWorld::Store<ESM::Region>::iterator it = regions.begin();
|
||||
for (; it != regions.end(); ++it)
|
||||
for (const ESM::Region ®ion : mStore.get<ESM::Region>())
|
||||
{
|
||||
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
|
||||
|
@ -713,10 +741,10 @@ namespace MWWorld
|
|||
if (!cell->getCell()->isExterior() || !cell->getCell()->mName.empty())
|
||||
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 getStore().get<ESM::GameSetting>().find ("sDefaultCellname")->mValue.getString();
|
||||
return mStore.get<ESM::GameSetting>().find ("sDefaultCellname")->mValue.getString();
|
||||
}
|
||||
|
||||
void World::removeRefScript (MWWorld::RefData *ref)
|
||||
|
@ -735,11 +763,9 @@ namespace MWWorld
|
|||
|
||||
std::string lowerCaseName = Misc::StringUtils::lowerCase(name);
|
||||
|
||||
for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin());
|
||||
iter!=mWorldScene->getActiveCells().end(); ++iter)
|
||||
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
||||
{
|
||||
// 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);
|
||||
|
||||
if (!ptr.isEmpty())
|
||||
|
@ -753,10 +779,8 @@ namespace MWWorld
|
|||
return ret;
|
||||
}
|
||||
|
||||
for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin());
|
||||
iter!=mWorldScene->getActiveCells().end(); ++iter)
|
||||
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
||||
{
|
||||
CellStore* cellstore = *iter;
|
||||
Ptr ptr = cellstore->searchInContainer(lowerCaseName);
|
||||
if (!ptr.isEmpty())
|
||||
return ptr;
|
||||
|
@ -884,15 +908,14 @@ namespace MWWorld
|
|||
if (ptr.getContainerStore() == &player.getClass().getContainerStore(player))
|
||||
return player;
|
||||
|
||||
const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells();
|
||||
for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt)
|
||||
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
||||
{
|
||||
FindContainerVisitor visitor(ptr);
|
||||
(*cellIt)->forEachType<ESM::Container>(visitor);
|
||||
cellstore->forEachType<ESM::Container>(visitor);
|
||||
if (visitor.mResult.isEmpty())
|
||||
(*cellIt)->forEachType<ESM::Creature>(visitor);
|
||||
cellstore->forEachType<ESM::Creature>(visitor);
|
||||
if (visitor.mResult.isEmpty())
|
||||
(*cellIt)->forEachType<ESM::NPC>(visitor);
|
||||
cellstore->forEachType<ESM::NPC>(visitor);
|
||||
|
||||
if (!visitor.mResult.isEmpty())
|
||||
return visitor.mResult;
|
||||
|
@ -1135,7 +1158,7 @@ namespace MWWorld
|
|||
"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
|
||||
|
@ -1224,7 +1247,7 @@ namespace MWWorld
|
|||
if (mActivationDistanceOverride >= 0)
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -1492,7 +1515,10 @@ namespace MWWorld
|
|||
{
|
||||
mRendering->moveObject(newPtr, vec);
|
||||
if (movePhysics)
|
||||
{
|
||||
mPhysics->updatePosition(newPtr);
|
||||
mPhysics->updatePtr(ptr, newPtr);
|
||||
}
|
||||
}
|
||||
if (isPlayer)
|
||||
{
|
||||
|
@ -2019,7 +2045,6 @@ namespace MWWorld
|
|||
|
||||
if (!paused)
|
||||
{
|
||||
doPhysics (duration);
|
||||
updateNavigator();
|
||||
}
|
||||
|
||||
|
@ -2039,6 +2064,14 @@ namespace MWWorld
|
|||
}
|
||||
}
|
||||
|
||||
void World::updatePhysics (float duration, bool paused)
|
||||
{
|
||||
if (!paused)
|
||||
{
|
||||
doPhysics (duration);
|
||||
}
|
||||
}
|
||||
|
||||
void World::updatePlayer()
|
||||
{
|
||||
MWWorld::Ptr player = getPlayerPtr();
|
||||
|
@ -2071,7 +2104,7 @@ namespace MWWorld
|
|||
bool swimming = isSwimming(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)
|
||||
mRendering->getCamera()->setSneakOffset(i1stPersonSneakDelta);
|
||||
else
|
||||
|
@ -2697,9 +2730,8 @@ namespace MWWorld
|
|||
player.getClass().getInventoryStore(player).setInvListener(anim, player);
|
||||
player.getClass().getInventoryStore(player).setContListener(anim);
|
||||
|
||||
scaleObject(getPlayerPtr(), 1.f); // apply race height
|
||||
|
||||
rotateObject(getPlayerPtr(), 0.f, 0.f, 0.f, true);
|
||||
scaleObject(player, player.getCellRef().getScale()); // apply race height
|
||||
rotateObject(player, 0.f, 0.f, 0.f, true);
|
||||
|
||||
MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr());
|
||||
MWBase::Environment::get().getMechanicsManager()->watchActor(getPlayerPtr());
|
||||
|
@ -2893,17 +2925,16 @@ namespace MWWorld
|
|||
|
||||
std::vector<MWWorld::Ptr> 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);
|
||||
if (stats.isDead())
|
||||
continue;
|
||||
|
||||
mPhysics->markAsNonSolid (object);
|
||||
|
||||
if (actor == getPlayerPtr() && MWBase::Environment::get().getWorld()->getGodModeState())
|
||||
return;
|
||||
if (actor == getPlayerPtr() && mGodMode)
|
||||
continue;
|
||||
|
||||
MWMechanics::DynamicStat<float> health = stats.getHealth();
|
||||
health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration());
|
||||
|
@ -2934,19 +2965,18 @@ namespace MWWorld
|
|||
End of tes3mp change (major)
|
||||
*/
|
||||
|
||||
std::vector<MWWorld::Ptr> actors;
|
||||
std::vector<Ptr> 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);
|
||||
if (stats.isDead())
|
||||
continue;
|
||||
|
||||
mPhysics->markAsNonSolid (object);
|
||||
|
||||
if (actor == getPlayerPtr() && MWBase::Environment::get().getWorld()->getGodModeState())
|
||||
return;
|
||||
if (actor == getPlayerPtr() && mGodMode)
|
||||
continue;
|
||||
|
||||
MWMechanics::DynamicStat<float> health = stats.getHealth();
|
||||
health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration());
|
||||
|
@ -3016,11 +3046,10 @@ namespace MWWorld
|
|||
|
||||
void World::getContainersOwnedBy (const MWWorld::ConstPtr& owner, std::vector<MWWorld::Ptr>& out)
|
||||
{
|
||||
const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells();
|
||||
for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt)
|
||||
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
||||
{
|
||||
GetContainersOwnedByVisitor visitor (owner, out);
|
||||
(*cellIt)->forEachType<ESM::Container>(visitor);
|
||||
cellstore->forEachType<ESM::Container>(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3038,15 +3067,14 @@ namespace MWWorld
|
|||
|
||||
void World::getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector<MWWorld::Ptr>& out)
|
||||
{
|
||||
const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells();
|
||||
for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt)
|
||||
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
||||
{
|
||||
ListObjectsVisitor visitor;
|
||||
(*cellIt)->forEach(visitor);
|
||||
cellstore->forEach(visitor);
|
||||
|
||||
for (std::vector<MWWorld::Ptr>::iterator it = visitor.mObjects.begin(); it != visitor.mObjects.end(); ++it)
|
||||
if (Misc::StringUtils::ciEqual(it->getCellRef().getOwner(), npc.getCellRef().getRefId()))
|
||||
out.push_back(*it);
|
||||
for (const Ptr &object : visitor.mObjects)
|
||||
if (Misc::StringUtils::ciEqual(object.getCellRef().getOwner(), npc.getCellRef().getRefId()))
|
||||
out.push_back(object);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3087,28 +3115,21 @@ namespace MWWorld
|
|||
|
||||
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.pos[0] = pos.pos[1] = pos.pos[2] = 0;
|
||||
|
||||
MWWorld::CellStore *cellStore = getInterior(name);
|
||||
|
||||
if (0 == cellStore) {
|
||||
if (!cellStore)
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<const MWWorld::CellRef *> sortedDoors;
|
||||
const DoorList &doors = cellStore->getReadOnlyDoors().mList;
|
||||
for (DoorList::const_iterator it = doors.begin(); it != doors.end(); ++it)
|
||||
for (const MWWorld::LiveCellRef<ESM::Door>& door : cellStore->getReadOnlyDoors().mList)
|
||||
{
|
||||
if (!it->mRef.getTeleport())
|
||||
{
|
||||
if (!door.mRef.getTeleport())
|
||||
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
|
||||
|
@ -3120,44 +3141,43 @@ namespace MWWorld
|
|||
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
|
||||
if ((*it)->getDestCell().empty())
|
||||
if (door->getDestCell().empty())
|
||||
{
|
||||
int x, y;
|
||||
ESM::Position doorDest = (*it)->getDoorDest();
|
||||
ESM::Position doorDest = door->getDoorDest();
|
||||
positionToIndex(doorDest.pos[0], doorDest.pos[1], x, y);
|
||||
source = getExterior(x, y);
|
||||
}
|
||||
// door to interior
|
||||
else
|
||||
{
|
||||
source = getInterior((*it)->getDestCell());
|
||||
source = getInterior(door->getDestCell());
|
||||
}
|
||||
if (0 != source)
|
||||
if (source)
|
||||
{
|
||||
// Find door leading to our current teleport door
|
||||
// and use its destination to position inside cell.
|
||||
const DoorList &destinationDoors = source->getReadOnlyDoors().mList;
|
||||
for (DoorList::const_iterator jt = destinationDoors.begin(); jt != destinationDoors.end(); ++jt)
|
||||
for (const MWWorld::LiveCellRef<ESM::Door>& destDoor : source->getReadOnlyDoors().mList)
|
||||
{
|
||||
if ((*it)->getTeleport() &&
|
||||
Misc::StringUtils::ciEqual(name, jt->mRef.getDestCell()))
|
||||
if (Misc::StringUtils::ciEqual(name, destDoor.mRef.getDestCell()))
|
||||
{
|
||||
/// \note Using _any_ door pointed to the interior,
|
||||
/// not the one pointed to current door.
|
||||
pos = jt->mRef.getDoorDest();
|
||||
pos = destDoor.mRef.getDoorDest();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fall back to the first static location.
|
||||
const StaticList &statics = cellStore->getReadOnlyStatics().mList;
|
||||
if ( statics.begin() != statics.end() ) {
|
||||
const MWWorld::CellRefList<ESM::Static>::List &statics = cellStore->getReadOnlyStatics().mList;
|
||||
if (!statics.empty())
|
||||
{
|
||||
pos = statics.begin()->mRef.getPosition();
|
||||
return true;
|
||||
}
|
||||
|
@ -3252,22 +3272,21 @@ namespace MWWorld
|
|||
void World::loadContentFiles(const Files::Collections& fileCollections,
|
||||
const std::vector<std::string>& content, ContentLoader& contentLoader)
|
||||
{
|
||||
std::vector<std::string>::const_iterator it(content.begin());
|
||||
std::vector<std::string>::const_iterator end(content.end());
|
||||
for (int idx = 0; it != end; ++it, ++idx)
|
||||
int idx = 0;
|
||||
for (const std::string &file : content)
|
||||
{
|
||||
boost::filesystem::path filename(*it);
|
||||
boost::filesystem::path filename(file);
|
||||
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
|
||||
{
|
||||
std::stringstream msg;
|
||||
msg << "Failed loading " << *it << ": the content file does not exist";
|
||||
throw std::runtime_error(msg.str());
|
||||
std::string message = "Failed loading " + file + ": the content file does not exist";
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3307,10 +3326,10 @@ namespace MWWorld
|
|||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
const ESM::Spell* spell = getStore().get<ESM::Spell>().find(selectedSpell);
|
||||
const ESM::Spell* spell = mStore.get<ESM::Spell>().find(selectedSpell);
|
||||
|
||||
// Check mana
|
||||
bool godmode = (isPlayer && getGodModeState());
|
||||
bool godmode = (isPlayer && mGodMode);
|
||||
MWMechanics::DynamicStat<float> magicka = stats.getMagicka();
|
||||
if (magicka.getCurrent() < spell->mData.mCost && !godmode)
|
||||
{
|
||||
|
@ -3348,7 +3367,7 @@ namespace MWWorld
|
|||
if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell)
|
||||
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();
|
||||
|
||||
|
@ -3366,14 +3385,13 @@ namespace MWWorld
|
|||
// For scripted spells we should not use hit contact
|
||||
if (manualSpell)
|
||||
{
|
||||
// Actors that are targeted by this actor's Follow or Escort packages also side with them
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -3431,7 +3449,7 @@ namespace MWWorld
|
|||
|
||||
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);
|
||||
}
|
||||
else if (actor.getClass().hasInventoryStore(actor))
|
||||
|
@ -3566,17 +3584,14 @@ namespace MWWorld
|
|||
while ( !nextCells.empty() ) {
|
||||
currentCells = nextCells;
|
||||
nextCells.clear();
|
||||
for( std::set< std::string >::const_iterator i = currentCells.begin(); i != currentCells.end(); ++i ) {
|
||||
MWWorld::CellStore *next = getInterior( *i );
|
||||
for (const std::string ¤tCell : currentCells)
|
||||
{
|
||||
MWWorld::CellStore *next = getInterior(currentCell);
|
||||
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
|
||||
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.getDestCell().empty())
|
||||
|
@ -3593,7 +3608,7 @@ namespace MWWorld
|
|||
}
|
||||
}
|
||||
|
||||
checkedCells.insert( *i );
|
||||
checkedCells.insert(currentCell);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3619,9 +3634,9 @@ namespace MWWorld
|
|||
while ( !nextCells.empty() ) {
|
||||
currentCells = nextCells;
|
||||
nextCells.clear();
|
||||
for( std::set< std::string >::const_iterator i = currentCells.begin(); i != currentCells.end(); ++i ) {
|
||||
MWWorld::CellStore *next = getInterior( *i );
|
||||
checkedCells.insert( *i );
|
||||
for (const std::string &cell : currentCells) {
|
||||
MWWorld::CellStore *next = getInterior(cell);
|
||||
checkedCells.insert(cell);
|
||||
if ( !next ) continue;
|
||||
|
||||
closestMarker = next->searchConst( id );
|
||||
|
@ -3630,14 +3645,9 @@ namespace MWWorld
|
|||
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
|
||||
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.getDestCell().empty())
|
||||
|
@ -3663,15 +3673,14 @@ namespace MWWorld
|
|||
|
||||
std::vector<MWWorld::Ptr> 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 = pos.asVec3();
|
||||
osg::Vec3f markerPos = marker.getRefData().getPosition().asVec3();
|
||||
float distance = (worldPos - markerPos).length2();
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestMarker = *it2;
|
||||
closestMarker = marker;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3802,10 +3811,8 @@ namespace MWWorld
|
|||
|
||||
AddDetectedReferenceVisitor visitor (out, ptr, type, dist*dist);
|
||||
|
||||
const Scene::CellStoreCollection& active = mWorldScene->getActiveCells();
|
||||
for (Scene::CellStoreCollection::const_iterator it = active.begin(); it != active.end(); ++it)
|
||||
for (CellStore* cellStore : mWorldScene->getActiveCells())
|
||||
{
|
||||
MWWorld::CellStore* cellStore = *it;
|
||||
cellStore->forEach(visitor);
|
||||
}
|
||||
}
|
||||
|
@ -3840,8 +3847,8 @@ namespace MWWorld
|
|||
int bounty = player.getClass().getNpcStats(player).getBounty();
|
||||
int playerGold = player.getClass().getContainerStore(player).count(ContainerStore::sGoldId);
|
||||
|
||||
float fCrimeGoldDiscountMult = getStore().get<ESM::GameSetting>().find("fCrimeGoldDiscountMult")->mValue.getFloat();
|
||||
float fCrimeGoldTurnInMult = getStore().get<ESM::GameSetting>().find("fCrimeGoldTurnInMult")->mValue.getFloat();
|
||||
static float fCrimeGoldDiscountMult = mStore.get<ESM::GameSetting>().find("fCrimeGoldDiscountMult")->mValue.getFloat();
|
||||
static float fCrimeGoldTurnInMult = mStore.get<ESM::GameSetting>().find("fCrimeGoldTurnInMult")->mValue.getFloat();
|
||||
|
||||
int discount = static_cast<int>(bounty * fCrimeGoldDiscountMult);
|
||||
int turnIn = static_cast<int>(bounty * fCrimeGoldTurnInMult);
|
||||
|
@ -3906,7 +3913,7 @@ namespace MWWorld
|
|||
mPlayer->recordCrimeId();
|
||||
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);
|
||||
|
||||
return;
|
||||
|
@ -3962,18 +3969,18 @@ namespace MWWorld
|
|||
|
||||
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]
|
||||
|
||||
for (int i=0; i<numCreatures; ++i)
|
||||
{
|
||||
std::string selectedCreature = MWMechanics::getLevelledItem(list, true);
|
||||
if (selectedCreature.empty())
|
||||
return;
|
||||
continue;
|
||||
|
||||
MWWorld::ManualRef ref(getStore(), selectedCreature, 1);
|
||||
MWWorld::ManualRef ref(mStore, selectedCreature, 1);
|
||||
|
||||
/*
|
||||
Start of tes3mp change (major)
|
||||
|
@ -4001,27 +4008,8 @@ namespace MWWorld
|
|||
if (ptr == getPlayerPtr() && Settings::Manager::getBool("hit fader", "GUI"))
|
||||
return;
|
||||
|
||||
int type = ptr.getClass().getBloodTexture(ptr);
|
||||
std::string texture;
|
||||
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());
|
||||
std::string texture = getFallback()->getFallbackString("Blood_Texture_" + std::to_string(ptr.getClass().getBloodTexture(ptr)));
|
||||
std::string model = "meshes\\" + getFallback()->getFallbackString("Blood_Model_" + std::to_string(Misc::Rng::rollDice(3))); // [0, 2]
|
||||
|
||||
mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false);
|
||||
}
|
||||
|
@ -4038,7 +4026,7 @@ namespace MWWorld
|
|||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = effects.mList.begin();
|
||||
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()))
|
||||
continue; // Not right range type, or not area effect and hit an actor
|
||||
|
@ -4052,9 +4040,9 @@ namespace MWWorld
|
|||
// Spawn the explosion orb effect
|
||||
const ESM::Static* areaStatic;
|
||||
if (!effect->mArea.empty())
|
||||
areaStatic = getStore().get<ESM::Static>().find (effect->mArea);
|
||||
areaStatic = mStore.get<ESM::Static>().find (effect->mArea);
|
||||
else
|
||||
areaStatic = getStore().get<ESM::Static>().find ("VFX_DefaultArea");
|
||||
areaStatic = mStore.get<ESM::Static>().find ("VFX_DefaultArea");
|
||||
|
||||
std::string texture = effect->mParticle;
|
||||
|
||||
|
@ -4082,13 +4070,13 @@ namespace MWWorld
|
|||
std::vector<MWWorld::Ptr> objects;
|
||||
MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(
|
||||
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.
|
||||
if (affected->getClass().isActor() && !isActorCollisionEnabled(*affected))
|
||||
if (affected.getClass().isActor() && !isActorCollisionEnabled(affected))
|
||||
continue;
|
||||
|
||||
toApply[*affected].push_back(*effectIt);
|
||||
toApply[affected].push_back(*effectIt);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4148,10 +4136,8 @@ namespace MWWorld
|
|||
};
|
||||
void World::resetActors()
|
||||
{
|
||||
for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin());
|
||||
iter!=mWorldScene->getActiveCells().end(); ++iter)
|
||||
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
||||
{
|
||||
CellStore* cellstore = *iter;
|
||||
ResetActorsVisitor visitor;
|
||||
cellstore->forEach(visitor);
|
||||
}
|
||||
|
|
|
@ -619,6 +619,7 @@ namespace MWWorld
|
|||
/// \return pointer to created record
|
||||
|
||||
void update (float duration, bool paused) override;
|
||||
void updatePhysics (float duration, bool paused) override;
|
||||
|
||||
void updateWindowManager () override;
|
||||
|
||||
|
|
|
@ -40,4 +40,13 @@ if (GTEST_FOUND AND GMOCK_FOUND)
|
|||
add_definitions(--coverage)
|
||||
target_link_libraries(openmw_test_suite gcov)
|
||||
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()
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
#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) {
|
||||
#endif
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
|
|
@ -856,7 +856,7 @@ QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QSt
|
|||
if (info.isDir()) {
|
||||
if (directories)
|
||||
{
|
||||
if (info.fileName() == fileName) {
|
||||
if (!info.fileName().compare(fileName, Qt::CaseInsensitive)) {
|
||||
result.append(info.absoluteFilePath());
|
||||
} else {
|
||||
if (recursive)
|
||||
|
@ -872,11 +872,11 @@ QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QSt
|
|||
|
||||
switch (flags) {
|
||||
case Qt::MatchExactly:
|
||||
if (info.fileName() == fileName)
|
||||
if (!info.fileName().compare(fileName, Qt::CaseInsensitive))
|
||||
result.append(info.absoluteFilePath());
|
||||
break;
|
||||
case Qt::MatchEndsWith:
|
||||
if (info.fileName().endsWith(fileName))
|
||||
if (info.fileName().endsWith(fileName), Qt::CaseInsensitive)
|
||||
result.append(info.absoluteFilePath());
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ add_component_dir (settings
|
|||
)
|
||||
|
||||
add_component_dir (bsa
|
||||
bsa_file
|
||||
bsa_file compressedbsafile memorystream
|
||||
)
|
||||
|
||||
add_component_dir (vfs
|
||||
|
@ -285,6 +285,7 @@ target_link_libraries(components
|
|||
${Boost_FILESYSTEM_LIBRARY}
|
||||
${Boost_THREAD_LIBRARY}
|
||||
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
||||
${Boost_IOSTREAMS_LIBRARY}
|
||||
${OSG_LIBRARIES}
|
||||
${OPENTHREADS_LIBRARIES}
|
||||
${OSGPARTICLE_LIBRARIES}
|
||||
|
@ -298,6 +299,7 @@ target_link_libraries(components
|
|||
${SDL2_LIBRARIES}
|
||||
${OPENGL_gl_LIBRARY}
|
||||
${MyGUI_LIBRARIES}
|
||||
${BSAOPTHASH_LIBRARIES}
|
||||
RecastNavigation::DebugUtils
|
||||
RecastNavigation::Detour
|
||||
RecastNavigation::Recast
|
||||
|
@ -305,7 +307,8 @@ target_link_libraries(components
|
|||
|
||||
if (WIN32)
|
||||
target_link_libraries(components
|
||||
${Boost_LOCALE_LIBRARY})
|
||||
${Boost_LOCALE_LIBRARY}
|
||||
${Boost_ZLIB_LIBRARY})
|
||||
endif()
|
||||
|
||||
if (USE_QT)
|
||||
|
|
|
@ -35,7 +35,7 @@ using namespace Bsa;
|
|||
/// Error handling
|
||||
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
|
||||
|
@ -71,10 +71,10 @@ void BSAFile::readHeader()
|
|||
* the beginning of this buffer.
|
||||
*
|
||||
*/
|
||||
assert(!isLoaded);
|
||||
assert(!mIsLoaded);
|
||||
|
||||
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
|
||||
std::streamoff fsize = 0;
|
||||
|
@ -117,8 +117,8 @@ void BSAFile::readHeader()
|
|||
input.read(reinterpret_cast<char*>(&offsets[0]), 12*filenum);
|
||||
|
||||
// Read the string table
|
||||
stringBuf.resize(dirsize-12*filenum);
|
||||
input.read(&stringBuf[0], stringBuf.size());
|
||||
mStringBuf.resize(dirsize-12*filenum);
|
||||
input.read(&mStringBuf[0], mStringBuf.size());
|
||||
|
||||
// Check our position
|
||||
assert(input.tellg() == std::streampos(12+dirsize));
|
||||
|
@ -129,40 +129,40 @@ void BSAFile::readHeader()
|
|||
size_t fileDataOffset = 12 + dirsize + 8*filenum;
|
||||
|
||||
// Set up the the FileStruct table
|
||||
files.resize(filenum);
|
||||
mFiles.resize(filenum);
|
||||
for(size_t i=0;i<filenum;i++)
|
||||
{
|
||||
FileStruct &fs = files[i];
|
||||
FileStruct &fs = mFiles[i];
|
||||
fs.fileSize = offsets[i*2];
|
||||
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)
|
||||
fail("Archive contains offsets outside itself");
|
||||
|
||||
// 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
|
||||
int BSAFile::getIndex(const char *str) const
|
||||
{
|
||||
Lookup::const_iterator it = lookup.find(str);
|
||||
if(it == lookup.end())
|
||||
Lookup::const_iterator it = mLookup.find(str);
|
||||
if(it == mLookup.end())
|
||||
return -1;
|
||||
|
||||
int res = it->second;
|
||||
assert(res >= 0 && (size_t)res < files.size());
|
||||
assert(res >= 0 && (size_t)res < mFiles.size());
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Open an archive file.
|
||||
void BSAFile::open(const string &file)
|
||||
{
|
||||
filename = file;
|
||||
mFilename = file;
|
||||
readHeader();
|
||||
}
|
||||
|
||||
|
@ -173,12 +173,12 @@ Files::IStreamPtr BSAFile::getFile(const char *file)
|
|||
if(i == -1)
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
private:
|
||||
protected:
|
||||
/// Table of files in this archive
|
||||
FileList files;
|
||||
FileList mFiles;
|
||||
|
||||
/// Filename string buffer
|
||||
std::vector<char> stringBuf;
|
||||
std::vector<char> mStringBuf;
|
||||
|
||||
/// True when an archive has been loaded
|
||||
bool isLoaded;
|
||||
bool mIsLoaded;
|
||||
|
||||
/// Used for error messages
|
||||
std::string filename;
|
||||
std::string mFilename;
|
||||
|
||||
/// Case insensitive string comparison
|
||||
struct iltstr
|
||||
|
@ -81,13 +81,16 @@ private:
|
|||
checks are case insensitive.
|
||||
*/
|
||||
typedef std::map<const char*, int, iltstr> Lookup;
|
||||
Lookup lookup;
|
||||
Lookup mLookup;
|
||||
|
||||
/// Error handling
|
||||
void fail(const std::string &msg);
|
||||
|
||||
/// 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
|
||||
/// @note Thread safe.
|
||||
|
@ -100,7 +103,10 @@ public:
|
|||
*/
|
||||
|
||||
BSAFile()
|
||||
: isLoaded(false)
|
||||
: mIsLoaded(false)
|
||||
{ }
|
||||
|
||||
virtual ~BSAFile()
|
||||
{ }
|
||||
|
||||
/// Open an archive file.
|
||||
|
@ -112,24 +118,24 @@ public:
|
|||
*/
|
||||
|
||||
/// Check if a file exists
|
||||
bool exists(const char *file) const
|
||||
virtual bool exists(const char *file) const
|
||||
{ return getIndex(file) != -1; }
|
||||
|
||||
/** Open a file contained in the archive. Throws an exception if the
|
||||
file doesn't exist.
|
||||
* @note Thread safe.
|
||||
*/
|
||||
Files::IStreamPtr getFile(const char *file);
|
||||
virtual Files::IStreamPtr getFile(const char *file);
|
||||
|
||||
/** Open a file contained in the archive.
|
||||
* @note Thread safe.
|
||||
*/
|
||||
Files::IStreamPtr getFile(const FileStruct* file);
|
||||
virtual Files::IStreamPtr getFile(const FileStruct* file);
|
||||
|
||||
/// Get a list of all files
|
||||
/// @note Thread safe.
|
||||
const FileList &getList() const
|
||||
{ return files; }
|
||||
{ return mFiles; }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
453
components/bsa/compressedbsafile.cpp
Normal file
453
components/bsa/compressedbsafile.cpp
Normal file
|
@ -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
|
99
components/bsa/compressedbsafile.hpp
Normal file
99
components/bsa/compressedbsafile.hpp
Normal file
|
@ -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
|
48
components/bsa/memorystream.cpp
Normal file
48
components/bsa/memorystream.cpp
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
62
components/bsa/memorystream.hpp
Normal file
62
components/bsa/memorystream.hpp
Normal file
|
@ -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)
|
||||
{
|
||||
// Ignore any junk special characters
|
||||
if (mState == BeginState)
|
||||
{
|
||||
if (code != Scanner::S_newline)
|
||||
reportWarning ("Ignoring stray special character before begin statement", loc);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (code==Scanner::S_newline)
|
||||
{
|
||||
if (mState==BeginState)
|
||||
{
|
||||
// ignore empty lines
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mState==BeginCompleteState)
|
||||
{
|
||||
// parse the script body
|
||||
|
|
|
@ -338,6 +338,9 @@ bool Config::GameSettings::writeFileWithComments(QFile &file)
|
|||
if (!comments.empty() && index != -1 && settingRegex.captureCount() >= 2 &&
|
||||
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)
|
||||
{
|
||||
*commentStart = *it;
|
||||
|
|
|
@ -474,9 +474,13 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path)
|
|||
|
||||
void ContentSelectorModel::ContentModel::clearFiles()
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), 0, mFiles.count()-1);
|
||||
mFiles.clear();
|
||||
endRemoveRows();
|
||||
const int filesCount = mFiles.count();
|
||||
|
||||
if (filesCount > 0) {
|
||||
beginRemoveRows(QModelIndex(), 0, filesCount - 1);
|
||||
mFiles.clear();
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
QStringList ContentSelectorModel::ContentModel::gameFiles() const
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
|
||||
namespace bfs = boost::filesystem;
|
||||
|
||||
|
@ -34,9 +35,13 @@ namespace bfs = boost::filesystem;
|
|||
|
||||
#if defined(__APPLE__)
|
||||
#include <sys/sysctl.h>
|
||||
#include <libproc.h>
|
||||
#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";
|
||||
|
||||
|
@ -413,6 +418,39 @@ static void crash_handler(const char *logfile)
|
|||
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*))
|
||||
{
|
||||
struct sigaction sa;
|
||||
|
@ -424,20 +462,7 @@ int crashCatcherInstallHandlers(int argc, char **argv, int num_signals, int *sig
|
|||
|
||||
cc_user_info = user_info;
|
||||
|
||||
if(argv[0][0] == '/')
|
||||
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]);
|
||||
}
|
||||
getExecPath(argv);
|
||||
|
||||
/* Set an alternate signal stack so SIGSEGVs caused by stack overflows
|
||||
* still run */
|
||||
|
@ -467,20 +492,24 @@ int crashCatcherInstallHandlers(int argc, char **argv, int num_signals, int *sig
|
|||
|
||||
static bool is_debugger_present()
|
||||
{
|
||||
#if !defined (__APPLE__)
|
||||
bfs::ifstream file((bfs::path("/proc/self/status")));
|
||||
while (!file.eof())
|
||||
#if defined (__linux__)
|
||||
bfs::path procstatus = bfs::path("/proc/self/status");
|
||||
if (bfs::exists(procstatus))
|
||||
{
|
||||
std::string word;
|
||||
file >> word;
|
||||
if (word == "TracerPid:")
|
||||
bfs::ifstream file((procstatus));
|
||||
while (!file.eof())
|
||||
{
|
||||
std::string word;
|
||||
file >> word;
|
||||
return word != "0";
|
||||
if (word == "TracerPid:")
|
||||
{
|
||||
file >> word;
|
||||
return word != "0";
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
#elif defined(__APPLE__)
|
||||
int junk;
|
||||
int mib[4];
|
||||
struct kinfo_proc info;
|
||||
|
@ -508,12 +537,24 @@ static bool is_debugger_present()
|
|||
// We're being debugged if the P_TRACED flag is set.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 };
|
||||
if (crashCatcherInstallHandlers(argc, argv, 5, s, crashLogPath.c_str(), nullptr) == -1)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "loadcell.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <list>
|
||||
|
||||
#include <boost/concept_check.hpp>
|
||||
|
@ -209,9 +208,7 @@ namespace ESM
|
|||
}
|
||||
else
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << mData.mX << ", " << mData.mY;
|
||||
return stream.str();
|
||||
return std::to_string(mData.mX) + ", " + std::to_string(mData.mY);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace ESM {
|
|||
|
||||
mScale = 1.f;
|
||||
mHasAI = false;
|
||||
mAiData.blank();
|
||||
|
||||
bool hasName = false;
|
||||
bool hasNpdt = false;
|
||||
|
@ -160,7 +161,6 @@ namespace ESM {
|
|||
mSpells.mList.clear();
|
||||
mHasAI = false;
|
||||
mAiData.blank();
|
||||
mAiData.mServices = 0;
|
||||
mAiPackage.mList.clear();
|
||||
mTransport.mList.clear();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "loadland.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
#include "esmreader.hpp"
|
||||
|
@ -200,9 +201,9 @@ namespace ESM
|
|||
mLandData->mTextures[i] = 0;
|
||||
for (int i = 0; i < LAND_NUM_VERTS; ++i)
|
||||
{
|
||||
mLandData->mColours[i*3+0] = -1;
|
||||
mLandData->mColours[i*3+1] = -1;
|
||||
mLandData->mColours[i*3+2] = -1;
|
||||
mLandData->mColours[i*3+0] = 255;
|
||||
mLandData->mColours[i*3+1] = 255;
|
||||
mLandData->mColours[i*3+2] = 255;
|
||||
}
|
||||
mLandData->mUnk1 = 0;
|
||||
mLandData->mUnk2 = 0;
|
||||
|
@ -252,8 +253,8 @@ namespace ESM
|
|||
if (reader.isNextSub("VHGT")) {
|
||||
VHGT vhgt;
|
||||
if (condLoad(reader, flags, target->mDataLoaded, DATA_VHGT, &vhgt, sizeof(vhgt))) {
|
||||
target->mMinHeight = FLT_MAX;
|
||||
target->mMaxHeight = -FLT_MAX;
|
||||
target->mMinHeight = std::numeric_limits<float>::max();
|
||||
target->mMaxHeight = -std::numeric_limits<float>::max();
|
||||
float rowOffset = vhgt.mHeightOffset;
|
||||
for (int y = 0; y < LAND_SIZE; y++) {
|
||||
rowOffset += vhgt.mHeightData[y * LAND_SIZE];
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace ESM
|
|||
mInventory.mList.clear();
|
||||
mTransport.mList.clear();
|
||||
mAiPackage.mList.clear();
|
||||
mAiData.blank();
|
||||
mHasAI = false;
|
||||
|
||||
bool hasName = false;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "interpreter.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "opcodes.hpp"
|
||||
|
@ -116,20 +115,14 @@ namespace Interpreter
|
|||
|
||||
void Interpreter::abortUnknownCode (int segment, int opcode)
|
||||
{
|
||||
std::ostringstream error;
|
||||
|
||||
error << "unknown opcode " << opcode << " in segment " << segment;
|
||||
|
||||
throw std::runtime_error (error.str());
|
||||
const std::string error = "unknown opcode " + std::to_string(opcode) + " in segment " + std::to_string(segment);
|
||||
throw std::runtime_error (error);
|
||||
}
|
||||
|
||||
void Interpreter::abortUnknownSegment (Type_Code code)
|
||||
{
|
||||
std::ostringstream error;
|
||||
|
||||
error << "opcode outside of the allocated segment range: " << code;
|
||||
|
||||
throw std::runtime_error (error.str());
|
||||
const std::string error = "opcode outside of the allocated segment range: " + std::to_string(code);
|
||||
throw std::runtime_error (error);
|
||||
}
|
||||
|
||||
void Interpreter::begin()
|
||||
|
|
|
@ -139,7 +139,7 @@ namespace Nif
|
|||
{
|
||||
Controller::read(nif);
|
||||
|
||||
nif->getUShort(); // always 0
|
||||
uvSet = nif->getUShort();
|
||||
data.read(nif);
|
||||
}
|
||||
|
||||
|
|
|
@ -112,6 +112,7 @@ class NiUVController : public Controller
|
|||
{
|
||||
public:
|
||||
NiUVDataPtr data;
|
||||
int uvSet;
|
||||
|
||||
void read(NIFStream *nif);
|
||||
void post(NIFFile *nif);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <osg/Geometry>
|
||||
#include <osg/Array>
|
||||
#include <osg/LOD>
|
||||
#include <osg/Switch>
|
||||
#include <osg/TexGen>
|
||||
#include <osg/ValueObject>
|
||||
|
||||
|
@ -324,6 +325,7 @@ namespace NifOsg
|
|||
osg::ref_ptr<osg::LOD> handleLodNode(const Nif::NiLODNode* niLodNode)
|
||||
{
|
||||
osg::ref_ptr<osg::LOD> lod (new osg::LOD);
|
||||
lod->setName(niLodNode->name);
|
||||
lod->setCenterMode(osg::LOD::USER_DEFINED_CENTER);
|
||||
lod->setCenter(niLodNode->lodCenter);
|
||||
for (unsigned int i=0; i<niLodNode->lodLevels.size(); ++i)
|
||||
|
@ -335,6 +337,14 @@ namespace NifOsg
|
|||
return lod;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Switch> handleSwitchNode(const Nif::NiSwitchNode* niSwitchNode)
|
||||
{
|
||||
osg::ref_ptr<osg::Switch> switchNode (new osg::Switch);
|
||||
switchNode->setName(niSwitchNode->name);
|
||||
switchNode->setNewChildDefaultValue(false);
|
||||
return switchNode;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Image> handleSourceTexture(const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager)
|
||||
{
|
||||
if (!st)
|
||||
|
@ -593,6 +603,23 @@ namespace NifOsg
|
|||
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);
|
||||
if(ninode)
|
||||
{
|
||||
|
@ -623,9 +650,14 @@ namespace NifOsg
|
|||
if (ctrl->recType == Nif::RC_NiUVController)
|
||||
{
|
||||
const Nif::NiUVController *niuvctrl = static_cast<const Nif::NiUVController*>(ctrl.getPtr());
|
||||
const int uvSet = niuvctrl->uvSet;
|
||||
std::set<int> texUnits;
|
||||
// UVController should work only for textures which use a given UV Set, usually 0.
|
||||
for (unsigned int i=0; i<boundTextures.size(); ++i)
|
||||
texUnits.insert(i);
|
||||
{
|
||||
if (boundTextures[i] == uvSet)
|
||||
texUnits.insert(i);
|
||||
}
|
||||
|
||||
osg::ref_ptr<UVController> uvctrl = new UVController(niuvctrl->data.getPtr(), texUnits);
|
||||
setupController(niuvctrl, uvctrl, animflags);
|
||||
|
@ -822,7 +854,9 @@ namespace NifOsg
|
|||
if (particle.vertex < int(particledata->colors.size()))
|
||||
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));
|
||||
box.expandBy(osg::BoundingSphere(position, size));
|
||||
|
@ -1092,13 +1126,12 @@ namespace NifOsg
|
|||
const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights;
|
||||
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.insert(indexWeight);
|
||||
influence.mWeights.emplace_back(weights[j].vertex, weights[j].weight);
|
||||
}
|
||||
influence.mInvBindMatrix = data->bones[i].trafo.toMatrix();
|
||||
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);
|
||||
|
||||
|
|
|
@ -737,6 +737,20 @@ bool Optimizer::CombineStaticTransformsVisitor::removeTransforms(osg::Node* node
|
|||
// RemoveEmptyNodes.
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Optimizer::RemoveEmptyNodesVisitor::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::RemoveEmptyNodesVisitor::apply(osg::LOD& lod)
|
||||
{
|
||||
// don't remove any direct children of the LOD because they are used to define each LOD level.
|
||||
for (unsigned int i=0; i<lod.getNumChildren(); ++i)
|
||||
traverse(*lod.getChild(i));
|
||||
}
|
||||
|
||||
void Optimizer::RemoveEmptyNodesVisitor::apply(osg::Group& group)
|
||||
{
|
||||
if (group.getNumParents()>0)
|
||||
|
@ -807,6 +821,13 @@ void Optimizer::RemoveRedundantNodesVisitor::apply(osg::LOD& lod)
|
|||
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)
|
||||
{
|
||||
if (typeid(group)==typeid(osg::Group) &&
|
||||
|
@ -1849,7 +1870,8 @@ bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawElementsUInt& lhs,
|
|||
|
||||
bool Optimizer::MergeGroupsVisitor::isOperationPermissible(osg::Group& node)
|
||||
{
|
||||
return !node.asTransform() &&
|
||||
return !node.asSwitch() &&
|
||||
!node.asTransform() &&
|
||||
!node.getCullCallback() &&
|
||||
!node.getEventCallback() &&
|
||||
!node.getUpdateCallback() &&
|
||||
|
@ -1862,6 +1884,12 @@ void Optimizer::MergeGroupsVisitor::apply(osg::LOD &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)
|
||||
{
|
||||
if (group.getNumChildren() <= 1)
|
||||
|
|
|
@ -321,6 +321,8 @@ class Optimizer
|
|||
BaseOptimizerVisitor(optimizer, REMOVE_REDUNDANT_NODES) {}
|
||||
|
||||
virtual void apply(osg::Group& group);
|
||||
virtual void apply(osg::LOD& lod);
|
||||
virtual void apply(osg::Switch& switchNode);
|
||||
|
||||
void removeEmptyNodes();
|
||||
|
||||
|
@ -340,6 +342,7 @@ class Optimizer
|
|||
virtual void apply(osg::Group& group);
|
||||
virtual void apply(osg::Transform& transform);
|
||||
virtual void apply(osg::LOD& lod);
|
||||
virtual void apply(osg::Switch& switchNode);
|
||||
|
||||
bool isOperationPermissible(osg::Node& node);
|
||||
|
||||
|
@ -360,6 +363,7 @@ class Optimizer
|
|||
|
||||
virtual void apply(osg::Group& group);
|
||||
virtual void apply(osg::LOD& lod);
|
||||
virtual void apply(osg::Switch& switchNode);
|
||||
};
|
||||
|
||||
class MergeGeometryVisitor : public BaseOptimizerVisitor
|
||||
|
|
|
@ -136,35 +136,35 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv)
|
|||
|
||||
typedef std::map<unsigned short, std::vector<BoneWeight> > 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)
|
||||
{
|
||||
Log(Debug::Error) << "Error: RigGeometry did not find bone " << it->first ;
|
||||
Log(Debug::Error) << "Error: RigGeometry did not find bone " << influencePair.first;
|
||||
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 (std::map<unsigned short, float>::const_iterator weightIt = weights.begin(); weightIt != weights.end(); ++weightIt)
|
||||
for (auto& weightPair: bi.mWeights)
|
||||
{
|
||||
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)
|
||||
{
|
||||
mBone2VertexMap[it->second].push_back(it->first);
|
||||
bone2VertexMap[it->second].push_back(it->first);
|
||||
}
|
||||
|
||||
mBone2VertexVector.assign(bone2VertexMap.begin(), bone2VertexMap.end());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -201,7 +201,7 @@ void RigGeometry::cull(osg::NodeVisitor* nv)
|
|||
osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(geom.getNormalArray());
|
||||
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,
|
||||
0, 0, 0, 0,
|
||||
|
@ -263,10 +263,10 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv)
|
|||
updateGeomToSkelMatrix(nv->getNodePath());
|
||||
|
||||
osg::BoundingBox box;
|
||||
for (BoneSphereMap::const_iterator it = mBoneSphereMap.begin(); it != mBoneSphereMap.end(); ++it)
|
||||
for (auto& boundPair : mBoneSphereVector)
|
||||
{
|
||||
Bone* bone = it->first;
|
||||
osg::BoundingSpheref bs = it->second;
|
||||
Bone* bone = boundPair.first;
|
||||
osg::BoundingSpheref bs = boundPair.second;
|
||||
if (mGeomToSkelMatrix)
|
||||
transformBoundingSphere(bone->mMatrixInSkeletonSpace * (*mGeomToSkelMatrix), bs);
|
||||
else
|
||||
|
|
|
@ -31,12 +31,12 @@ namespace SceneUtil
|
|||
osg::Matrixf mInvBindMatrix;
|
||||
osg::BoundingSpheref mBoundSphere;
|
||||
// <vertex index, weight>
|
||||
std::map<unsigned short, float> mWeights;
|
||||
std::vector<std::pair<unsigned short, float>> mWeights;
|
||||
};
|
||||
|
||||
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);
|
||||
|
@ -73,12 +73,13 @@ namespace SceneUtil
|
|||
typedef std::vector<unsigned short> VertexList;
|
||||
|
||||
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;
|
||||
bool mBoundsFirstFrame;
|
||||
|
|
|
@ -8,48 +8,6 @@
|
|||
#include <boost/filesystem/fstream.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
|
||||
{
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
|
@ -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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
setString(setting, category, toString(value));
|
||||
setString(setting, category, value ? "true" : "false");
|
||||
}
|
||||
|
||||
const CategorySettingVector Manager::apply()
|
||||
|
|
|
@ -1,20 +1,33 @@
|
|||
#include "bsaarchive.hpp"
|
||||
#include <components/bsa/compressedbsafile.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace VFS
|
||||
{
|
||||
|
||||
|
||||
BsaArchive::BsaArchive(const std::string &filename)
|
||||
{
|
||||
mFile.open(filename);
|
||||
Bsa::BsaVersion bsaVersion = Bsa::CompressedBSAFile::detectVersion(filename);
|
||||
|
||||
const Bsa::BSAFile::FileList &filelist = mFile.getList();
|
||||
if (bsaVersion == Bsa::BSAVER_COMPRESSED) {
|
||||
mFile = std::unique_ptr<Bsa::CompressedBSAFile>(new Bsa::CompressedBSAFile());
|
||||
}
|
||||
else {
|
||||
mFile = std::unique_ptr<Bsa::BSAFile>(new Bsa::BSAFile());
|
||||
}
|
||||
|
||||
mFile->open(filename);
|
||||
|
||||
const Bsa::BSAFile::FileList &filelist = mFile->getList();
|
||||
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))
|
||||
{
|
||||
for (std::vector<BsaArchiveFile>::iterator it = mResources.begin(); it != mResources.end(); ++it)
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
namespace VFS
|
||||
{
|
||||
|
||||
class BsaArchiveFile : public File
|
||||
{
|
||||
public:
|
||||
|
@ -23,15 +22,13 @@ namespace VFS
|
|||
{
|
||||
public:
|
||||
BsaArchive(const std::string& filename);
|
||||
|
||||
virtual ~BsaArchive();
|
||||
virtual void listResources(std::map<std::string, File*>& out, char (*normalize_function) (char));
|
||||
|
||||
private:
|
||||
Bsa::BSAFile mFile;
|
||||
|
||||
std::unique_ptr<Bsa::BSAFile> mFile;
|
||||
std::vector<BsaArchiveFile> mResources;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -14,18 +14,10 @@ namespace Gui
|
|||
virtual void setFontName(const std::string& name)
|
||||
{
|
||||
T::setFontName(name);
|
||||
T::setPropertyOverride ("FontHeight", mFontSize);
|
||||
T::setPropertyOverride ("FontHeight", getFontSize());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
T::setPropertyOverride (_key, _value);
|
||||
|
@ -34,11 +26,22 @@ namespace Gui
|
|||
// We should restore it.
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -23,11 +23,12 @@ 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::
|
||||
1. Download `Pelagiad <https://isaskar.github.io/Pelagiad/>`_ by Isak Larborn (aka Isaskar).
|
||||
2. Create ``Fonts`` folder at the location of your ``openmw.cfg``.
|
||||
3. Copy ``openmw_font.xml`` and ``Pelagiad.ttf`` files into the folder.
|
||||
4. Either remove the ``MonoFont`` entry from the created ``openmw_font.xml`` or copy the ``DejaVuLGCSansMono.ttf`` to the ``Fonts`` folder (prefer latter option on Windows).
|
||||
5. If desired, you can now delete the original ``Magic_Cards.*`` files from your ``Data Files/Fonts`` directory.
|
||||
6. Pelagiad glyphs may appear to be pretty small, so open ``settings.cfg`` and adjust the following settings as you deem necessary::
|
||||
|
||||
[GUI]
|
||||
font size = 17
|
||||
|
@ -35,9 +36,9 @@ This is the recommended way to install replacement fonts.
|
|||
|
||||
- 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``::
|
||||
1. Download `Ayembedt <https://github.com/georgd/OpenMW-Fonts>`_ by Georg Duffner.
|
||||
2. Install ``OMWAyembedt.otf`` into the ``Fonts`` folder.
|
||||
3. Add the following lines into ``openmw_font.xml``::
|
||||
|
||||
<Resource type="ResourceTrueTypeFont" name="Daedric">
|
||||
<Property key="Source" value="OMWAyembedt.otf"/>
|
||||
|
@ -51,7 +52,7 @@ This is the recommended way to install replacement fonts.
|
|||
</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.
|
||||
4. 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.
|
||||
|
||||
|
@ -62,15 +63,15 @@ Pre-0.45.0 way
|
|||
|
||||
- To replace the primary "Magic Cards" font:
|
||||
|
||||
#. 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.
|
||||
#. 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.
|
||||
1. Download `Pelagiad <https://isaskar.github.io/Pelagiad/>`_ by Isak Larborn (aka Isaskar).
|
||||
2. Install the ``openmw_font.xml`` file into ``resources/mygui/openmw_font.xml`` in your OpenMW installation.
|
||||
3. Copy ``Pelagiad.ttf`` into ``resources/mygui/`` as well.
|
||||
4. If desired, you can now delete the original ``Magic_Cards.*`` files from your ``Data Files/Fonts`` directory.
|
||||
- You can also replace the Daedric font:
|
||||
|
||||
#. Download `Ayembedt <https://github.com/georgd/OpenMW-Fonts>`_ by Georg Duffner.
|
||||
#. Install ``OMWAyembedt.otf`` into ``/resources/mygui/`` folder in your OpenMW installation.
|
||||
#. Add the following lines to ``openmw_font.xml``::
|
||||
1. Download `Ayembedt <https://github.com/georgd/OpenMW-Fonts>`_ by Georg Duffner.
|
||||
2. Install ``OMWAyembedt.otf`` into ``/resources/mygui/`` folder in your OpenMW installation.
|
||||
3. Add the following lines to ``openmw_font.xml``::
|
||||
|
||||
<Resource type="ResourceTrueTypeFont" name="Daedric">
|
||||
<Property key="Source" value="OMWAyembedt.otf"/>
|
||||
|
@ -86,12 +87,12 @@ Pre-0.45.0 way
|
|||
</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.
|
||||
4. 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.
|
||||
|
||||
#. Install the ``Oblivion.ttf`` file into ``resources/mygui/``.
|
||||
#. The ``openmw_font.xml`` entry is::
|
||||
1. Install the ``Oblivion.ttf`` file into ``resources/mygui/``.
|
||||
2. The ``openmw_font.xml`` entry is::
|
||||
|
||||
<Resource type="ResourceTrueTypeFont" name="Daedric">
|
||||
<Property key="Source" value="Oblivion.ttf"/>
|
||||
|
|
|
@ -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*.
|
||||
#. Finally, `we are done`_!
|
||||
|
||||
Since these models have one or two textures applied to them, the fix was not that time-consuming.
|
||||
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.
|
||||
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.
|
||||
|
||||
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/?
|
||||
.. _`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
|
||||
.. _`no longer have shiny models`: https://imgur.com/vu1k7n1
|
||||
.. _`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
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
<!-- Selected spell box -->
|
||||
<Widget type="Button" position="122 146 36 41" align="Left Bottom" name="SpellBox">
|
||||
<Widget type="Widget" skin="HUD_Box" position="0 0 36 36">
|
||||
<Widget type="ItemWidget" skin="MW_ItemIconNoShadow" position="-3 -3 42 42" align="Left Top" name="SpellImage"/>
|
||||
<Widget type="SpellWidget" skin="MW_ItemIconNoShadow" position="-3 -3 42 42" align="Left Top" name="SpellImage"/>
|
||||
<Property key="NeedMouse" value="false"/>
|
||||
</Widget>
|
||||
<Widget type="ProgressBar" skin="MW_EnergyBar_Magic" position="0 36 36 6" align="Left Bottom" name="SpellStatus">
|
||||
|
|
|
@ -68,7 +68,7 @@ void main()
|
|||
|
||||
vec3 normalizedNormal = normalize(passNormal);
|
||||
vec3 normalizedTangent = normalize(passTangent.xyz);
|
||||
vec3 binormal = cross(normalizedTangent, normalizedNormal);
|
||||
vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w;
|
||||
mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal);
|
||||
|
||||
vec3 viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0));
|
||||
|
|
Loading…
Reference in a new issue