1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-10-05 04:56:30 +00:00

Add OpenMW commits up to 1 Apr 2019

# Conflicts:
#	.travis.yml
#	CMakeLists.txt
#	apps/openmw/engine.cpp
#	apps/openmw/mwdialogue/dialoguemanagerimp.cpp
#	apps/openmw/mwgui/jailscreen.cpp
#	apps/openmw/mwgui/trainingwindow.cpp
#	apps/openmw/mwgui/travelwindow.cpp
#	apps/openmw/mwgui/waitdialog.cpp
This commit is contained in:
David Cernat 2019-08-21 23:54:39 +03:00
commit f671c0bddc
135 changed files with 1785 additions and 1303 deletions

View file

@ -3,6 +3,9 @@
Bug #2969: Scripted items can stack Bug #2969: Scripted items can stack
Bug #2987: Editor: some chance and AI data fields can overflow Bug #2987: Editor: some chance and AI data fields can overflow
Bug #3006: 'else if' operator breaks script compilation
Bug #3109: SetPos/Position handles actors differently
Bug #3282: Unintended behaviour when assigning F3 and Windows keys
Bug #3623: Fix HiDPI on Windows Bug #3623: Fix HiDPI on Windows
Bug #3733: Normal maps are inverted on mirrored UVs Bug #3733: Normal maps are inverted on mirrored UVs
Bug #3765: DisableTeleporting makes Mark/Recall/Intervention effects undetectable Bug #3765: DisableTeleporting makes Mark/Recall/Intervention effects undetectable
@ -18,6 +21,7 @@
Bug #4720: Inventory avatar has shield with two-handed weapon during [un]equipping animation Bug #4720: Inventory avatar has shield with two-handed weapon during [un]equipping animation
Bug #4723: ResetActors command works incorrectly Bug #4723: ResetActors command works incorrectly
Bug #4745: Editor: Interior cell lighting field values are not displayed as colors Bug #4745: Editor: Interior cell lighting field values are not displayed as colors
Bug #4736: LandTexture records overrides do not work
Bug #4746: Non-solid player can't run or sneak Bug #4746: Non-solid player can't run or sneak
Bug #4747: Bones are not read from X.NIF file for NPC animation Bug #4747: Bones are not read from X.NIF file for NPC animation
Bug #4750: Sneaking doesn't work in first person view if the player is in attack ready state Bug #4750: Sneaking doesn't work in first person view if the player is in attack ready state
@ -32,15 +36,26 @@
Bug #4813: Creatures with known file but no "Sound Gen Creature" assigned use default sounds Bug #4813: Creatures with known file but no "Sound Gen Creature" assigned use default sounds
Bug #4815: "Finished" journal entry with lower index doesn't close journal, SetJournalIndex closes journal Bug #4815: "Finished" journal entry with lower index doesn't close journal, SetJournalIndex closes journal
Bug #4820: Spell absorption is broken Bug #4820: Spell absorption is broken
Bug #4823: Jail progress bar works incorrectly
Bug #4827: NiUVController is handled incorrectly Bug #4827: NiUVController is handled incorrectly
Bug #4828: Potion looping effects VFX are not shown for NPCs Bug #4828: Potion looping effects VFX are not shown for NPCs
Bug #4837: CTD when a mesh with NiLODNode root node with particles is loaded Bug #4837: CTD when a mesh with NiLODNode root node with particles is loaded
Bug #4841: Russian localization ignores implicit keywords
Bug #4860: Actors outside of processing range visible for one frame after spawning Bug #4860: Actors outside of processing range visible for one frame after spawning
Bug #4867: Arbitrary text after local variable declarations breaks script compilation
Bug #4876: AI ratings handling inconsistencies Bug #4876: AI ratings handling inconsistencies
Bug #4877: Startup script executes only on a new game start
Bug #4888: Global variable stray explicit reference calls break script compilation Bug #4888: Global variable stray explicit reference calls break script compilation
Bug #4896: Title screen music doesn't loop
Bug #4911: Editor: QOpenGLContext::swapBuffers() warning with Qt5
Bug #4916: Specular power (shininess) material parameter is ignored when shaders are used.
Bug #4922: Werewolves can not attack if the transformation happens during attack
Bug #4938: Strings from subrecords with actually empty headers can't be empty
Bug #4942: Hand-to-Hand attack type is chosen randomly when "always use best attack" is turned off
Feature #2229: Improve pathfinding AI Feature #2229: Improve pathfinding AI
Feature #3442: Default values for fallbacks from ini file Feature #3442: Default values for fallbacks from ini file
Feature #3610: Option to invert X axis Feature #3610: Option to invert X axis
Feature #3893: Implicit target for "set" function in console
Feature #3980: In-game option to disable controller Feature #3980: In-game option to disable controller
Feature #4209: Editor: Faction rank sub-table Feature #4209: Editor: Faction rank sub-table
Feature #4673: Weapon sheathing Feature #4673: Weapon sheathing
@ -52,6 +67,7 @@
Feature #4887: Add openmw command option to set initial random seed Feature #4887: Add openmw command option to set initial random seed
Feature #4890: Make Distant Terrain configurable Feature #4890: Make Distant Terrain configurable
Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4686: Upgrade media decoder to a more current FFmpeg API
Task #4695: Optimize Distant Terrain memory consumption
0.45.0 0.45.0
------ ------

View file

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

View file

@ -4,12 +4,15 @@ export CXX=clang++
export CC=clang export CC=clang
DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps" DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps"
QT_PATH=`brew --prefix qt` QT_PATH=$(brew --prefix qt)
CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache
mkdir build mkdir build
cd build cd build
cmake \ cmake \
-D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \ -D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \
-D CMAKE_C_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE" \
-D CMAKE_CXX_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE" \
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.9" \ -D CMAKE_OSX_DEPLOYMENT_TARGET="10.9" \
-D CMAKE_OSX_SYSROOT="macosx10.14" \ -D CMAKE_OSX_SYSROOT="macosx10.14" \
-D CMAKE_BUILD_TYPE=Release \ -D CMAKE_BUILD_TYPE=Release \

View file

@ -267,11 +267,12 @@ endif()
IF(BUILD_OPENMW OR BUILD_OPENCS) IF(BUILD_OPENMW OR BUILD_OPENCS)
find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX osgShadow) find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX osgShadow)
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) include_directories(SYSTEM ${OPENSCENEGRAPH_INCLUDE_DIRS})
set(USED_OSG_PLUGINS set(USED_OSG_PLUGINS
osgdb_bmp osgdb_bmp
osgdb_dds osgdb_dds
osgdb_freetype
osgdb_jpeg osgdb_jpeg
osgdb_osg osgdb_osg
osgdb_png osgdb_png
@ -858,8 +859,8 @@ endif()
# Apple bundling # Apple bundling
if (OPENMW_OSX_DEPLOYMENT AND APPLE AND DESIRED_QT_VERSION MATCHES 5) if (OPENMW_OSX_DEPLOYMENT AND APPLE AND DESIRED_QT_VERSION MATCHES 5)
if (${CMAKE_MAJOR_VERSION} STREQUAL "3" AND ${CMAKE_MINOR_VERSION} STREQUAL "13") if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.13 AND CMAKE_VERSION VERSION_LESS 3.13.4)
message(FATAL_ERROR "macOS packaging is broken in CMake 3.13.*, see https://gitlab.com/OpenMW/openmw/issues/4767. Please use an older version like 3.12.4") message(FATAL_ERROR "macOS packaging is broken in early CMake 3.13 releases, see https://gitlab.com/OpenMW/openmw/issues/4767. Please use at least 3.13.4 or an older version like 3.12.4")
endif () endif ()
get_property(QT_COCOA_PLUGIN_PATH TARGET Qt5::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE) get_property(QT_COCOA_PLUGIN_PATH TARGET Qt5::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE)

View file

@ -739,7 +739,7 @@ void Record<ESM::Faction>::print()
std::cout << " Faction Reaction: " std::cout << " Faction Reaction: "
<< mData.mData.mRankData[i].mFactReaction << std::endl; << mData.mData.mRankData[i].mFactReaction << std::endl;
} }
for (const std::pair<std::string, int> &reaction : mData.mReactions) for (const auto &reaction : mData.mReactions)
std::cout << " Reaction: " << reaction.second << " = " << reaction.first << std::endl; std::cout << " Reaction: " << reaction.second << " = " << reaction.first << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl;
} }

View file

@ -102,6 +102,17 @@ bool parseOptions (int argc, char** argv, std::vector<std::string>& files)
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv). bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv).
options(desc).positional(p).run(); options(desc).positional(p).run();
bpo::store(valid_opts, variables); bpo::store(valid_opts, variables);
bpo::notify(variables);
if (variables.count ("help"))
{
std::cout << desc << std::endl;
return false;
}
if (variables.count("input-file"))
{
files = variables["input-file"].as< std::vector<std::string> >();
return true;
}
} }
catch(std::exception &e) catch(std::exception &e)
{ {
@ -110,18 +121,6 @@ bool parseOptions (int argc, char** argv, std::vector<std::string>& files)
return false; return false;
} }
bpo::notify(variables);
if (variables.count ("help"))
{
std::cout << desc << std::endl;
return false;
}
if (variables.count("input-file"))
{
files = variables["input-file"].as< std::vector<std::string> >();
return true;
}
std::cout << "No input files or directories specified!" << std::endl; std::cout << "No input files or directories specified!" << std::endl;
std::cout << desc << std::endl; std::cout << desc << std::endl;
return false; return false;

View file

@ -195,7 +195,7 @@ namespace CSMPrefs
// Only activate the best match; in exact conflicts, this will favor the first shortcut added. // Only activate the best match; in exact conflicts, this will favor the first shortcut added.
if (!potentials.empty()) if (!potentials.empty())
{ {
std::sort(potentials.begin(), potentials.end(), ShortcutEventHandler::sort); std::stable_sort(potentials.begin(), potentials.end(), ShortcutEventHandler::sort);
Shortcut* shortcut = potentials.front().second; Shortcut* shortcut = potentials.front().second;
if (shortcut->getModifierStatus() && shortcut->getSecondaryMode() == Shortcut::SM_Replace) if (shortcut->getModifierStatus() && shortcut->getSecondaryMode() == Shortcut::SM_Replace)
@ -325,7 +325,7 @@ namespace CSMPrefs
if (left.first == Matches_WithMod && right.first == Matches_NoMod) if (left.first == Matches_WithMod && right.first == Matches_NoMod)
return true; return true;
else else
return left.second->getPosition() >= right.second->getPosition(); return left.second->getPosition() > right.second->getPosition();
} }
void ShortcutEventHandler::widgetDestroyed() void ShortcutEventHandler::widgetDestroyed()

View file

@ -1006,7 +1006,7 @@ void CSMWorld::Data::loadFallbackEntries()
std::make_pair("PrisonMarker", "marker_prison.nif") std::make_pair("PrisonMarker", "marker_prison.nif")
}; };
for (const std::pair<std::string, std::string> &marker : staticMarkers) for (const auto &marker : staticMarkers)
{ {
if (mReferenceables.searchId (marker.first)==-1) if (mReferenceables.searchId (marker.first)==-1)
{ {
@ -1020,7 +1020,7 @@ void CSMWorld::Data::loadFallbackEntries()
} }
} }
for (const std::pair<std::string, std::string> &marker : doorMarkers) for (const auto &marker : doorMarkers)
{ {
if (mReferenceables.searchId (marker.first)==-1) if (mReferenceables.searchId (marker.first)==-1)
{ {

View file

@ -2,6 +2,7 @@
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <string>
#include <QMouseEvent> #include <QMouseEvent>
#include <QApplication> #include <QApplication>
@ -615,7 +616,39 @@ void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint)
} }
else if (hint[0]=='r') else if (hint[0]=='r')
{ {
/// \todo implement 'r' type hints // syntax r:ref#number (e.g. r:ref#100)
char ignore;
std::istringstream stream (hint.c_str());
if (stream >> ignore) // ignore r
{
char ignore1; // : or ;
std::string refCode; // ref#number (e.g. ref#100)
while (stream >> ignore1 >> refCode) {}
//Find out cell coordinate
CSMWorld::IdTable& references = dynamic_cast<CSMWorld::IdTable&> (
*mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_References));
int cellColumn = references.findColumnIndex(CSMWorld::Columns::ColumnId_Cell);
QVariant cell = references.data(references.getModelIndex(refCode, cellColumn)).value<QVariant>();
QString cellqs = cell.toString();
std::istringstream streamCellCoord (cellqs.toStdString().c_str());
if (streamCellCoord >> ignore) //ignore #
{
// Current coordinate
int x, y;
// Loop through all the coordinates to add them to selection
while (streamCellCoord >> x >> y)
selection.add (CSMWorld::CellCoordinates (x, y));
// Mark that camera needs setup
mCamPositionSet=false;
}
}
} }
setCellSelection (selection); setCellSelection (selection);

View file

@ -18,10 +18,10 @@ namespace CSVRender
private: private:
const CSMWorld::Data& mData; const CSMWorld::Data& mData;
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY); virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY) override;
virtual const ESM::LandTexture* getLandTexture(int index, short plugin); virtual const ESM::LandTexture* getLandTexture(int index, short plugin) override;
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) override;
}; };
} }

View file

@ -44,6 +44,8 @@
End of tes3mp addition End of tes3mp addition
*/ */
#include <components/detournavigator/navigator.hpp>
#include "mwinput/inputmanagerimp.hpp" #include "mwinput/inputmanagerimp.hpp"
#include "mwgui/windowmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp"
@ -289,6 +291,8 @@ bool OMW::Engine::frame(float frametime)
stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems()); stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems());
stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads()); stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads());
mEnvironment.getWorld()->getNavigator()->reportStats(frameNumber, *stats);
} }
} }
@ -313,6 +317,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
, mActivationDistanceOverride(-1) , mActivationDistanceOverride(-1)
, mGrab(true) , mGrab(true)
, mExportFonts(false) , mExportFonts(false)
, mRandomSeed(0)
, mScriptContext (0) , mScriptContext (0)
, mFSStrict (false) , mFSStrict (false)
, mScriptBlacklistUse (true) , mScriptBlacklistUse (true)
@ -667,7 +672,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
// Create the world // Create the world
mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(),
mFileCollections, mContentFiles, mEncoder, mFallbackMap, mFileCollections, mContentFiles, mEncoder, mFallbackMap,
mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mActivationDistanceOverride, mCellName, mResDir.string(), mCfgMgr.getUserDataPath().string()));
mEnvironment.getWorld()->setupPlayer(); mEnvironment.getWorld()->setupPlayer();
input->setPlayer(&mEnvironment.getWorld()->getPlayer()); input->setPlayer(&mEnvironment.getWorld()->getPlayer());
@ -783,6 +788,9 @@ void OMW::Engine::go()
*/ */
Log(Debug::Info) << "OSG version: " << osgGetVersion(); Log(Debug::Info) << "OSG version: " << osgGetVersion();
SDL_version sdlVersion;
SDL_GetVersion(&sdlVersion);
Log(Debug::Info) << "SDL version: " << (int)sdlVersion.major << "." << (int)sdlVersion.minor << "." << (int)sdlVersion.patch;
Misc::Rng::init(mRandomSeed); Misc::Rng::init(mRandomSeed);
@ -855,22 +863,21 @@ void OMW::Engine::go()
{ {
// start in main menu // start in main menu
mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu);
try mEnvironment.getSoundManager()->playTitleMusic();
{ std::string logo = mFallbackMap["Movies_Morrowind_Logo"];
// Is there an ini setting for this filename or something? if (!logo.empty())
mEnvironment.getSoundManager()->streamMusic("Special/morrowind title.mp3"); mEnvironment.getWindowManager()->playVideo(logo, true);
std::string logo = mFallbackMap["Movies_Morrowind_Logo"];
if (!logo.empty())
mEnvironment.getWindowManager()->playVideo(logo, true);
}
catch (...) {}
} }
else else
{ {
mEnvironment.getStateManager()->newGame (!mNewGame); mEnvironment.getStateManager()->newGame (!mNewGame);
} }
if (!mStartupScript.empty() && mEnvironment.getStateManager()->getState() == MWState::StateManager::State_Running)
{
mEnvironment.getWindowManager()->executeInConsole(mStartupScript);
}
// Start the main rendering loop // Start the main rendering loop
osg::Timer frameTimer; osg::Timer frameTimer;
double simulationTime = 0.0; double simulationTime = 0.0;

View file

@ -90,9 +90,9 @@ namespace MWBase
virtual void setPlayerClass (const ESM::Class& class_) = 0; virtual void setPlayerClass (const ESM::Class& class_) = 0;
///< Set player class to custom class. ///< Set player class to custom class.
virtual void restoreDynamicStats(MWWorld::Ptr actor, bool sleep) = 0; virtual void restoreDynamicStats(MWWorld::Ptr actor, double hours, bool sleep) = 0;
virtual void rest(bool sleep) = 0; virtual void rest(double hours, bool sleep) = 0;
///< If the player is sleeping or waiting, this should be called every hour. ///< If the player is sleeping or waiting, this should be called every hour.
/// @param sleep is the player sleeping or waiting? /// @param sleep is the player sleeping or waiting?

View file

@ -89,6 +89,9 @@ namespace MWBase
///< Start playing music from the selected folder ///< Start playing music from the selected folder
/// \param name of the folder that contains the playlist /// \param name of the folder that contains the playlist
virtual void playTitleMusic() = 0;
///< Start playing title music
virtual void say(const MWWorld::ConstPtr &reference, const std::string& filename) = 0; virtual void say(const MWWorld::ConstPtr &reference, const std::string& filename) = 0;
///< Make an actor say some text. ///< Make an actor say some text.
/// \param filename name of a sound file in "Sound/" in the data directory. /// \param filename name of a sound file in "Sound/" in the data directory.

View file

@ -406,7 +406,7 @@ namespace MWBase
virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0; virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0;
virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool moveToActive=false) = 0;
///< @return an updated Ptr in case the Ptr's cell changes ///< @return an updated Ptr in case the Ptr's cell changes
virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0;
@ -760,7 +760,7 @@ namespace MWBase
virtual bool isPlayerInJail() const = 0; virtual bool isPlayerInJail() const = 0;
virtual void rest() = 0; virtual void rest(double hours) = 0;
virtual void setPlayerTraveling(bool traveling) = 0; virtual void setPlayerTraveling(bool traveling) = 0;
virtual bool isPlayerTraveling() const = 0; virtual bool isPlayerTraveling() const = 0;

View file

@ -131,9 +131,6 @@ namespace MWDialogue
End of tes3mp addition End of tes3mp addition
*/ */
if (tok->isImplicitKeyword() && mTranslationDataStorage.hasTranslation())
continue;
if (mActorKnownTopics.count( topicId )) if (mActorKnownTopics.count( topicId ))
mKnownTopics.insert( topicId ); mKnownTopics.insert( topicId );
} }

View file

@ -19,7 +19,6 @@ namespace MWDialogue
Token(const std::string & text, Type type) : mText(text), mType(type) {} Token(const std::string & text, Type type) : mText(text), mType(type) {}
bool isExplicitLink() { return mType == ExplicitLink; } bool isExplicitLink() { return mType == ExplicitLink; }
bool isImplicitKeyword() { return mType == ImplicitKeyword; }
std::string mText; std::string mText;
Type mType; Type mType;

View file

@ -11,10 +11,12 @@
#include "../mwscript/extensions.hpp" #include "../mwscript/extensions.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/scriptmanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/class.hpp"
namespace MWGui namespace MWGui
{ {
@ -173,6 +175,12 @@ namespace MWGui
print("> " + command + "\n"); print("> " + command + "\n");
Compiler::Locals locals; Compiler::Locals locals;
if (!mPtr.isEmpty())
{
std::string script = mPtr.getClass().getScript(mPtr);
if (!script.empty())
locals = MWBase::Environment::get().getScriptManager()->getLocals(script);
}
Compiler::Output output (locals); Compiler::Output output (locals);
if (compile (command + "\n", output)) if (compile (command + "\n", output))

View file

@ -122,8 +122,7 @@ namespace MWGui
End of tes3mp addition End of tes3mp addition
*/ */
for (int i=0; i<mDays*24; ++i) MWBase::Environment::get().getMechanicsManager()->rest(mDays * 24, true);
MWBase::Environment::get().getMechanicsManager()->rest(true);
/* /*
Start of tes3mp change (major) Start of tes3mp change (major)

View file

@ -304,11 +304,10 @@ namespace MWGui
MyGUI::IntSize requested = button->getRequestedSize(); MyGUI::IntSize requested = button->getRequestedSize();
button->setImageCoord(MyGUI::IntCoord(0, 0, requested.width, requested.height));
// Trim off some of the excessive padding // Trim off some of the excessive padding
// TODO: perhaps do this within ImageButton? // TODO: perhaps do this within ImageButton?
int trim = 8; int height = requested.height-16;
button->setImageCoord(MyGUI::IntCoord(0, trim, requested.width, requested.height-trim));
int height = requested.height-trim*2;
button->setImageTile(MyGUI::IntSize(requested.width, height)); button->setImageTile(MyGUI::IntSize(requested.width, height));
button->setCoord((maxwidth-requested.width) / 2, curH, requested.width, height); button->setCoord((maxwidth-requested.width) / 2, curH, requested.width, height);
curH += height; curH += height;

View file

@ -244,8 +244,7 @@ namespace MWGui
map->setNeedMouseFocus(false); map->setNeedMouseFocus(false);
fog->setNeedMouseFocus(false); fog->setNeedMouseFocus(false);
mMapWidgets.push_back(map); mMaps.emplace_back(map, fog);
mFogWidgets.push_back(fog);
} }
} }
} }
@ -265,36 +264,37 @@ namespace MWGui
void LocalMapBase::applyFogOfWar() void LocalMapBase::applyFogOfWar()
{ {
TextureVector fogTextures;
for (int mx=0; mx<mNumCells; ++mx) for (int mx=0; mx<mNumCells; ++mx)
{ {
for (int my=0; my<mNumCells; ++my) for (int my=0; my<mNumCells; ++my)
{ {
int x = mCurX + (mx - mCellDistance); int x = mCurX + (mx - mCellDistance);
int y = mCurY + (-1*(my - mCellDistance)); int y = mCurY + (-1*(my - mCellDistance));
MyGUI::ImageBox* fog = mFogWidgets[my + mNumCells*mx];
MapEntry& entry = mMaps[my + mNumCells*mx];
MyGUI::ImageBox* fog = entry.mFogWidget;
if (!mFogOfWarToggled || !mFogOfWarEnabled) if (!mFogOfWarToggled || !mFogOfWarEnabled)
{ {
fog->setImageTexture(""); fog->setImageTexture("");
entry.mFogTexture.reset();
continue; continue;
} }
osg::ref_ptr<osg::Texture2D> tex = mLocalMapRender->getFogOfWarTexture(x, y); osg::ref_ptr<osg::Texture2D> tex = mLocalMapRender->getFogOfWarTexture(x, y);
if (tex) if (tex)
{ {
std::shared_ptr<MyGUI::ITexture> myguitex (new osgMyGUI::OSGTexture(tex)); entry.mFogTexture.reset(new osgMyGUI::OSGTexture(tex));
fog->setRenderItemTexture(myguitex.get()); fog->setRenderItemTexture(entry.mFogTexture.get());
fog->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); fog->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f));
fogTextures.push_back(myguitex);
} }
else else
{
fog->setImageTexture("black"); fog->setImageTexture("black");
entry.mFogTexture.reset();
}
} }
} }
// Move the textures we just set into mFogTextures, and move the previous textures into fogTextures, for deletion when this function ends.
// Note, above we need to ensure that all widgets are getting a new texture set, lest we delete textures that are still in use.
mFogTextures.swap(fogTextures);
redraw(); redraw();
} }
@ -428,7 +428,6 @@ namespace MWGui
applyFogOfWar(); applyFogOfWar();
// Update the map textures // Update the map textures
TextureVector textures;
for (int mx=0; mx<mNumCells; ++mx) for (int mx=0; mx<mNumCells; ++mx)
{ {
for (int my=0; my<mNumCells; ++my) for (int my=0; my<mNumCells; ++my)
@ -436,21 +435,23 @@ namespace MWGui
int mapX = x + (mx - mCellDistance); int mapX = x + (mx - mCellDistance);
int mapY = y + (-1*(my - mCellDistance)); int mapY = y + (-1*(my - mCellDistance));
MyGUI::ImageBox* box = mMapWidgets[my + mNumCells*mx]; MapEntry& entry = mMaps[my + mNumCells*mx];
MyGUI::ImageBox* box = entry.mMapWidget;
osg::ref_ptr<osg::Texture2D> texture = mLocalMapRender->getMapTexture(mapX, mapY); osg::ref_ptr<osg::Texture2D> texture = mLocalMapRender->getMapTexture(mapX, mapY);
if (texture) if (texture)
{ {
std::shared_ptr<MyGUI::ITexture> guiTex (new osgMyGUI::OSGTexture(texture)); entry.mMapTexture.reset(new osgMyGUI::OSGTexture(texture));
textures.push_back(guiTex); box->setRenderItemTexture(entry.mMapTexture.get());
box->setRenderItemTexture(guiTex.get());
box->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); box->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f));
} }
else else
{
box->setRenderItemTexture(nullptr); box->setRenderItemTexture(nullptr);
entry.mMapTexture.reset();
}
} }
} }
mMapTextures.swap(textures);
// Delay the door markers update until scripts have been given a chance to run. // Delay the door markers update until scripts have been given a chance to run.
// If we don't do this, door markers that should be disabled will still appear on the map. // If we don't do this, door markers that should be disabled will still appear on the map.
@ -896,22 +897,13 @@ namespace MWGui
void MapWindow::cellExplored(int x, int y) void MapWindow::cellExplored(int x, int y)
{ {
mQueuedToExplore.push_back(std::make_pair(x,y)); mGlobalMapRender->cleanupCameras();
mGlobalMapRender->exploreCell(x, y, mLocalMapRender->getMapTexture(x, y));
} }
void MapWindow::onFrame(float dt) void MapWindow::onFrame(float dt)
{ {
LocalMapBase::onFrame(dt); LocalMapBase::onFrame(dt);
mGlobalMapRender->cleanupCameras();
for (CellId& cellId : mQueuedToExplore)
{
mGlobalMapRender->exploreCell(cellId.first, cellId.second, mLocalMapRender->getMapTexture(cellId.first, cellId.second));
}
mQueuedToExplore.clear();
NoDrop::onFrame(dt); NoDrop::onFrame(dt);
} }
@ -1128,8 +1120,8 @@ namespace MWGui
NoDrop::setAlpha(alpha); NoDrop::setAlpha(alpha);
// can't allow showing map with partial transparency, as the fog of war will also go transparent // can't allow showing map with partial transparency, as the fog of war will also go transparent
// and reveal parts of the map you shouldn't be able to see // and reveal parts of the map you shouldn't be able to see
for (MyGUI::ImageBox* widget : mMapWidgets) for (MapEntry& entry : mMaps)
widget->setVisible(alpha == 1); entry.mMapWidget->setVisible(alpha == 1);
} }
void MapWindow::customMarkerCreated(MyGUI::Widget *marker) void MapWindow::customMarkerCreated(MyGUI::Widget *marker)

View file

@ -148,12 +148,17 @@ namespace MWGui
// Stores markers that were placed by a player. May be shared between multiple map views. // Stores markers that were placed by a player. May be shared between multiple map views.
CustomMarkerCollection& mCustomMarkers; CustomMarkerCollection& mCustomMarkers;
std::vector<MyGUI::ImageBox*> mMapWidgets; struct MapEntry
std::vector<MyGUI::ImageBox*> mFogWidgets; {
MapEntry(MyGUI::ImageBox* mapWidget, MyGUI::ImageBox* fogWidget)
: mMapWidget(mapWidget), mFogWidget(fogWidget) {}
typedef std::vector<std::shared_ptr<MyGUI::ITexture> > TextureVector; MyGUI::ImageBox* mMapWidget;
TextureVector mMapTextures; MyGUI::ImageBox* mFogWidget;
TextureVector mFogTextures; std::shared_ptr<MyGUI::ITexture> mMapTexture;
std::shared_ptr<MyGUI::ITexture> mFogTexture;
};
std::vector<MapEntry> mMaps;
// Keep track of created marker widgets, just to easily remove them later. // Keep track of created marker widgets, just to easily remove them later.
std::vector<MyGUI::Widget*> mDoorMarkerWidgets; std::vector<MyGUI::Widget*> mDoorMarkerWidgets;
@ -333,10 +338,6 @@ namespace MWGui
typedef std::pair<int, int> CellId; typedef std::pair<int, int> CellId;
std::set<CellId> mMarkers; std::set<CellId> mMarkers;
// Cells that should be explored in the next frame (i.e. their map revealed on the global map)
// We can't do this immediately, because the map update is not immediate either (see mNeedMapUpdate in scene.cpp)
std::vector<CellId> mQueuedToExplore;
MyGUI::Button* mEventBoxGlobal; MyGUI::Button* mEventBoxGlobal;
MyGUI::Button* mEventBoxLocal; MyGUI::Button* mEventBoxLocal;

View file

@ -141,7 +141,8 @@ namespace MWGui
MyGUI::ScrollBar* scroll = current->castType<MyGUI::ScrollBar>(); MyGUI::ScrollBar* scroll = current->castType<MyGUI::ScrollBar>();
std::string valueStr; std::string valueStr;
if (getSettingValueType(current) == "Float") std::string valueType = getSettingValueType(current);
if (valueType == "Float" || valueType == "Integer")
{ {
// TODO: ScrollBar isn't meant for this. should probably use a dedicated FloatSlider widget // TODO: ScrollBar isn't meant for this. should probably use a dedicated FloatSlider widget
float min,max; float min,max;
@ -438,14 +439,18 @@ namespace MWGui
if (getSettingType(scroller) == "Slider") if (getSettingType(scroller) == "Slider")
{ {
std::string valueStr; std::string valueStr;
if (getSettingValueType(scroller) == "Float") std::string valueType = getSettingValueType(scroller);
if (valueType == "Float" || valueType == "Integer")
{ {
float value = pos / float(scroller->getScrollRange()-1); float value = pos / float(scroller->getScrollRange()-1);
float min,max; float min,max;
getSettingMinMax(scroller, min, max); getSettingMinMax(scroller, min, max);
value = min + (max-min) * value; value = min + (max-min) * value;
Settings::Manager::setFloat(getSettingName(scroller), getSettingCategory(scroller), value); if (valueType == "Float")
Settings::Manager::setFloat(getSettingName(scroller), getSettingCategory(scroller), value);
else
Settings::Manager::setInt(getSettingName(scroller), getSettingCategory(scroller), (int)value);
valueStr = MyGUI::utility::toString(int(value)); valueStr = MyGUI::utility::toString(int(value));
} }
else else

View file

@ -79,14 +79,11 @@ namespace MWGui
static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fMagicStartIconBlink")->mValue.getFloat(); static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fMagicStartIconBlink")->mValue.getFloat();
std::vector<MagicEffectInfo>& effectInfos = effectInfoPair.second; std::vector<MagicEffectInfo>& effectInfos = effectInfoPair.second;
bool addNewLine = true; bool addNewLine = false;
for (const MagicEffectInfo& effectInfo : effectInfos) for (const MagicEffectInfo& effectInfo : effectInfos)
{ {
if (addNewLine) if (addNewLine)
{
sourcesDescription += "\n"; sourcesDescription += "\n";
addNewLine = false;
}
// if at least one of the effect sources is permanent, the effect will never wear off // if at least one of the effect sources is permanent, the effect will never wear off
if (effectInfo.mPermanent) if (effectInfo.mPermanent)
@ -161,6 +158,8 @@ namespace MWGui
sourcesDescription += MWGui::ToolTips::toString(duration) + "s"; sourcesDescription += MWGui::ToolTips::toString(duration) + "s";
} }
} }
addNewLine = true;
} }
if (remainingDuration > 0.f) if (remainingDuration > 0.f)

View file

@ -169,8 +169,7 @@ namespace MWGui
npcStats.setGoldPool(npcStats.getGoldPool() + price); npcStats.setGoldPool(npcStats.getGoldPool() + price);
// advance time // advance time
MWBase::Environment::get().getMechanicsManager()->rest(false); MWBase::Environment::get().getMechanicsManager()->rest(2, false);
MWBase::Environment::get().getMechanicsManager()->rest(false);
/* /*
Start of tes3mp change (major) Start of tes3mp change (major)

View file

@ -174,10 +174,7 @@ namespace MWGui
ESM::Position playerPos = player.getRefData().getPosition(); ESM::Position playerPos = player.getRefData().getPosition();
float d = (osg::Vec3f(pos.pos[0], pos.pos[1], 0) - osg::Vec3f(playerPos.pos[0], playerPos.pos[1], 0)).length(); float d = (osg::Vec3f(pos.pos[0], pos.pos[1], 0) - osg::Vec3f(playerPos.pos[0], playerPos.pos[1], 0)).length();
int hours = static_cast<int>(d /MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fTravelTimeMult")->mValue.getFloat()); int hours = static_cast<int>(d /MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fTravelTimeMult")->mValue.getFloat());
for(int i = 0;i < hours;i++) MWBase::Environment::get().getMechanicsManager()->rest(hours, true);
{
MWBase::Environment::get().getMechanicsManager ()->rest (true);
}
/* /*
Start of tes3mp change (major) Start of tes3mp change (major)

View file

@ -271,7 +271,7 @@ namespace MWGui
void WaitDialog::onWaitingProgressChanged(int cur, int total) void WaitDialog::onWaitingProgressChanged(int cur, int total)
{ {
mProgressBar.setProgress(cur, total); mProgressBar.setProgress(cur, total);
MWBase::Environment::get().getMechanicsManager()->rest(mSleeping); MWBase::Environment::get().getMechanicsManager()->rest(1, mSleeping);
/* /*
Start of tes3mp change (major) Start of tes3mp change (major)

View file

@ -1070,9 +1070,6 @@ namespace MWGui
updateMap(); updateMap();
if (!mMap->isVisible())
mMap->onFrame(frameDuration);
mHud->onFrame(frameDuration); mHud->onFrame(frameDuration);
mDebugWindow->onFrame(frameDuration); mDebugWindow->onFrame(frameDuration);

View file

@ -208,8 +208,6 @@ namespace MWInput
void InputManager::handleGuiArrowKey(int action) void InputManager::handleGuiArrowKey(int action)
{ {
// Temporary shut-down of this function until deemed necessary.
return;
if (SDL_IsTextInputActive()) if (SDL_IsTextInputActive())
return; return;
@ -414,7 +412,8 @@ namespace MWInput
case A_MoveRight: case A_MoveRight:
case A_MoveForward: case A_MoveForward:
case A_MoveBackward: case A_MoveBackward:
handleGuiArrowKey(action); // Temporary shut-down of this function until deemed necessary.
//handleGuiArrowKey(action);
break; break;
case A_Journal: case A_Journal:
toggleJournal (); toggleJournal ();
@ -1905,6 +1904,17 @@ namespace MWInput
MWBase::Environment::get().getWindowManager ()->notifyInputActionBound (); MWBase::Environment::get().getWindowManager ()->notifyInputActionBound ();
return; return;
} }
// Disallow binding reserved keys
if (key == SDL_SCANCODE_F3 || key == SDL_SCANCODE_F4 || key == SDL_SCANCODE_F10 || key == SDL_SCANCODE_F11)
return;
#ifndef __APPLE__
// Disallow binding Windows/Meta keys
if (key == SDL_SCANCODE_LGUI || key == SDL_SCANCODE_RGUI)
return;
#endif
if(!mDetectingKeyboard) if(!mDetectingKeyboard)
return; return;

View file

@ -147,23 +147,45 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
const MWWorld::Store<ESM::GameSetting>& settings = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); const MWWorld::Store<ESM::GameSetting>& settings = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0;
int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified ();
health = 0.1f * endurance; health = 0.1f * endurance;
magicka = 0; float fRestMagicMult = settings.find("fRestMagicMult")->mValue.getFloat ();
if (!stunted) magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
{
float fRestMagicMult = settings.find("fRestMagicMult")->mValue.getFloat ();
magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
}
} }
} }
namespace MWMechanics namespace MWMechanics
{ {
class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor
{
public:
float mRemainingTime;
GetStuntedMagickaDuration(const MWWorld::Ptr& actor)
: mRemainingTime(0.f){}
virtual void visit (MWMechanics::EffectKey key,
const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1)
{
if (mRemainingTime == -1) return;
if (key.mId == ESM::MagicEffect::StuntedMagicka)
{
if (totalTime == -1)
{
mRemainingTime = -1;
return;
}
if (remainingTime > mRemainingTime)
mRemainingTime = remainingTime;
}
}
};
class SoulTrap : public MWMechanics::EffectSourceVisitor class SoulTrap : public MWMechanics::EffectSourceVisitor
{ {
MWWorld::Ptr mCreature; MWWorld::Ptr mCreature;
@ -611,7 +633,7 @@ namespace MWMechanics
creatureStats.setMagicka(magicka); creatureStats.setMagicka(magicka);
} }
void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, bool sleep) void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, double hours, bool sleep)
{ {
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
if (stats.isDead()) if (stats.isDead())
@ -625,12 +647,36 @@ namespace MWMechanics
getRestorationPerHourOfSleep(ptr, health, magicka); getRestorationPerHourOfSleep(ptr, health, magicka);
DynamicStat<float> stat = stats.getHealth(); DynamicStat<float> stat = stats.getHealth();
stat.setCurrent(stat.getCurrent() + health); stat.setCurrent(stat.getCurrent() + health * hours);
stats.setHealth(stat); stats.setHealth(stat);
stat = stats.getMagicka(); double restoreHours = hours;
stat.setCurrent(stat.getCurrent() + magicka); bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0;
stats.setMagicka(stat); if (stunted)
{
// Stunted Magicka effect should be taken into account.
GetStuntedMagickaDuration visitor(ptr);
stats.getActiveSpells().visitEffectSources(visitor);
stats.getSpells().visitEffectSources(visitor);
if (ptr.getClass().hasInventoryStore(ptr))
ptr.getClass().getInventoryStore(ptr).visitEffectSources(visitor);
// Take a maximum remaining duration of Stunted Magicka effects (-1 is a constant one) in game hours.
if (visitor.mRemainingTime > 0)
{
double timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor();
restoreHours = std::max(0.0, hours - visitor.mRemainingTime * timeScale / 3600.f);
}
else if (visitor.mRemainingTime == -1)
restoreHours = 0;
}
if (restoreHours > 0)
{
stat = stats.getMagicka();
stat.setCurrent(stat.getCurrent() + magicka * restoreHours);
stats.setMagicka(stat);
}
} }
// Current fatigue can be above base value due to a fortify effect. // Current fatigue can be above base value due to a fortify effect.
@ -653,7 +699,7 @@ namespace MWMechanics
float x = fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance); float x = fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance);
x *= fEndFatigueMult * endurance; x *= fEndFatigueMult * endurance;
fatigue.setCurrent (fatigue.getCurrent() + 3600 * x); fatigue.setCurrent (fatigue.getCurrent() + 3600 * x * hours);
stats.setFatigue (fatigue); stats.setFatigue (fatigue);
} }
@ -1925,9 +1971,9 @@ namespace MWMechanics
} }
} }
void Actors::rest(bool sleep) void Actors::rest(double hours, bool sleep)
{ {
float duration = 3600.f / MWBase::Environment::get().getWorld()->getTimeScaleFactor(); float duration = hours * 3600.f / MWBase::Environment::get().getWorld()->getTimeScaleFactor();
const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
@ -1937,7 +1983,7 @@ namespace MWMechanics
continue; continue;
if (!sleep || iter->first == player) if (!sleep || iter->first == player)
restoreDynamicStats(iter->first, sleep); restoreDynamicStats(iter->first, hours, sleep);
if ((!iter->first.getRefData().getBaseNode()) || if ((!iter->first.getRefData().getBaseNode()) ||
(playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange) (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange)
@ -2049,11 +2095,12 @@ namespace MWMechanics
getRestorationPerHourOfSleep(ptr, healthPerHour, magickaPerHour); getRestorationPerHourOfSleep(ptr, healthPerHour, magickaPerHour);
CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0;
float healthHours = healthPerHour > 0 float healthHours = healthPerHour > 0
? (stats.getHealth().getModified() - stats.getHealth().getCurrent()) / healthPerHour ? (stats.getHealth().getModified() - stats.getHealth().getCurrent()) / healthPerHour
: 1.0f; : 1.0f;
float magickaHours = magickaPerHour > 0 float magickaHours = magickaPerHour > 0 && !stunted
? (stats.getMagicka().getModified() - stats.getMagicka().getCurrent()) / magickaPerHour ? (stats.getMagicka().getModified() - stats.getMagicka().getCurrent()) / magickaPerHour
: 1.0f; : 1.0f;

View file

@ -121,13 +121,13 @@ namespace MWMechanics
void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor,
MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance); MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance);
void rest(bool sleep); void rest(double hours, bool sleep);
///< Update actors while the player is waiting or sleeping. This should be called every hour. ///< Update actors while the player is waiting or sleeping.
void updateSneaking(CharacterController* ctrl, float duration); void updateSneaking(CharacterController* ctrl, float duration);
///< Update the sneaking indicator state according to the given player character controller. ///< Update the sneaking indicator state according to the given player character controller.
void restoreDynamicStats(const MWWorld::Ptr& actor, bool sleep); void restoreDynamicStats(const MWWorld::Ptr& actor, double hours, bool sleep);
int getHoursToRest(const MWWorld::Ptr& ptr) const; int getHoursToRest(const MWWorld::Ptr& ptr) const;
///< Calculate how many hours the given actor needs to rest in order to be fully healed ///< Calculate how many hours the given actor needs to rest in order to be fully healed

View file

@ -13,6 +13,8 @@
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "../mwphysics/collisiontype.hpp"
#include "pathgrid.hpp" #include "pathgrid.hpp"
#include "creaturestats.hpp" #include "creaturestats.hpp"
#include "steering.hpp" #include "steering.hpp"
@ -45,6 +47,16 @@ namespace MWMechanics
std::string("idle9"), std::string("idle9"),
}; };
namespace
{
inline int getCountBeforeReset(const MWWorld::ConstPtr& actor)
{
if (actor.getClass().isPureWaterCreature(actor) || actor.getClass().isPureFlyingCreature(actor))
return 1;
return COUNT_BEFORE_RESET;
}
}
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat): AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle),
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)),
@ -298,7 +310,8 @@ namespace MWMechanics
const auto currentPosition = actor.getRefData().getPosition().asVec3(); const auto currentPosition = actor.getRefData().getPosition().asVec3();
std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here
bool isWaterCreature = actor.getClass().isPureWaterCreature(actor); const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor);
const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor);
do { do {
// Determine a random location within radius of original position // Determine a random location within radius of original position
const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance; const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance;
@ -309,22 +322,31 @@ namespace MWMechanics
mDestination = osg::Vec3f(destinationX, destinationY, destinationZ); mDestination = osg::Vec3f(destinationX, destinationY, destinationZ);
// Check if land creature will walk onto water or if water creature will swim onto land // Check if land creature will walk onto water or if water creature will swim onto land
if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) || if (!isWaterCreature && destinationIsAtWater(actor, mDestination))
(isWaterCreature && !destinationThroughGround(currentPosition, mDestination))) continue;
if ((isWaterCreature || isFlyingCreature) && destinationThroughGround(currentPosition, mDestination))
continue;
if (isWaterCreature || isFlyingCreature)
{
mPathFinder.buildStraightPath(mDestination);
}
else
{ {
const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor);
mPathFinder.buildPath(actor, currentPosition, mDestination, actor.getCell(), mPathFinder.buildPathByNavMesh(actor, currentPosition, mDestination, halfExtents,
getPathGridGraph(actor.getCell()), halfExtents, getNavigatorFlags(actor)); getNavigatorFlags(actor));
mPathFinder.addPointToPath(mDestination);
if (mPathFinder.isPathConstructed())
{
storage.setState(AiWanderStorage::Wander_Walking, true);
mHasDestination = true;
mUsePathgrid = false;
}
return;
} }
if (mPathFinder.isPathConstructed())
{
storage.setState(AiWanderStorage::Wander_Walking, true);
mHasDestination = true;
mUsePathgrid = false;
}
break;
} while (--attempts); } while (--attempts);
} }
@ -342,8 +364,10 @@ namespace MWMechanics
* Returns true if the start to end point travels through a collision point (land). * Returns true if the start to end point travels through a collision point (land).
*/ */
bool AiWander::destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination) { bool AiWander::destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination) {
const int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door;
return MWBase::Environment::get().getWorld()->castRay(startPoint.x(), startPoint.y(), startPoint.z(), return MWBase::Environment::get().getWorld()->castRay(startPoint.x(), startPoint.y(), startPoint.z(),
destination.x(), destination.y(), destination.z()); destination.x(), destination.y(), destination.z(),
mask);
} }
void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) { void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) {
@ -479,7 +503,7 @@ namespace MWMechanics
} }
// if stuck for sufficiently long, act like current location was the destination // if stuck for sufficiently long, act like current location was the destination
if (storage.mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset if (storage.mStuckCount >= getCountBeforeReset(actor)) // something has gone wrong, reset
{ {
mObstacleCheck.clear(); mObstacleCheck.clear();
stopWalking(actor, storage); stopWalking(actor, storage);

View file

@ -1720,20 +1720,22 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
End of tes3mp change (major) End of tes3mp change (major)
*/ */
{ {
if (isWeapon) if (Settings::Manager::getBool("best attack", "Game"))
{ {
if (Settings::Manager::getBool("best attack", "Game")) if (isWeapon)
{ {
MWWorld::ConstContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); MWWorld::ConstContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
mAttackType = getBestAttack(weapon->get<ESM::Weapon>()->mBase); mAttackType = getBestAttack(weapon->get<ESM::Weapon>()->mBase);
} }
else else
setAttackTypeBasedOnMovement(); {
// There is no "best attack" for Hand-to-Hand
setAttackTypeRandomly(mAttackType);
}
} }
else else
{ {
// There is no "best attack" for Hand-to-Hand setAttackTypeBasedOnMovement();
setAttackTypeRandomly(mAttackType);
} }
} }
// else if (mPtr != getPlayer()) use mAttackType set by AiCombat // else if (mPtr != getPlayer()) use mAttackType set by AiCombat
@ -2697,6 +2699,13 @@ void CharacterController::forceStateUpdate()
return; return;
clearAnimQueue(); clearAnimQueue();
// Make sure we canceled the current attack or spellcasting,
// because we disabled attack animations anyway.
mCastingManualSpell = false;
mAttackingOrSpell = false;
if (mUpperBodyState != UpperCharState_Nothing)
mUpperBodyState = UpperCharState_WeapEquiped;
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
if(mDeathState != CharState_None) if(mDeathState != CharState_None)

View file

@ -303,7 +303,9 @@ namespace MWMechanics
void MechanicsManager::advanceTime (float duration) void MechanicsManager::advanceTime (float duration)
{ {
// Uses ingame time, but scaled to real time // Uses ingame time, but scaled to real time
duration /= MWBase::Environment::get().getWorld()->getTimeScaleFactor(); const float timeScaleFactor = MWBase::Environment::get().getWorld()->getTimeScaleFactor();
if (timeScaleFactor != 0.0f)
duration /= timeScaleFactor;
MWWorld::Ptr player = getPlayer(); MWWorld::Ptr player = getPlayer();
player.getClass().getInventoryStore(player).rechargeItems(duration); player.getClass().getInventoryStore(player).rechargeItems(duration);
} }
@ -491,17 +493,17 @@ namespace MWMechanics
return mActors.isSneaking(ptr); return mActors.isSneaking(ptr);
} }
void MechanicsManager::rest(bool sleep) void MechanicsManager::rest(double hours, bool sleep)
{ {
if (sleep) if (sleep)
MWBase::Environment::get().getWorld()->rest(); MWBase::Environment::get().getWorld()->rest(hours);
mActors.rest(sleep); mActors.rest(hours, sleep);
} }
void MechanicsManager::restoreDynamicStats(MWWorld::Ptr actor, bool sleep) void MechanicsManager::restoreDynamicStats(MWWorld::Ptr actor, double hours, bool sleep)
{ {
mActors.restoreDynamicStats(actor, sleep); mActors.restoreDynamicStats(actor, hours, sleep);
} }
int MechanicsManager::getHoursToRest() const int MechanicsManager::getHoursToRest() const

View file

@ -95,9 +95,9 @@ namespace MWMechanics
virtual void setPlayerClass (const ESM::Class& class_) override; virtual void setPlayerClass (const ESM::Class& class_) override;
///< Set player class to custom class. ///< Set player class to custom class.
virtual void restoreDynamicStats(MWWorld::Ptr actor, bool sleep) override; virtual void restoreDynamicStats(MWWorld::Ptr actor, double hours, bool sleep) override;
virtual void rest(bool sleep) override; virtual void rest(double hours, bool sleep) override;
///< If the player is sleeping or waiting, this should be called every hour. ///< If the player is sleeping or waiting, this should be called every hour.
/// @param sleep is the player sleeping or waiting? /// @param sleep is the player sleeping or waiting?

View file

@ -2,6 +2,8 @@
#include <components/sceneutil/positionattitudetransform.hpp> #include <components/sceneutil/positionattitudetransform.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
@ -121,17 +123,19 @@ namespace MWMechanics
* u = how long to move sideways * u = how long to move sideways
* *
*/ */
void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance) void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration)
{ {
const MWWorld::Class& cls = actor.getClass(); const ESM::Position pos = actor.getRefData().getPosition();
ESM::Position pos = actor.getRefData().getPosition();
if(mDistSameSpot == -1) if (mDistSameSpot == -1)
mDistSameSpot = DIST_SAME_SPOT * cls.getSpeed(actor) * scaleMinimumDistance; {
const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
mDistSameSpot = DIST_SAME_SPOT * actor.getClass().getSpeed(actor) + 1.2 * std::max(halfExtents.x(), halfExtents.y());
}
float distSameSpot = mDistSameSpot * duration; const float distSameSpot = mDistSameSpot * duration;
const float squaredMovedDistance = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2();
bool samePosition = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2() < distSameSpot * distSameSpot; const bool samePosition = squaredMovedDistance < distSameSpot * distSameSpot;
// update position // update position
mPrevX = pos.pos[0]; mPrevX = pos.pos[0];

View file

@ -30,7 +30,7 @@ namespace MWMechanics
bool isEvading() const; bool isEvading() const;
// Updates internal state, call each frame for moving actor // Updates internal state, call each frame for moving actor
void update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f); void update(const MWWorld::Ptr& actor, float duration);
// change direction to try to fix "stuck" actor // change direction to try to fix "stuck" actor
void takeEvasiveAction(MWMechanics::Movement& actorMovement) const; void takeEvasiveAction(MWMechanics::Movement& actorMovement) const;

View file

@ -269,6 +269,13 @@ namespace MWMechanics
mPath.pop_front(); mPath.pop_front();
} }
void PathFinder::buildStraightPath(const osg::Vec3f& endPoint)
{
mPath.clear();
mPath.push_back(endPoint);
mConstructed = true;
}
void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph) const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
{ {
@ -280,6 +287,16 @@ namespace MWMechanics
mConstructed = true; mConstructed = true;
} }
void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags)
{
mPath.clear();
buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath));
mConstructed = true;
}
void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
const DetourNavigator::Flags flags) const DetourNavigator::Flags flags)

View file

@ -72,9 +72,14 @@ namespace MWMechanics
mCell = nullptr; mCell = nullptr;
} }
void buildStraightPath(const osg::Vec3f& endPoint);
void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
void buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags);
void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
const DetourNavigator::Flags flags); const DetourNavigator::Flags flags);

View file

@ -170,7 +170,7 @@ namespace MWMechanics
float rating = rateEffects(enchantment->mEffects, actor, enemy); float rating = rateEffects(enchantment->mEffects, actor, enemy);
rating *= 2; // prefer rechargable magic items over spells rating *= 1.25f; // prefer rechargable magic items over spells
return rating; return rating;
} }

View file

@ -127,7 +127,9 @@ namespace MWMechanics
value = ref->mBase->mData.mCombat; value = ref->mBase->mData.mCombat;
} }
rating *= getHitChance(actor, enemy, value) / 100.f; // Take hit chance in account, but do not allow rating become negative.
float chance = getHitChance(actor, enemy, value) / 100.f;
rating *= std::min(1.f, std::max(0.01f, chance));
if (weapon->mData.mType < ESM::Weapon::Arrow) if (weapon->mData.mType < ESM::Weapon::Arrow)
rating *= weapon->mData.mSpeed; rating *= weapon->mData.mSpeed;

View file

@ -242,7 +242,8 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
{ {
osg::Vec4f glowColor = getEnchantmentColor(*weapon); osg::Vec4f glowColor = getEnchantmentColor(*weapon);
mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor); mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor);
resetControllers(mScabbard->getNode()); if (mScabbard)
resetControllers(mScabbard->getNode());
} }
return; return;

View file

@ -1798,17 +1798,13 @@ namespace MWRender
material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha)); material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha));
material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
stateset->addUniform(new osg::Uniform("colorMode", 0), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
mObjectRoot->setStateSet(stateset); mObjectRoot->setStateSet(stateset);
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
} }
} }
else else
{ {
mObjectRoot->setStateSet(nullptr); mObjectRoot->setStateSet(nullptr);
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
} }
setRenderBin(); setRenderBin();

View file

@ -329,7 +329,7 @@ namespace MWRender
camera->setViewMatrix(osg::Matrix::identity()); camera->setViewMatrix(osg::Matrix::identity());
camera->setProjectionMatrix(osg::Matrix::identity()); camera->setProjectionMatrix(osg::Matrix::identity());
camera->setProjectionResizePolicy(osg::Camera::FIXED); camera->setProjectionResizePolicy(osg::Camera::FIXED);
camera->setRenderOrder(osg::Camera::PRE_RENDER); camera->setRenderOrder(osg::Camera::PRE_RENDER, 1); // Make sure the global map is rendered after the local map
y = mHeight - y - height; // convert top-left origin to bottom-left y = mHeight - y - height; // convert top-left origin to bottom-left
camera->setViewport(x, y, width, height); camera->setViewport(x, y, width, height);

View file

@ -12,16 +12,15 @@ namespace MWRender
{ {
LandManager::LandManager(int loadFlags) LandManager::LandManager(int loadFlags)
: ResourceManager(nullptr) : GenericResourceManager<std::pair<int, int> >(nullptr)
, mLoadFlags(loadFlags) , mLoadFlags(loadFlags)
{ {
mCache = new CacheType;
} }
osg::ref_ptr<ESMTerrain::LandObject> LandManager::getLand(int x, int y) osg::ref_ptr<ESMTerrain::LandObject> LandManager::getLand(int x, int y)
{ {
std::string idstr = std::to_string(x) + " " + std::to_string(y); osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(std::make_pair(x,y));
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(idstr);
if (obj) if (obj)
return static_cast<ESMTerrain::LandObject*>(obj.get()); return static_cast<ESMTerrain::LandObject*>(obj.get());
else else
@ -30,7 +29,7 @@ osg::ref_ptr<ESMTerrain::LandObject> LandManager::getLand(int x, int y)
if (!land) if (!land)
return nullptr; return nullptr;
osg::ref_ptr<ESMTerrain::LandObject> landObj (new ESMTerrain::LandObject(land, mLoadFlags)); osg::ref_ptr<ESMTerrain::LandObject> landObj (new ESMTerrain::LandObject(land, mLoadFlags));
mCache->addEntryToObjectCache(idstr, landObj.get()); mCache->addEntryToObjectCache(std::make_pair(x,y), landObj.get());
return landObj; return landObj;
} }
} }

View file

@ -3,6 +3,7 @@
#include <osg/Object> #include <osg/Object>
#include <components/resource/objectcache.hpp>
#include <components/resource/resourcemanager.hpp> #include <components/resource/resourcemanager.hpp>
#include <components/esmterrain/storage.hpp> #include <components/esmterrain/storage.hpp>
@ -14,7 +15,7 @@ namespace ESM
namespace MWRender namespace MWRender
{ {
class LandManager : public Resource::ResourceManager class LandManager : public Resource::GenericResourceManager<std::pair<int, int> >
{ {
public: public:
LandManager(int loadFlags); LandManager(int loadFlags);

View file

@ -613,7 +613,8 @@ void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orient
if (!segment.mFogOfWarImage || !segment.mMapTexture) if (!segment.mFogOfWarImage || !segment.mMapTexture)
continue; continue;
unsigned char* data = segment.mFogOfWarImage->data(); uint32_t* data = (uint32_t*)segment.mFogOfWarImage->data();
bool changed = false;
for (int texV = 0; texV<sFogOfWarResolution; ++texV) for (int texV = 0; texV<sFogOfWarResolution; ++texV)
{ {
for (int texU = 0; texU<sFogOfWarResolution; ++texU) for (int texU = 0; texU<sFogOfWarResolution; ++texU)
@ -625,14 +626,22 @@ void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orient
uint8_t alpha = (clr >> 24); uint8_t alpha = (clr >> 24);
alpha = std::min( alpha, (uint8_t) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); alpha = std::min( alpha, (uint8_t) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) );
*(uint32_t*)data = (uint32_t) (alpha << 24); uint32_t val = (uint32_t) (alpha << 24);
if ( *data != val)
{
*data = val;
changed = true;
}
data += 4; ++data;
} }
} }
segment.mHasFogState = true; if (changed)
segment.mFogOfWarImage->dirty(); {
segment.mHasFogState = true;
segment.mFogOfWarImage->dirty();
}
} }
} }
} }

View file

@ -54,10 +54,8 @@ std::string getVampireHead(const std::string& race, bool female)
if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) if (sVampireMapping.find(thisCombination) == sVampireMapping.end())
{ {
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>(); for (const ESM::BodyPart& bodypart : store.get<ESM::BodyPart>())
for(MWWorld::Store<ESM::BodyPart>::iterator it = partStore.begin(); it != partStore.end(); ++it)
{ {
const ESM::BodyPart& bodypart = *it;
if (!bodypart.mData.mVampire) if (!bodypart.mData.mVampire)
continue; continue;
if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) if (bodypart.mData.mType != ESM::BodyPart::MT_Skin)
@ -68,12 +66,11 @@ std::string getVampireHead(const std::string& race, bool female)
continue; continue;
if (!Misc::StringUtils::ciEqual(bodypart.mRace, race)) if (!Misc::StringUtils::ciEqual(bodypart.mRace, race))
continue; continue;
sVampireMapping[thisCombination] = &*it; sVampireMapping[thisCombination] = &bodypart;
} }
} }
if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) sVampireMapping.emplace(thisCombination, nullptr);
sVampireMapping[thisCombination] = nullptr;
const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination]; const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination];
if (!bodyPart) if (!bodyPart)
@ -238,6 +235,18 @@ void HeadAnimationTime::setBlinkStop(float value)
// ---------------------------------------------------- // ----------------------------------------------------
NpcAnimation::NpcType NpcAnimation::getNpcType()
{
const MWWorld::Class &cls = mPtr.getClass();
NpcAnimation::NpcType curType = Type_Normal;
if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0)
curType = Type_Vampire;
if (cls.getNpcStats(mPtr).isWerewolf())
curType = Type_Werewolf;
return curType;
}
static NpcAnimation::PartBoneMap createPartListMap() static NpcAnimation::PartBoneMap createPartListMap()
{ {
NpcAnimation::PartBoneMap result; NpcAnimation::PartBoneMap result;
@ -283,7 +292,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> par
mViewMode(viewMode), mViewMode(viewMode),
mShowWeapons(false), mShowWeapons(false),
mShowCarriedLeft(true), mShowCarriedLeft(true),
mNpcType(Type_Normal), mNpcType(getNpcType()),
mFirstPersonFieldOfView(firstPersonFieldOfView), mFirstPersonFieldOfView(firstPersonFieldOfView),
mSoundsDisabled(disableSounds), mSoundsDisabled(disableSounds),
mAccurateAiming(false), mAccurateAiming(false),
@ -431,41 +440,39 @@ void NpcAnimation::updateNpcBase()
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Race *race = store.get<ESM::Race>().find(mNpc->mRace); const ESM::Race *race = store.get<ESM::Race>().find(mNpc->mRace);
bool isWerewolf = (mNpcType == Type_Werewolf); NpcType curType = getNpcType();
bool isVampire = (mNpcType == Type_Vampire); bool isWerewolf = (curType == Type_Werewolf);
bool isVampire = (curType == Type_Vampire);
bool isFemale = !mNpc->isMale(); bool isFemale = !mNpc->isMale();
if (isWerewolf) mHeadModel.clear();
{ mHairModel.clear();
mHeadModel = "meshes\\" + store.get<ESM::BodyPart>().find("WerewolfHead")->mModel;
mHairModel = "meshes\\" + store.get<ESM::BodyPart>().find("WerewolfHair")->mModel;
}
else
{
mHeadModel = "";
const std::string& vampireHead = getVampireHead(mNpc->mRace, isFemale);
if (isVampire && !vampireHead.empty())
mHeadModel = vampireHead;
else if (!mNpc->mHead.empty())
{
const ESM::BodyPart* bp = store.get<ESM::BodyPart>().search(mNpc->mHead);
if (bp)
mHeadModel = "meshes\\" + bp->mModel;
else
Log(Debug::Warning) << "Warning: Failed to load body part '" << mNpc->mHead << "'";
}
mHairModel = ""; std::string headName = isWerewolf ? "WerewolfHead" : mNpc->mHead;
if (!mNpc->mHair.empty()) std::string hairName = isWerewolf ? "WerewolfHair" : mNpc->mHair;
{
const ESM::BodyPart* bp = store.get<ESM::BodyPart>().search(mNpc->mHair); if (!headName.empty())
if (bp) {
mHairModel = "meshes\\" + bp->mModel; const ESM::BodyPart* bp = store.get<ESM::BodyPart>().search(headName);
else if (bp)
Log(Debug::Warning) << "Warning: Failed to load body part '" << mNpc->mHair << "'"; mHeadModel = "meshes\\" + bp->mModel;
} else
Log(Debug::Warning) << "Warning: Failed to load body part '" << headName << "'";
} }
if (!hairName.empty())
{
const ESM::BodyPart* bp = store.get<ESM::BodyPart>().search(hairName);
if (bp)
mHairModel = "meshes\\" + bp->mModel;
else
Log(Debug::Warning) << "Warning: Failed to load body part '" << hairName << "'";
}
const std::string& vampireHead = getVampireHead(mNpc->mRace, isFemale);
if (!isWerewolf && isVampire && !vampireHead.empty())
mHeadModel = vampireHead;
bool is1stPerson = mViewMode == VM_FirstPerson; bool is1stPerson = mViewMode == VM_FirstPerson;
bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0;
@ -473,7 +480,7 @@ void NpcAnimation::updateNpcBase()
defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS()); defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS());
std::string smodel = defaultSkeleton; std::string smodel = defaultSkeleton;
if (!is1stPerson && !isWerewolf & !mNpc->mModel.empty()) if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty())
smodel = Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS()); smodel = Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS());
setObjectRoot(smodel, true, true, false); setObjectRoot(smodel, true, true, false);
@ -517,14 +524,7 @@ void NpcAnimation::updateParts()
if (!mObjectRoot.get()) if (!mObjectRoot.get())
return; return;
const MWWorld::Class &cls = mPtr.getClass(); NpcType curType = getNpcType();
NpcType curType = Type_Normal;
if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0)
curType = Type_Vampire;
if (cls.getNpcStats(mPtr).isWerewolf())
curType = Type_Werewolf;
if (curType != mNpcType) if (curType != mNpcType)
{ {
mNpcType = curType; mNpcType = curType;
@ -632,7 +632,7 @@ void NpcAnimation::updateParts()
showWeapons(mShowWeapons); showWeapons(mShowWeapons);
showCarriedLeft(mShowCarriedLeft); showCarriedLeft(mShowCarriedLeft);
bool isWerewolf = (mNpcType == Type_Werewolf); bool isWerewolf = (getNpcType() == Type_Werewolf);
std::string race = (isWerewolf ? "werewolf" : Misc::StringUtils::lowerCase(mNpc->mRace)); std::string race = (isWerewolf ? "werewolf" : Misc::StringUtils::lowerCase(mNpc->mRace));
const std::vector<const ESM::BodyPart*> &parts = getBodyParts(race, !mNpc->isMale(), mViewMode == VM_FirstPerson, isWerewolf); const std::vector<const ESM::BodyPart*> &parts = getBodyParts(race, !mNpc->isMale(), mViewMode == VM_FirstPerson, isWerewolf);
@ -649,9 +649,6 @@ void NpcAnimation::updateParts()
if (wasArrowAttached) if (wasArrowAttached)
attachArrow(); attachArrow();
if (mAlpha != 1.f)
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
} }
@ -730,10 +727,7 @@ void NpcAnimation::removePartGroup(int group)
bool NpcAnimation::isFirstPersonPart(const ESM::BodyPart* bodypart) bool NpcAnimation::isFirstPersonPart(const ESM::BodyPart* bodypart)
{ {
return (bodypart->mId.size() >= 3) return bodypart->mId.size() >= 3 && bodypart->mId.substr(bodypart->mId.size()-3, 3) == "1st";
&& bodypart->mId[bodypart->mId.size()-3] == '1'
&& bodypart->mId[bodypart->mId.size()-2] == 's'
&& bodypart->mId[bodypart->mId.size()-1] == 't';
} }
bool NpcAnimation::isFemalePart(const ESM::BodyPart* bodypart) bool NpcAnimation::isFemalePart(const ESM::BodyPart* bodypart)
@ -793,16 +787,16 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
osg::Object* obj = node->getUserDataContainer()->getUserObject(i); osg::Object* obj = node->getUserDataContainer()->getUserObject(i);
if (NifOsg::TextKeyMapHolder* keys = dynamic_cast<NifOsg::TextKeyMapHolder*>(obj)) if (NifOsg::TextKeyMapHolder* keys = dynamic_cast<NifOsg::TextKeyMapHolder*>(obj))
{ {
for (NifOsg::TextKeyMap::const_iterator it = keys->mTextKeys.begin(); it != keys->mTextKeys.end(); ++it) for (const auto &key : keys->mTextKeys)
{ {
if (Misc::StringUtils::ciEqual(it->second, "talk: start")) if (Misc::StringUtils::ciEqual(key.second, "talk: start"))
mHeadAnimationTime->setTalkStart(it->first); mHeadAnimationTime->setTalkStart(key.first);
if (Misc::StringUtils::ciEqual(it->second, "talk: stop")) if (Misc::StringUtils::ciEqual(key.second, "talk: stop"))
mHeadAnimationTime->setTalkStop(it->first); mHeadAnimationTime->setTalkStop(key.first);
if (Misc::StringUtils::ciEqual(it->second, "blink: start")) if (Misc::StringUtils::ciEqual(key.second, "blink: start"))
mHeadAnimationTime->setBlinkStart(it->first); mHeadAnimationTime->setBlinkStart(key.first);
if (Misc::StringUtils::ciEqual(it->second, "blink: stop")) if (Misc::StringUtils::ciEqual(key.second, "blink: stop"))
mHeadAnimationTime->setBlinkStop(it->first); mHeadAnimationTime->setBlinkStop(key.first);
} }
break; break;
@ -828,16 +822,15 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM::
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>(); const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
const char *ext = (mViewMode == VM_FirstPerson) ? ".1st" : ""; const char *ext = (mViewMode == VM_FirstPerson) ? ".1st" : "";
std::vector<ESM::PartReference>::const_iterator part(parts.begin()); for(const ESM::PartReference& part : parts)
for(;part != parts.end();++part)
{ {
const ESM::BodyPart *bodypart = 0; const ESM::BodyPart *bodypart = nullptr;
if(!mNpc->isMale() && !part->mFemale.empty()) if(!mNpc->isMale() && !part.mFemale.empty())
{ {
bodypart = partStore.search(part->mFemale+ext); bodypart = partStore.search(part.mFemale+ext);
if(!bodypart && mViewMode == VM_FirstPerson) if(!bodypart && mViewMode == VM_FirstPerson)
{ {
bodypart = partStore.search(part->mFemale); bodypart = partStore.search(part.mFemale);
if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand || if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand ||
bodypart->mData.mPart == ESM::BodyPart::MP_Wrist || bodypart->mData.mPart == ESM::BodyPart::MP_Wrist ||
bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || bodypart->mData.mPart == ESM::BodyPart::MP_Forearm ||
@ -845,14 +838,14 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM::
bodypart = nullptr; bodypart = nullptr;
} }
else if (!bodypart) else if (!bodypart)
Log(Debug::Warning) << "Warning: Failed to find body part '" << part->mFemale << "'"; Log(Debug::Warning) << "Warning: Failed to find body part '" << part.mFemale << "'";
} }
if(!bodypart && !part->mMale.empty()) if(!bodypart && !part.mMale.empty())
{ {
bodypart = partStore.search(part->mMale+ext); bodypart = partStore.search(part.mMale+ext);
if(!bodypart && mViewMode == VM_FirstPerson) if(!bodypart && mViewMode == VM_FirstPerson)
{ {
bodypart = partStore.search(part->mMale); bodypart = partStore.search(part.mMale);
if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand || if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand ||
bodypart->mData.mPart == ESM::BodyPart::MP_Wrist || bodypart->mData.mPart == ESM::BodyPart::MP_Wrist ||
bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || bodypart->mData.mPart == ESM::BodyPart::MP_Forearm ||
@ -860,13 +853,13 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM::
bodypart = nullptr; bodypart = nullptr;
} }
else if (!bodypart) else if (!bodypart)
Log(Debug::Warning) << "Warning: Failed to find body part '" << part->mMale << "'"; Log(Debug::Warning) << "Warning: Failed to find body part '" << part.mMale << "'";
} }
if(bodypart) if(bodypart)
addOrReplaceIndividualPart((ESM::PartReferenceType)part->mPart, group, priority, "meshes\\"+bodypart->mModel, enchantedGlow, glowColor); addOrReplaceIndividualPart((ESM::PartReferenceType)part.mPart, group, priority, "meshes\\"+bodypart->mModel, enchantedGlow, glowColor);
else else
reserveIndividualPart((ESM::PartReferenceType)part->mPart, group, priority); reserveIndividualPart((ESM::PartReferenceType)part.mPart, group, priority);
} }
} }
@ -885,7 +878,7 @@ void NpcAnimation::addControllers()
osg::MatrixTransform* node = found->second.get(); osg::MatrixTransform* node = found->second.get();
mFirstPersonNeckController = new NeckController(mObjectRoot.get()); mFirstPersonNeckController = new NeckController(mObjectRoot.get());
node->addUpdateCallback(mFirstPersonNeckController); node->addUpdateCallback(mFirstPersonNeckController);
mActiveControllers.insert(std::make_pair(node, mFirstPersonNeckController)); mActiveControllers.emplace(node, mFirstPersonNeckController);
} }
} }
else if (mViewMode == VM_Normal) else if (mViewMode == VM_Normal)
@ -918,9 +911,6 @@ void NpcAnimation::showWeapons(bool showWeapon)
attachArrow(); attachArrow();
} }
} }
// Note: we will need to recreate shaders later if we use weapon sheathing anyway, so there is no point to update them here
if (mAlpha != 1.f && !mWeaponSheathing)
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
} }
else else
{ {
@ -932,10 +922,6 @@ void NpcAnimation::showWeapons(bool showWeapon)
updateHolsteredWeapon(!mShowWeapons); updateHolsteredWeapon(!mShowWeapons);
updateQuiver(); updateQuiver();
// Recreate shaders for invisible actors, otherwise sheath nodes will be visible
if (mAlpha != 1.f && mWeaponSheathing)
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
} }
void NpcAnimation::showCarriedLeft(bool show) void NpcAnimation::showCarriedLeft(bool show)
@ -953,8 +939,6 @@ void NpcAnimation::showCarriedLeft(bool show)
if (iter->getTypeName() == typeid(ESM::Light).name() && mObjectParts[ESM::PRT_Shield]) if (iter->getTypeName() == typeid(ESM::Light).name() && mObjectParts[ESM::PRT_Shield])
addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), iter->get<ESM::Light>()->mBase); addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), iter->get<ESM::Light>()->mBase);
} }
if (mAlpha != 1.f)
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
} }
else else
removeIndividualPart(ESM::PRT_Shield); removeIndividualPart(ESM::PRT_Shield);
@ -1087,40 +1071,39 @@ const std::vector<const ESM::BodyPart *>& NpcAnimation::getBodyParts(const std::
std::vector<const ESM::BodyPart*>& parts = sRaceMapping[std::make_pair(race, flags)]; std::vector<const ESM::BodyPart*>& parts = sRaceMapping[std::make_pair(race, flags)];
typedef std::multimap<ESM::BodyPart::MeshPart,ESM::PartReferenceType> BodyPartMapType; typedef std::multimap<ESM::BodyPart::MeshPart,ESM::PartReferenceType> BodyPartMapType;
static BodyPartMapType sBodyPartMap; static const BodyPartMapType sBodyPartMap =
if(sBodyPartMap.empty())
{ {
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Neck, ESM::PRT_Neck)); {ESM::BodyPart::MP_Neck, ESM::PRT_Neck},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass)); {ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Groin, ESM::PRT_Groin)); {ESM::BodyPart::MP_Groin, ESM::PRT_Groin},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_RHand)); {ESM::BodyPart::MP_Hand, ESM::PRT_RHand},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_LHand)); {ESM::BodyPart::MP_Hand, ESM::PRT_LHand},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist)); {ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist)); {ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm)); {ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm)); {ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm)); {ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm)); {ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_RFoot)); {ESM::BodyPart::MP_Foot, ESM::PRT_RFoot},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_LFoot)); {ESM::BodyPart::MP_Foot, ESM::PRT_LFoot},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle)); {ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle)); {ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_RKnee)); {ESM::BodyPart::MP_Knee, ESM::PRT_RKnee},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_LKnee)); {ESM::BodyPart::MP_Knee, ESM::PRT_LKnee},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg)); {ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg)); {ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg},
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Tail, ESM::PRT_Tail)); {ESM::BodyPart::MP_Tail, ESM::PRT_Tail}
} };
parts.resize(ESM::PRT_Count, nullptr); parts.resize(ESM::PRT_Count, nullptr);
if (werewolf)
return parts;
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
for(MWWorld::Store<ESM::BodyPart>::iterator it = partStore.begin(); it != partStore.end(); ++it) for(const ESM::BodyPart& bodypart : store.get<ESM::BodyPart>())
{ {
if(werewolf)
break;
const ESM::BodyPart& bodypart = *it;
if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable) if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable)
continue; continue;
if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) if (bodypart.mData.mType != ESM::BodyPart::MT_Skin)

View file

@ -74,6 +74,8 @@ private:
void updateNpcBase(); void updateNpcBase();
NpcType getNpcType();
PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename, PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename,
const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor=nullptr); const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor=nullptr);

View file

@ -314,6 +314,7 @@ namespace MWRender
mTerrain->setDefaultViewer(mViewer->getCamera()); mTerrain->setDefaultViewer(mViewer->getCamera());
mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells"));
mTerrain->setWorkQueue(mWorkQueue.get());
mCamera.reset(new Camera(mViewer->getCamera())); mCamera.reset(new Camera(mViewer->getCamera()));
@ -371,8 +372,10 @@ namespace MWRender
mNearClip = Settings::Manager::getFloat("near clip", "Camera"); mNearClip = Settings::Manager::getFloat("near clip", "Camera");
mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera");
mFieldOfView = Settings::Manager::getFloat("field of view", "Camera"); float fov = Settings::Manager::getFloat("field of view", "Camera");
mFirstPersonFieldOfView = Settings::Manager::getFloat("first person field of view", "Camera"); mFieldOfView = std::min(std::max(1.f, fov), 179.f);
float firstPersonFov = Settings::Manager::getFloat("first person field of view", "Camera");
mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f);
mStateUpdater->setFogEnd(mViewDistance); mStateUpdater->setFogEnd(mViewDistance);
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip));
@ -1198,6 +1201,12 @@ namespace MWRender
mUniformNear->set(mNearClip); mUniformNear->set(mNearClip);
mUniformFar->set(mViewDistance); mUniformFar->set(mViewDistance);
// Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear.
// Limit FOV here just for sure, otherwise viewing distance can be too high.
fov = std::min(mFieldOfView, 140.f);
float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f);
mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f));
} }
void RenderingManager::updateTextureFiltering() void RenderingManager::updateTextureFiltering()
@ -1446,8 +1455,8 @@ namespace MWRender
{ {
try try
{ {
const auto locked = it->second.lockConst(); const auto locked = it->second->lockConst();
mNavMesh->update(locked->getValue(), mNavMeshNumber, locked->getGeneration(), mNavMesh->update(locked->getImpl(), mNavMeshNumber, locked->getGeneration(),
locked->getNavMeshRevision(), mNavigator.getSettings()); locked->getNavMeshRevision(), mNavigator.getSettings());
} }
catch (const std::exception& e) catch (const std::exception& e)

View file

@ -22,6 +22,15 @@ namespace MWRender
mResourceSystem->removeResourceManager(mLandManager.get()); mResourceSystem->removeResourceManager(mLandManager.get());
} }
bool TerrainStorage::hasData(int cellX, int cellY)
{
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
const ESM::Land* land = esmStore.get<ESM::Land>().search(cellX, cellY);
return land != nullptr;
}
void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY)
{ {
minX = 0, minY = 0, maxX = 0, maxY = 0; minX = 0, minY = 0, maxX = 0, maxY = 0;

View file

@ -20,11 +20,13 @@ namespace MWRender
TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern = "", const std::string& normalHeightMapPatteern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern = "", const std::string& normalHeightMapPatteern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false);
~TerrainStorage(); ~TerrainStorage();
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY); virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY) override;
virtual const ESM::LandTexture* getLandTexture(int index, short plugin); virtual const ESM::LandTexture* getLandTexture(int index, short plugin) override;
virtual bool hasData(int cellX, int cellY) override;
/// Get bounds of the whole terrain in cell units /// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) override;
LandManager* getLandManager() const; LandManager* getLandManager() const;

View file

@ -1070,7 +1070,7 @@ namespace MWScript
const std::string script = ptr.getClass().getScript(ptr); const std::string script = ptr.getClass().getScript(ptr);
if(script.empty()) if(script.empty())
str<< ptr.getCellRef().getRefId()<<" does not have a script."; str<< ptr.getCellRef().getRefId()<<" does not have a script.";
else else
{ {
str<< "Local variables for "<<ptr.getCellRef().getRefId(); str<< "Local variables for "<<ptr.getCellRef().getRefId();

View file

@ -285,11 +285,11 @@ namespace MWScript
MWWorld::Ptr updated = ptr; MWWorld::Ptr updated = ptr;
if(axis == "x") if(axis == "x")
{ {
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,pos,ay,az); updated = MWBase::Environment::get().getWorld()->moveObject(ptr,pos,ay,az,true);
} }
else if(axis == "y") else if(axis == "y")
{ {
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,pos,az); updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,pos,az,true);
} }
else if(axis == "z") else if(axis == "z")
{ {
@ -304,7 +304,7 @@ namespace MWScript
pos = terrainHeight; pos = terrainHeight;
} }
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos); updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos,true);
} }
else else
throw std::runtime_error ("invalid axis: " + axis); throw std::runtime_error ("invalid axis: " + axis);
@ -447,7 +447,7 @@ namespace MWScript
} }
else else
{ {
ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z); ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true);
} }
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(base,ptr); dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(base,ptr);

View file

@ -96,25 +96,25 @@ bool FFmpeg_Decoder::getAVAudioData()
return false; return false;
do { do {
if(mPacket.size == 0 && !getNextPacket())
return false;
/* Decode some data, and check for errors */ /* Decode some data, and check for errors */
int ret = 0; int ret = avcodec_receive_frame(mCodecCtx, mFrame);
ret = avcodec_receive_frame(mCodecCtx, mFrame);
if (ret == 0)
got_frame = true;
if (ret == AVERROR(EAGAIN)) if (ret == AVERROR(EAGAIN))
ret = 0; {
if (ret == 0) if (mPacket.size == 0 && !getNextPacket())
return false;
ret = avcodec_send_packet(mCodecCtx, &mPacket); ret = avcodec_send_packet(mCodecCtx, &mPacket);
if (ret < 0 && ret != AVERROR(EAGAIN)) av_packet_unref(&mPacket);
if (ret == 0)
continue;
}
if (ret != 0)
return false; return false;
av_packet_unref(&mPacket); av_packet_unref(&mPacket);
if (!got_frame || mFrame->nb_samples == 0) if (mFrame->nb_samples == 0)
continue; continue;
got_frame = true;
if(mSwr) if(mSwr)
{ {
@ -138,7 +138,7 @@ bool FFmpeg_Decoder::getAVAudioData()
else else
mFrameData = &mFrame->data[0]; mFrameData = &mFrame->data[0];
} while(!got_frame || mFrame->nb_samples == 0); } while(!got_frame);
mNextPts += (double)mFrame->nb_samples / mCodecCtx->sample_rate; mNextPts += (double)mFrame->nb_samples / mCodecCtx->sample_rate;
return true; return true;

View file

@ -471,6 +471,36 @@ namespace MWSound
startRandomTitle(); startRandomTitle();
} }
void SoundManager::playTitleMusic()
{
if (mCurrentPlaylist == "Title")
return;
if (mMusicFiles.find("Title") == mMusicFiles.end())
{
std::vector<std::string> filelist;
const std::map<std::string, VFS::File*>& index = mVFS->getIndex();
// Is there an ini setting for this filename or something?
std::string filename = "music/special/morrowind title.mp3";
auto found = index.find(filename);
if (found != index.end())
{
filelist.emplace_back(found->first);
mMusicFiles["Title"] = filelist;
}
else
{
Log(Debug::Warning) << "Title music not found";
return;
}
}
if (mMusicFiles["Title"].empty())
return;
mCurrentPlaylist = "Title";
startRandomTitle();
}
void SoundManager::say(const MWWorld::ConstPtr &ptr, const std::string &filename) void SoundManager::say(const MWWorld::ConstPtr &ptr, const std::string &filename)
{ {
@ -1122,10 +1152,10 @@ namespace MWSound
if(!mOutput->isInitialized()) if(!mOutput->isInitialized())
return; return;
updateSounds(duration);
if (MWBase::Environment::get().getStateManager()->getState()!= if (MWBase::Environment::get().getStateManager()->getState()!=
MWBase::StateManager::State_NoGame) MWBase::StateManager::State_NoGame)
{ {
updateSounds(duration);
updateRegionSound(duration); updateRegionSound(duration);
updateWaterSound(duration); updateWaterSound(duration);
} }

View file

@ -168,6 +168,9 @@ namespace MWSound
///< Start playing music from the selected folder ///< Start playing music from the selected folder
/// \param name of the folder that contains the playlist /// \param name of the folder that contains the playlist
virtual void playTitleMusic();
///< Start playing title music
virtual void say(const MWWorld::ConstPtr &reference, const std::string& filename); virtual void say(const MWWorld::ConstPtr &reference, const std::string& filename);
///< Make an actor say some text. ///< Make an actor say some text.
/// \param filename name of a sound file in "Sound/" in the data directory. /// \param filename name of a sound file in "Sound/" in the data directory.

View file

@ -381,7 +381,7 @@ namespace MWWorld
{ {
for (unsigned int i=0; i<mTerrainViews.size() && i<mPreloadPositions.size() && !mAbort; ++i) for (unsigned int i=0; i<mTerrainViews.size() && i<mPreloadPositions.size() && !mAbort; ++i)
{ {
mWorld->preload(mTerrainViews[i], mPreloadPositions[i]); mWorld->preload(mTerrainViews[i], mPreloadPositions[i], mAbort);
mTerrainViews[i]->reset(0); mTerrainViews[i]->reset(0);
} }
} }

View file

@ -150,16 +150,16 @@ MWWorld::CellStore *MWWorld::Cells::getInterior (const std::string& name)
return &result->second; return &result->second;
} }
void MWWorld::Cells::rest () void MWWorld::Cells::rest (double hours)
{ {
for (auto &interior : mInteriors) for (auto &interior : mInteriors)
{ {
interior.second.rest(); interior.second.rest(hours);
} }
for (auto &exterior : mExteriors) for (auto &exterior : mExteriors)
{ {
exterior.second.rest(); exterior.second.rest(hours);
} }
} }

View file

@ -61,7 +61,7 @@ namespace MWWorld
/// @note name must be lower case /// @note name must be lower case
Ptr getPtr (const std::string& name); Ptr getPtr (const std::string& name);
void rest (); void rest (double hours);
/// Get all Ptrs referencing \a name in exterior cells /// Get all Ptrs referencing \a name in exterior cells
/// @note Due to the current implementation of getPtr this only supports one Ptr per cell. /// @note Due to the current implementation of getPtr this only supports one Ptr per cell.

View file

@ -1152,7 +1152,7 @@ namespace MWWorld
} }
} }
void CellStore::rest() void CellStore::rest(double hours)
{ {
if (mState == State_Loaded) if (mState == State_Loaded)
{ {
@ -1161,7 +1161,7 @@ namespace MWWorld
Ptr ptr = getCurrentPtr(&*it); Ptr ptr = getCurrentPtr(&*it);
if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
{ {
MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, true); MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true);
} }
} }
for (CellRefList<ESM::NPC>::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) for (CellRefList<ESM::NPC>::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it)
@ -1169,7 +1169,7 @@ namespace MWWorld
Ptr ptr = getCurrentPtr(&*it); Ptr ptr = getCurrentPtr(&*it);
if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
{ {
MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, true); MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true);
} }
} }
} }

View file

@ -183,7 +183,7 @@ namespace MWWorld
/// @return updated MWWorld::Ptr with the new CellStore pointer set. /// @return updated MWWorld::Ptr with the new CellStore pointer set.
MWWorld::Ptr moveTo(const MWWorld::Ptr& object, MWWorld::CellStore* cellToMoveTo); MWWorld::Ptr moveTo(const MWWorld::Ptr& object, MWWorld::CellStore* cellToMoveTo);
void rest(); void rest(double hours);
/// Make a copy of the given object and insert it into this cell. /// Make a copy of the given object and insert it into this cell.
/// @note If you get a linker error here, this means the given type can not be inserted into a cell. /// @note If you get a linker error here, this means the given type can not be inserted into a cell.

View file

@ -360,7 +360,7 @@ namespace MWWorld
{ {
if (const auto object = mPhysics->getObject(ptr)) if (const auto object = mPhysics->getObject(ptr))
navigator->removeObject(DetourNavigator::ObjectId(object)); navigator->removeObject(DetourNavigator::ObjectId(object));
else if (const auto actor = mPhysics->getActor(ptr)) else if (mPhysics->getActor(ptr))
{ {
navigator->removeAgent(world->getPathfindingHalfExtents(ptr)); navigator->removeAgent(world->getPathfindingHalfExtents(ptr));
mRendering.removeActorPath(ptr); mRendering.removeActorPath(ptr);
@ -892,7 +892,7 @@ namespace MWWorld
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
navigator->update(player.getRefData().getPosition().asVec3()); navigator->update(player.getRefData().getPosition().asVec3());
} }
else if (const auto actor = mPhysics->getActor(ptr)) else if (mPhysics->getActor(ptr))
{ {
navigator->removeAgent(MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(ptr)); navigator->removeAgent(MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(ptr));
} }

View file

@ -387,6 +387,21 @@ namespace MWWorld
assert(plugin < mStatic.size()); assert(plugin < mStatic.size());
// Replace texture for records with given ID and index from all plugins.
for (unsigned int i=0; i<mStatic.size(); i++)
{
ESM::LandTexture* tex = const_cast<ESM::LandTexture*>(search(lt.mIndex, i));
if (tex)
{
const std::string texId = Misc::StringUtils::lowerCase(tex->mId);
const std::string ltId = Misc::StringUtils::lowerCase(lt.mId);
if (texId == ltId)
{
tex->mTexture = lt.mTexture;
}
}
}
LandTextureList &ltexl = mStatic[plugin]; LandTextureList &ltexl = mStatic[plugin];
if(lt.mIndex + 1 > (int)ltexl.size()) if(lt.mIndex + 1 > (int)ltexl.size())
ltexl.resize(lt.mIndex+1); ltexl.resize(lt.mIndex+1);

View file

@ -178,12 +178,12 @@ namespace MWWorld
const Files::Collections& fileCollections, const Files::Collections& fileCollections,
const std::vector<std::string>& contentFiles, const std::vector<std::string>& contentFiles,
ToUTF8::Utf8Encoder* encoder, const std::map<std::string,std::string>& fallbackMap, ToUTF8::Utf8Encoder* encoder, const std::map<std::string,std::string>& fallbackMap,
int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, int activationDistanceOverride, const std::string& startCell,
const std::string& resourcePath, const std::string& userDataPath) const std::string& resourcePath, const std::string& userDataPath)
: mResourceSystem(resourceSystem), mFallback(fallbackMap), mLocalScripts (mStore), : mResourceSystem(resourceSystem), mFallback(fallbackMap), mLocalScripts (mStore),
mSky (true), mCells (mStore, mEsm), mSky (true), mCells (mStore, mEsm),
mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles), mUserDataPath(userDataPath), mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles), mUserDataPath(userDataPath),
mActivationDistanceOverride (activationDistanceOverride), mStartupScript(startupScript), mActivationDistanceOverride (activationDistanceOverride),
mStartCell (startCell), mDistanceToFacedObject(-1), mTeleportEnabled(true), mStartCell (startCell), mDistanceToFacedObject(-1), mTeleportEnabled(true),
mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0),
mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f)
@ -337,9 +337,6 @@ namespace MWWorld
if (!mPhysics->toggleCollisionMode()) if (!mPhysics->toggleCollisionMode())
mPhysics->toggleCollisionMode(); mPhysics->toggleCollisionMode();
if (!mStartupScript.empty())
MWBase::Environment::get().getWindowManager()->executeInConsole(mStartupScript);
MWBase::Environment::get().getWindowManager()->updatePlayer(); MWBase::Environment::get().getWindowManager()->updatePlayer();
} }
@ -503,7 +500,7 @@ namespace MWWorld
gmst["iWereWolfBounty"] = ESM::Variant(1000); gmst["iWereWolfBounty"] = ESM::Variant(1000);
gmst["fCombatDistanceWerewolfMod"] = ESM::Variant(0.3f); gmst["fCombatDistanceWerewolfMod"] = ESM::Variant(0.3f);
for (const std::pair<std::string, ESM::Variant> &params : gmst) for (const auto &params : gmst)
{ {
if (!mStore.get<ESM::GameSetting>().search(params.first)) if (!mStore.get<ESM::GameSetting>().search(params.first))
{ {
@ -533,7 +530,7 @@ namespace MWWorld
globals["crimegoldturnin"] = ESM::Variant(0); globals["crimegoldturnin"] = ESM::Variant(0);
globals["pchasturnin"] = ESM::Variant(0); globals["pchasturnin"] = ESM::Variant(0);
for (const std::pair<std::string, ESM::Variant> &params : globals) for (const auto &params : globals)
{ {
if (!mStore.get<ESM::Global>().search(params.first)) if (!mStore.get<ESM::Global>().search(params.first))
{ {
@ -552,7 +549,7 @@ namespace MWWorld
statics["templemarker"] = "marker_temple.nif"; statics["templemarker"] = "marker_temple.nif";
statics["travelmarker"] = "marker_travel.nif"; statics["travelmarker"] = "marker_travel.nif";
for (const std::pair<std::string, std::string> &params : statics) for (const auto &params : statics)
{ {
if (!mStore.get<ESM::Static>().search(params.first)) if (!mStore.get<ESM::Static>().search(params.first))
{ {
@ -566,7 +563,7 @@ namespace MWWorld
std::map<std::string, std::string> doors; std::map<std::string, std::string> doors;
doors["prisonmarker"] = "marker_prison.nif"; doors["prisonmarker"] = "marker_prison.nif";
for (const std::pair<std::string, std::string> &params : doors) for (const auto &params : doors)
{ {
if (!mStore.get<ESM::Door>().search(params.first)) if (!mStore.get<ESM::Door>().search(params.first))
{ {
@ -1512,23 +1509,24 @@ namespace MWWorld
return newPtr; return newPtr;
} }
MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics) MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive)
{ {
CellStore *cell = ptr.getCell(); int cellX, cellY;
positionToIndex(x, y, cellX, cellY);
if (cell->isExterior()) { CellStore* cell = ptr.getCell();
int cellX, cellY; CellStore* newCell = getExterior(cellX, cellY);
positionToIndex(x, y, cellX, cellY); bool isCellActive = getPlayerPtr().getCell()->isExterior() && mWorldScene->isCellActive(*newCell);
cell = getExterior(cellX, cellY); if (cell->isExterior() || (moveToActive && isCellActive && ptr.getClass().isActor()))
} cell = newCell;
return moveObject(ptr, cell, x, y, z, movePhysics); return moveObject(ptr, cell, x, y, z, movePhysics);
} }
MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z) MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive)
{ {
return moveObjectImp(ptr, x, y, z); return moveObjectImp(ptr, x, y, z, true, moveToActive);
} }
void World::scaleObject (const Ptr& ptr, float scale) void World::scaleObject (const Ptr& ptr, float scale)
@ -3698,9 +3696,9 @@ namespace MWWorld
return closestMarker; return closestMarker;
} }
void World::rest() void World::rest(double hours)
{ {
mCells.rest(); mCells.rest(hours);
} }
void World::teleportToClosestMarker (const MWWorld::Ptr& ptr, void World::teleportToClosestMarker (const MWWorld::Ptr& ptr,

View file

@ -120,8 +120,6 @@ namespace MWWorld
int mActivationDistanceOverride; int mActivationDistanceOverride;
std::string mStartupScript;
std::map<MWWorld::Ptr, int> mDoorStates; std::map<MWWorld::Ptr, int> mDoorStates;
///< only holds doors that are currently moving. 1 = opening, 2 = closing ///< only holds doors that are currently moving. 1 = opening, 2 = closing
@ -132,7 +130,7 @@ namespace MWWorld
void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust); void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust);
Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true); Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false);
///< @return an updated Ptr in case the Ptr's cell changes ///< @return an updated Ptr in case the Ptr's cell changes
Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos);
@ -211,7 +209,7 @@ namespace MWWorld
const Files::Collections& fileCollections, const Files::Collections& fileCollections,
const std::vector<std::string>& contentFiles, const std::vector<std::string>& contentFiles,
ToUTF8::Utf8Encoder* encoder, const std::map<std::string,std::string>& fallbackMap, ToUTF8::Utf8Encoder* encoder, const std::map<std::string,std::string>& fallbackMap,
int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath); int activationDistanceOverride, const std::string& startCell, const std::string& resourcePath, const std::string& userDataPath);
virtual ~World(); virtual ~World();
@ -499,7 +497,7 @@ namespace MWWorld
void undeleteObject (const Ptr& ptr) override; void undeleteObject (const Ptr& ptr) override;
MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z) override; MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive=false) override;
///< @return an updated Ptr in case the Ptr's cell changes ///< @return an updated Ptr in case the Ptr's cell changes
MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override; MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override;
@ -737,7 +735,7 @@ namespace MWWorld
RestPermitted canRest() const override; RestPermitted canRest() const override;
///< check if the player is allowed to rest ///< check if the player is allowed to rest
void rest() override; void rest(double hours) override;
/// \todo Probably shouldn't be here /// \todo Probably shouldn't be here
MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) override; MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) override;

View file

@ -80,14 +80,6 @@ namespace
EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), NavigatorException); EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), NavigatorException);
} }
TEST_F(DetourNavigatorNavigatorTest, find_path_for_removed_agent_should_return_empty)
{
mNavigator->addAgent(mAgentHalfExtents);
mNavigator->removeAgent(mAgentHalfExtents);
mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut);
EXPECT_EQ(mPath, std::deque<osg::Vec3f>());
}
TEST_F(DetourNavigatorNavigatorTest, add_agent_should_count_each_agent) TEST_F(DetourNavigatorNavigatorTest, add_agent_should_count_each_agent)
{ {
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);

View file

@ -125,7 +125,7 @@ namespace Compiler
if (loop.size()!=loop2.size()) if (loop.size()!=loop2.size())
throw std::logic_error ( throw std::logic_error (
"internal compiler error: failed to generate a while loop"); "Internal compiler error: failed to generate a while loop");
std::copy (loop2.begin(), loop2.end(), std::back_inserter (mCode)); std::copy (loop2.begin(), loop2.end(), std::back_inserter (mCode));
@ -179,6 +179,14 @@ namespace Compiler
scanner.scan (mLineParser); scanner.scan (mLineParser);
return true; return true;
} }
else if (mState==IfElseJunkState)
{
getErrorHandler().warning ("Extra text after else", loc);
SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip);
mState = IfElseBodyState;
return true;
}
return Parser::parseName (name, loc, scanner); return Parser::parseName (name, loc, scanner);
} }
@ -207,8 +215,7 @@ namespace Compiler
return true; return true;
} }
} }
else if (mState==IfBodyState || mState==IfElseifBodyState || mState==IfElseBodyState || else if (mState==IfBodyState || mState==IfElseifBodyState || mState==IfElseBodyState)
mState==IfElseJunkState)
{ {
if (parseIfBody (keyword, loc, scanner)) if (parseIfBody (keyword, loc, scanner))
return true; return true;
@ -218,6 +225,14 @@ namespace Compiler
if ( parseWhileBody (keyword, loc, scanner)) if ( parseWhileBody (keyword, loc, scanner))
return true; return true;
} }
else if (mState==IfElseJunkState)
{
getErrorHandler().warning ("Extra text after else", loc);
SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip);
mState = IfElseBodyState;
return true;
}
return Parser::parseKeyword (keyword, loc, scanner); return Parser::parseKeyword (keyword, loc, scanner);
} }
@ -250,8 +265,9 @@ namespace Compiler
default: ; default: ;
} }
} }
else if (code==Scanner::S_open && mState==IfElseJunkState) else if (mState==IfElseJunkState)
{ {
getErrorHandler().warning ("Extra text after else", loc);
SkipParser skip (getErrorHandler(), getContext()); SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip); scanner.scan (skip);
mState = IfElseBodyState; mState = IfElseBodyState;

View file

@ -22,20 +22,20 @@ bool Compiler::DeclarationParser::parseName (const std::string& name, const Toke
char type = mLocals.getType (name2); char type = mLocals.getType (name2);
if (type!=' ') if (type!=' ')
{ getErrorHandler().warning ("Local variable re-declaration", loc);
/// \todo add option to make re-declared local variables an error else
getErrorHandler().warning ("ignoring local variable re-declaration", mLocals.declare (mType, name2);
loc);
mState = State_End;
return true;
}
mLocals.declare (mType, name2);
mState = State_End; mState = State_End;
return true; return true;
} }
else if (mState==State_End)
{
getErrorHandler().warning ("Extra text after local variable declaration", loc);
SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip);
return false;
}
return Parser::parseName (name, loc, scanner); return Parser::parseName (name, loc, scanner);
} }
@ -61,17 +61,31 @@ bool Compiler::DeclarationParser::parseKeyword (int keyword, const TokenLoc& loc
else if (mState==State_Name) else if (mState==State_Name)
{ {
// allow keywords to be used as local variable names. MW script compiler, you suck! // allow keywords to be used as local variable names. MW script compiler, you suck!
/// \todo option to disable this atrocity.
return parseName (loc.mLiteral, loc, scanner); return parseName (loc.mLiteral, loc, scanner);
} }
else if (mState==State_End)
{
getErrorHandler().warning ("Extra text after local variable declaration", loc);
SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip);
return false;
}
return Parser::parseKeyword (keyword, loc, scanner); return Parser::parseKeyword (keyword, loc, scanner);
} }
bool Compiler::DeclarationParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) bool Compiler::DeclarationParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
{ {
if (code==Scanner::S_newline && mState==State_End) if (mState==State_End)
{
if (code!=Scanner::S_newline)
{
getErrorHandler().warning ("Extra text after local variable declaration", loc);
SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip);
}
return false; return false;
}
return Parser::parseSpecial (code, loc, scanner); return Parser::parseSpecial (code, loc, scanner);
} }

View file

@ -11,7 +11,7 @@ namespace Compiler
{ {
public: public:
virtual const char *what() const throw() { return "compile error";} virtual const char *what() const throw() { return "Compile error";}
///< Return error message ///< Return error message
}; };
@ -21,7 +21,7 @@ namespace Compiler
{ {
public: public:
virtual const char *what() const throw() { return "can't read file"; } virtual const char *what() const throw() { return "Can't read file"; }
///< Return error message ///< Return error message
}; };
@ -31,7 +31,7 @@ namespace Compiler
{ {
public: public:
virtual const char *what() const throw() { return "end of file"; } virtual const char *what() const throw() { return "End of file"; }
///< Return error message ///< Return error message
}; };
} }

View file

@ -99,7 +99,7 @@ namespace Compiler
else if (t1=='f' || t2=='f') else if (t1=='f' || t2=='f')
mOperands.push_back ('f'); mOperands.push_back ('f');
else else
throw std::logic_error ("failed to determine result operand type"); throw std::logic_error ("Failed to determine result operand type");
} }
void ExprParser::pop() void ExprParser::pop()
@ -158,7 +158,7 @@ namespace Compiler
default: default:
throw std::logic_error ("unknown operator"); throw std::logic_error ("Unknown operator");
} }
} }
@ -287,7 +287,7 @@ namespace Compiler
else else
{ {
mExplicit.clear(); mExplicit.clear();
getErrorHandler().warning ("Ignoring stray explicit reference", loc); getErrorHandler().warning ("Stray explicit reference", loc);
} }
} }
@ -430,7 +430,7 @@ namespace Compiler
{ {
if (!hasExplicit) if (!hasExplicit)
{ {
getErrorHandler().warning ("ignoring stray explicit reference", loc); getErrorHandler().warning ("Stray explicit reference", loc);
mExplicit.clear(); mExplicit.clear();
} }
@ -735,13 +735,13 @@ namespace Compiler
{ {
if (mOperands.empty() && mOperators.empty()) if (mOperands.empty() && mOperators.empty())
{ {
getErrorHandler().error ("missing expression", mTokenLoc); getErrorHandler().error ("Missing expression", mTokenLoc);
return 'l'; return 'l';
} }
if (mNextOperand || mOperands.empty()) if (mNextOperand || mOperands.empty())
{ {
getErrorHandler().error ("syntax error in expression", mTokenLoc); getErrorHandler().error ("Syntax error in expression", mTokenLoc);
return 'l'; return 'l';
} }
@ -799,7 +799,7 @@ namespace Compiler
++optionalCount; ++optionalCount;
} }
else else
getErrorHandler().warning ("ignoring extra argument", getErrorHandler().warning ("Extra argument",
stringParser.getTokenLoc()); stringParser.getTokenLoc());
} }
else if (*iter=='X') else if (*iter=='X')
@ -813,7 +813,7 @@ namespace Compiler
if (parser.isEmpty()) if (parser.isEmpty())
break; break;
else else
getErrorHandler().warning("ignoring extra argument", parser.getTokenLoc()); getErrorHandler().warning("Extra argument", parser.getTokenLoc());
} }
else if (*iter=='z') else if (*iter=='z')
{ {
@ -825,7 +825,7 @@ namespace Compiler
if (discardParser.isEmpty()) if (discardParser.isEmpty())
break; break;
else else
getErrorHandler().warning("ignoring extra argument", discardParser.getTokenLoc()); getErrorHandler().warning("Extra argument", discardParser.getTokenLoc());
} }
else if (*iter=='j') else if (*iter=='j')
{ {

View file

@ -356,7 +356,7 @@ namespace Compiler
opcodePlaySound3DExplicit); opcodePlaySound3DExplicit);
extensions.registerInstruction ("playsound3dvp", "cff", opcodePlaySound3DVP, extensions.registerInstruction ("playsound3dvp", "cff", opcodePlaySound3DVP,
opcodePlaySound3DVPExplicit); opcodePlaySound3DVPExplicit);
extensions.registerInstruction ("playloopsound3d", "c", opcodePlayLoopSound3D, extensions.registerInstruction ("playloopsound3d", "cXX", opcodePlayLoopSound3D,
opcodePlayLoopSound3DExplicit); opcodePlayLoopSound3DExplicit);
extensions.registerInstruction ("playloopsound3dvp", "cff", opcodePlayLoopSound3DVP, extensions.registerInstruction ("playloopsound3dvp", "cff", opcodePlayLoopSound3DVP,
opcodePlayLoopSound3DVPExplicit); opcodePlayLoopSound3DVPExplicit);

View file

@ -98,7 +98,7 @@ namespace Compiler
if (mState == BeginState) if (mState == BeginState)
{ {
if (code != Scanner::S_newline) if (code != Scanner::S_newline)
reportWarning ("Ignoring stray special character before begin statement", loc); reportWarning ("Stray special character before begin statement", loc);
return true; return true;
} }

View file

@ -29,7 +29,7 @@ bool Compiler::JunkParser::parseName (const std::string& name, const TokenLoc& l
bool Compiler::JunkParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) bool Compiler::JunkParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner)
{ {
if (keyword==mIgnoreKeyword) if (keyword==mIgnoreKeyword)
reportWarning ("ignoring found junk", loc); reportWarning ("Ignoring found junk", loc);
else else
scanner.putbackKeyword (keyword, loc); scanner.putbackKeyword (keyword, loc);
@ -39,7 +39,7 @@ bool Compiler::JunkParser::parseKeyword (int keyword, const TokenLoc& loc, Scann
bool Compiler::JunkParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) bool Compiler::JunkParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
{ {
if (code==Scanner::S_member) if (code==Scanner::S_member)
reportWarning ("ignoring found junk", loc); reportWarning ("Ignoring found junk", loc);
else else
scanner.putbackSpecial (code, loc); scanner.putbackSpecial (code, loc);

View file

@ -48,7 +48,7 @@ namespace Compiler
default: default:
throw std::runtime_error ("unknown expression result type"); throw std::runtime_error ("Unknown expression result type");
} }
} }
@ -88,7 +88,7 @@ namespace Compiler
{ {
if (mState==PotentialEndState) if (mState==PotentialEndState)
{ {
getErrorHandler().warning ("ignoring stray string argument", loc); getErrorHandler().warning ("Stray string argument", loc);
mState = EndState; mState = EndState;
return true; return true;
} }
@ -132,7 +132,7 @@ namespace Compiler
return true; return true;
} }
getErrorHandler().error ("unknown variable", loc); getErrorHandler().error ("Unknown variable", loc);
SkipParser skip (getErrorHandler(), getContext()); SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip); scanner.scan (skip);
return false; return false;
@ -233,7 +233,7 @@ namespace Compiler
if (mState==SetPotentialMemberVarState && keyword==Scanner::K_to) if (mState==SetPotentialMemberVarState && keyword==Scanner::K_to)
{ {
getErrorHandler().warning ("unknown variable, ignoring set instruction", loc); getErrorHandler().warning ("Unknown variable", loc);
SkipParser skip (getErrorHandler(), getContext()); SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip); scanner.scan (skip);
return false; return false;
@ -286,7 +286,7 @@ namespace Compiler
{ {
if (!hasExplicit && mState==ExplicitState) if (!hasExplicit && mState==ExplicitState)
{ {
getErrorHandler().warning ("ignoring stray explicit reference", loc); getErrorHandler().warning ("Stray explicit reference", loc);
mExplicit.clear(); mExplicit.clear();
} }
@ -344,7 +344,7 @@ namespace Compiler
{ {
if (!hasExplicit && !mExplicit.empty()) if (!hasExplicit && !mExplicit.empty())
{ {
getErrorHandler().warning ("ignoring stray explicit reference", loc); getErrorHandler().warning ("Stray explicit reference", loc);
mExplicit.clear(); mExplicit.clear();
} }
@ -360,7 +360,7 @@ namespace Compiler
if (mState==ExplicitState) if (mState==ExplicitState)
{ {
// drop stray explicit reference // drop stray explicit reference
getErrorHandler().warning ("ignoring stray explicit reference", loc); getErrorHandler().warning ("Stray explicit reference", loc);
mState = BeginState; mState = BeginState;
mExplicit.clear(); mExplicit.clear();
} }
@ -375,8 +375,7 @@ namespace Compiler
{ {
if (!getContext().canDeclareLocals()) if (!getContext().canDeclareLocals())
{ {
getErrorHandler().error ( getErrorHandler().error("Local variables cannot be declared in this context", loc);
"local variables can't be declared in this context", loc);
SkipParser skip (getErrorHandler(), getContext()); SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip); scanner.scan (skip);
return true; return true;
@ -412,19 +411,19 @@ namespace Compiler
case Scanner::K_else: case Scanner::K_else:
getErrorHandler().warning ("ignoring stray else", loc); getErrorHandler().warning ("Stray else", loc);
mState = EndState; mState = EndState;
return true; return true;
case Scanner::K_endif: case Scanner::K_endif:
getErrorHandler().warning ("ignoring stray endif", loc); getErrorHandler().warning ("Stray endif", loc);
mState = EndState; mState = EndState;
return true; return true;
case Scanner::K_begin: case Scanner::K_begin:
getErrorHandler().warning ("ignoring stray begin", loc); getErrorHandler().warning ("Stray begin", loc);
mState = EndState; mState = EndState;
return true; return true;
} }
@ -491,7 +490,7 @@ namespace Compiler
{ {
if (mState==EndState && code==Scanner::S_open) if (mState==EndState && code==Scanner::S_open)
{ {
getErrorHandler().warning ("ignoring stray '[' or '(' at the end of the line", getErrorHandler().warning ("Stray '[' or '(' at the end of the line",
loc); loc);
return true; return true;
} }
@ -508,7 +507,7 @@ namespace Compiler
if (code==Scanner::S_ref && mState==SetPotentialMemberVarState) if (code==Scanner::S_ref && mState==SetPotentialMemberVarState)
{ {
getErrorHandler().warning ("Ignoring stray explicit reference", loc); getErrorHandler().warning ("Stray explicit reference", loc);
mState = SetState; mState = SetState;
return true; return true;
} }

View file

@ -18,7 +18,7 @@ namespace Compiler
case 'f': return mFloats; case 'f': return mFloats;
} }
throw std::logic_error ("unknown variable type"); throw std::logic_error ("Unknown variable type");
} }
int Locals::searchIndex (char type, const std::string& name) const int Locals::searchIndex (char type, const std::string& name) const
@ -48,7 +48,7 @@ namespace Compiler
case 'f': return mFloats; case 'f': return mFloats;
} }
throw std::logic_error ("unknown variable type"); throw std::logic_error ("Unknown variable type");
} }
char Locals::getType (const std::string& name) const char Locals::getType (const std::string& name) const

View file

@ -158,7 +158,7 @@ namespace Compiler
TokenLoc loc (mLoc); TokenLoc loc (mLoc);
mLoc.mLiteral.clear(); mLoc.mLiteral.clear();
mErrorHandler.error ("syntax error", loc); mErrorHandler.error ("Syntax error", loc);
throw SourceException(); throw SourceException();
} }
@ -521,7 +521,7 @@ namespace Compiler
else if (c == '<' || c == '>') // Treat <> and << as < else if (c == '<' || c == '>') // Treat <> and << as <
{ {
special = S_cmpLT; special = S_cmpLT;
mErrorHandler.warning (std::string("invalid operator <") + c + ", treating it as <", mLoc); mErrorHandler.warning ("Invalid operator, treating it as <", mLoc);
} }
else else
{ {
@ -549,7 +549,7 @@ namespace Compiler
else if (c == '<' || c == '>') // Treat >< and >> as > else if (c == '<' || c == '>') // Treat >< and >> as >
{ {
special = S_cmpGT; special = S_cmpGT;
mErrorHandler.warning (std::string("invalid operator >") + c + ", treating it as >", mLoc); mErrorHandler.warning ("Invalid operator, treating it as >", mLoc);
} }
else else
{ {

View file

@ -5,6 +5,8 @@
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <osg/Stats>
namespace namespace
{ {
using DetourNavigator::ChangeType; using DetourNavigator::ChangeType;
@ -102,6 +104,20 @@ namespace DetourNavigator
mDone.wait(lock, [&] { return mJobs.empty(); }); mDone.wait(lock, [&] { return mJobs.empty(); });
} }
void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{
std::size_t jobs = 0;
{
const std::lock_guard<std::mutex> lock(mMutex);
jobs = mJobs.size();
}
stats.setAttribute(frameNumber, "NavMesh UpdateJobs", jobs);
mNavMeshTilesCache.reportStats(frameNumber, stats);
}
void AsyncNavMeshUpdater::process() throw() void AsyncNavMeshUpdater::process() throw()
{ {
log("start process jobs"); log("start process jobs");
@ -129,12 +145,17 @@ namespace DetourNavigator
const auto firstStart = setFirstStart(start); const auto firstStart = setFirstStart(start);
const auto navMeshCacheItem = job.mNavMeshCacheItem.lock();
if (!navMeshCacheItem)
return true;
const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile); const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile);
const auto playerTile = *mPlayerTile.lockConst(); const auto playerTile = *mPlayerTile.lockConst();
const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile);
const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile, const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile,
offMeshConnections, mSettings, job.mNavMeshCacheItem, mNavMeshTilesCache); offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache);
const auto finish = std::chrono::steady_clock::now(); const auto finish = std::chrono::steady_clock::now();
@ -143,7 +164,7 @@ namespace DetourNavigator
using FloatMs = std::chrono::duration<float, std::milli>; using FloatMs = std::chrono::duration<float, std::milli>;
{ {
const auto locked = job.mNavMeshCacheItem.lockConst(); const auto locked = navMeshCacheItem->lockConst();
log("cache updated for agent=", job.mAgentHalfExtents, " status=", status, log("cache updated for agent=", job.mAgentHalfExtents, " status=", status,
" generation=", locked->getGeneration(), " generation=", locked->getGeneration(),
" revision=", locked->getNavMeshRevision(), " revision=", locked->getNavMeshRevision(),
@ -157,9 +178,7 @@ namespace DetourNavigator
boost::optional<AsyncNavMeshUpdater::Job> AsyncNavMeshUpdater::getNextJob() boost::optional<AsyncNavMeshUpdater::Job> AsyncNavMeshUpdater::getNextJob()
{ {
std::unique_lock<std::mutex> lock(mMutex); std::unique_lock<std::mutex> lock(mMutex);
if (mJobs.empty()) if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), [&] { return !mJobs.empty(); }))
mHasJob.wait_for(lock, std::chrono::milliseconds(10));
if (mJobs.empty())
{ {
mFirstStart.lock()->reset(); mFirstStart.lock()->reset();
mDone.notify_all(); mDone.notify_all();
@ -194,7 +213,8 @@ namespace DetourNavigator
writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x()) writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x())
+ "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision); + "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision);
if (mSettings.get().mEnableWriteNavMeshToFile) if (mSettings.get().mEnableWriteNavMeshToFile)
writeToFile(job.mNavMeshCacheItem.lockConst()->getValue(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); if (const auto shared = job.mNavMeshCacheItem.lock())
writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision);
} }
std::chrono::steady_clock::time_point AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value) std::chrono::steady_clock::time_point AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value)

View file

@ -44,11 +44,13 @@ namespace DetourNavigator
void wait(); void wait();
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
private: private:
struct Job struct Job
{ {
osg::Vec3f mAgentHalfExtents; osg::Vec3f mAgentHalfExtents;
SharedNavMeshCacheItem mNavMeshCacheItem; std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem;
TilePosition mChangedTile; TilePosition mChangedTile;
unsigned mTryNumber; unsigned mTryNumber;
ChangeType mChangeType; ChangeType mChangeType;
@ -72,7 +74,7 @@ namespace DetourNavigator
std::reference_wrapper<TileCachedRecastMeshManager> mRecastMeshManager; std::reference_wrapper<TileCachedRecastMeshManager> mRecastMeshManager;
std::reference_wrapper<OffMeshConnectionsManager> mOffMeshConnectionsManager; std::reference_wrapper<OffMeshConnectionsManager> mOffMeshConnectionsManager;
std::atomic_bool mShouldStop; std::atomic_bool mShouldStop;
std::mutex mMutex; mutable std::mutex mMutex;
std::condition_variable mHasJob; std::condition_variable mHasJob;
std::condition_variable mDone; std::condition_variable mDone;
Jobs mJobs; Jobs mJobs;

View file

@ -25,8 +25,6 @@ namespace
{ {
using namespace DetourNavigator; using namespace DetourNavigator;
static const int doNotTransferOwnership = 0;
void initPolyMeshDetail(rcPolyMeshDetail& value) void initPolyMeshDetail(rcPolyMeshDetail& value)
{ {
value.meshes = nullptr; value.meshes = nullptr;
@ -441,56 +439,7 @@ namespace
return NavMeshData(navMeshData, navMeshDataSize); return NavMeshData(navMeshData, navMeshDataSize);
} }
class UpdateNavMeshStatusBuilder
{
public:
UpdateNavMeshStatusBuilder() = default;
UpdateNavMeshStatusBuilder removed(bool value)
{
if (value)
set(UpdateNavMeshStatus::removed);
else
unset(UpdateNavMeshStatus::removed);
return *this;
}
UpdateNavMeshStatusBuilder added(bool value)
{
if (value)
set(UpdateNavMeshStatus::added);
else
unset(UpdateNavMeshStatus::added);
return *this;
}
UpdateNavMeshStatusBuilder failed(bool value)
{
if (value)
set(UpdateNavMeshStatus::failed);
else
unset(UpdateNavMeshStatus::failed);
return *this;
}
UpdateNavMeshStatus getResult() const
{
return mResult;
}
private:
UpdateNavMeshStatus mResult = UpdateNavMeshStatus::ignored;
void set(UpdateNavMeshStatus value)
{
mResult = static_cast<UpdateNavMeshStatus>(static_cast<unsigned>(mResult) | static_cast<unsigned>(value));
}
void unset(UpdateNavMeshStatus value)
{
mResult = static_cast<UpdateNavMeshStatus>(static_cast<unsigned>(mResult) & ~static_cast<unsigned>(value));
}
};
template <class T> template <class T>
unsigned long getMinValuableBitsNumber(const T value) unsigned long getMinValuableBitsNumber(const T value)
@ -500,49 +449,6 @@ namespace
++power; ++power;
return power; return power;
} }
dtStatus addTile(dtNavMesh& navMesh, const NavMeshData& navMeshData)
{
const dtTileRef lastRef = 0;
dtTileRef* const result = nullptr;
return navMesh.addTile(navMeshData.mValue.get(), navMeshData.mSize,
doNotTransferOwnership, lastRef, result);
}
dtStatus addTile(dtNavMesh& navMesh, const NavMeshTilesCache::Value& cachedNavMeshData)
{
const dtTileRef lastRef = 0;
dtTileRef* const result = nullptr;
return navMesh.addTile(cachedNavMeshData.get().mValue, cachedNavMeshData.get().mSize,
doNotTransferOwnership, lastRef, result);
}
template <class T>
UpdateNavMeshStatus replaceTile(const SharedNavMeshCacheItem& navMeshCacheItem,
const TilePosition& changedTile, T&& navMeshData)
{
const auto locked = navMeshCacheItem.lock();
auto& navMesh = locked->getValue();
const int layer = 0;
const auto tileRef = navMesh.getTileRefAt(changedTile.x(), changedTile.y(), layer);
unsigned char** const data = nullptr;
int* const dataSize = nullptr;
const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, data, dataSize));
const auto addStatus = addTile(navMesh, navMeshData);
if (dtStatusSucceed(addStatus))
{
locked->setUsedTile(changedTile, std::forward<T>(navMeshData));
return UpdateNavMeshStatusBuilder().added(true).removed(removed).getResult();
}
else
{
if (removed)
locked->removeUsedTile(changedTile);
log("failed to add tile with status=", WriteDtStatus {addStatus});
return UpdateNavMeshStatusBuilder().removed(removed).failed((addStatus & DT_OUT_OF_MEMORY) != 0).getResult();
}
}
} }
namespace DetourNavigator namespace DetourNavigator
@ -591,26 +497,13 @@ namespace DetourNavigator
" playerTile=", playerTile, " playerTile=", playerTile,
" changedTileDistance=", getDistance(changedTile, playerTile)); " changedTileDistance=", getDistance(changedTile, playerTile));
const auto params = *navMeshCacheItem.lockConst()->getValue().getParams(); const auto params = *navMeshCacheItem->lockConst()->getImpl().getParams();
const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]); const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]);
const auto x = changedTile.x();
const auto y = changedTile.y();
const auto removeTile = [&] {
const auto locked = navMeshCacheItem.lock();
auto& navMesh = locked->getValue();
const auto tileRef = navMesh.getTileRefAt(x, y, 0);
const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr));
if (removed)
locked->removeUsedTile(changedTile);
return UpdateNavMeshStatusBuilder().removed(removed).getResult();
};
if (!recastMesh) if (!recastMesh)
{ {
log("ignore add tile: recastMesh is null"); log("ignore add tile: recastMesh is null");
return removeTile(); return navMeshCacheItem->lock()->removeTile(changedTile);
} }
auto recastMeshBounds = recastMesh->getBounds(); auto recastMeshBounds = recastMesh->getBounds();
@ -625,13 +518,13 @@ namespace DetourNavigator
if (isEmpty(recastMeshBounds)) if (isEmpty(recastMeshBounds))
{ {
log("ignore add tile: recastMesh is empty"); log("ignore add tile: recastMesh is empty");
return removeTile(); return navMeshCacheItem->lock()->removeTile(changedTile);
} }
if (!shouldAddTile(changedTile, playerTile, std::min(settings.mMaxTilesNumber, params.maxTiles))) if (!shouldAddTile(changedTile, playerTile, std::min(settings.mMaxTilesNumber, params.maxTiles)))
{ {
log("ignore add tile: too far from player"); log("ignore add tile: too far from player");
return removeTile(); return navMeshCacheItem->lock()->removeTile(changedTile);
} }
auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections); auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections);
@ -648,7 +541,7 @@ namespace DetourNavigator
if (!navMeshData.mValue) if (!navMeshData.mValue)
{ {
log("ignore add tile: NavMeshData is null"); log("ignore add tile: NavMeshData is null");
return removeTile(); return navMeshCacheItem->lock()->removeTile(changedTile);
} }
try try
@ -665,10 +558,10 @@ namespace DetourNavigator
if (!cachedNavMeshData) if (!cachedNavMeshData)
{ {
log("cache overflow"); log("cache overflow");
return replaceTile(navMeshCacheItem, changedTile, std::move(navMeshData)); return navMeshCacheItem->lock()->updateTile(changedTile, std::move(navMeshData));
} }
} }
return replaceTile(navMeshCacheItem, changedTile, std::move(cachedNavMeshData)); return navMeshCacheItem->lock()->updateTile(changedTile, std::move(cachedNavMeshData));
} }
} }

View file

@ -20,21 +20,6 @@ namespace DetourNavigator
class RecastMesh; class RecastMesh;
struct Settings; struct Settings;
enum class UpdateNavMeshStatus : unsigned
{
ignored = 0,
removed = 1 << 0,
added = 1 << 1,
replaced = removed | added,
failed = 1 << 2,
lost = removed | failed,
};
inline bool isSuccess(UpdateNavMeshStatus value)
{
return (static_cast<unsigned>(value) & static_cast<unsigned>(UpdateNavMeshStatus::failed)) == 0;
}
inline float getLength(const osg::Vec2i& value) inline float getLength(const osg::Vec2i& value)
{ {
return std::sqrt(float(osg::square(value.x()) + osg::square(value.y()))); return std::sqrt(float(osg::square(value.x()) + osg::square(value.y())));

View file

@ -174,7 +174,7 @@ namespace DetourNavigator
if (!navMesh) if (!navMesh)
return out; return out;
const auto settings = getSettings(); const auto settings = getSettings();
return findSmoothPath(navMesh.lock()->getValue(), toNavMeshCoordinates(settings, agentHalfExtents), return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents),
toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start), toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start),
toNavMeshCoordinates(settings, end), includeFlags, settings, out); toNavMeshCoordinates(settings, end), includeFlags, settings, out);
} }
@ -191,7 +191,9 @@ namespace DetourNavigator
*/ */
virtual std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const = 0; virtual std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const = 0;
virtual Settings getSettings() const = 0; virtual const Settings& getSettings() const = 0;
virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0;
}; };
} }

View file

@ -21,10 +21,10 @@ namespace DetourNavigator
void NavigatorImpl::removeAgent(const osg::Vec3f& agentHalfExtents) void NavigatorImpl::removeAgent(const osg::Vec3f& agentHalfExtents)
{ {
const auto it = mAgents.find(agentHalfExtents); const auto it = mAgents.find(agentHalfExtents);
if (it == mAgents.end() || --it->second) if (it == mAgents.end())
return; return;
mAgents.erase(it); if (it->second > 0)
mNavMeshManager.reset(agentHalfExtents); --it->second;
} }
bool NavigatorImpl::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) bool NavigatorImpl::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform)
@ -113,6 +113,7 @@ namespace DetourNavigator
void NavigatorImpl::update(const osg::Vec3f& playerPosition) void NavigatorImpl::update(const osg::Vec3f& playerPosition)
{ {
removeUnusedNavMeshes();
for (const auto& v : mAgents) for (const auto& v : mAgents)
mNavMeshManager.update(playerPosition, v.first); mNavMeshManager.update(playerPosition, v.first);
} }
@ -132,11 +133,16 @@ namespace DetourNavigator
return mNavMeshManager.getNavMeshes(); return mNavMeshManager.getNavMeshes();
} }
Settings NavigatorImpl::getSettings() const const Settings& NavigatorImpl::getSettings() const
{ {
return mSettings; return mSettings;
} }
void NavigatorImpl::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{
mNavMeshManager.reportStats(frameNumber, stats);
}
void NavigatorImpl::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId) void NavigatorImpl::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId)
{ {
updateId(id, avoidId, mWaterIds); updateId(id, avoidId, mWaterIds);
@ -156,4 +162,15 @@ namespace DetourNavigator
inserted.first->second = updateId; inserted.first->second = updateId;
} }
} }
void NavigatorImpl::removeUnusedNavMeshes()
{
for (auto it = mAgents.begin(); it != mAgents.end();)
{
if (it->second == 0 && mNavMeshManager.reset(it->first))
it = mAgents.erase(it);
else
++it;
}
}
} }

View file

@ -46,7 +46,9 @@ namespace DetourNavigator
std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const override; std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const override;
Settings getSettings() const override; const Settings& getSettings() const override;
void reportStats(unsigned int frameNumber, osg::Stats& stats) const override;
private: private:
Settings mSettings; Settings mSettings;
@ -58,6 +60,7 @@ namespace DetourNavigator
void updateAvoidShapeId(const ObjectId id, const ObjectId avoidId); void updateAvoidShapeId(const ObjectId id, const ObjectId avoidId);
void updateWaterShapeId(const ObjectId id, const ObjectId waterId); void updateWaterShapeId(const ObjectId id, const ObjectId waterId);
void updateId(const ObjectId id, const ObjectId waterId, std::unordered_map<ObjectId, ObjectId>& ids); void updateId(const ObjectId id, const ObjectId waterId, std::unordered_map<ObjectId, ObjectId>& ids);
void removeUnusedNavMeshes();
}; };
} }

View file

@ -5,8 +5,9 @@
namespace DetourNavigator namespace DetourNavigator
{ {
struct NavigatorStub final : public Navigator class NavigatorStub final : public Navigator
{ {
public:
NavigatorStub() = default; NavigatorStub() = default;
void addAgent(const osg::Vec3f& /*agentHalfExtents*/) override {} void addAgent(const osg::Vec3f& /*agentHalfExtents*/) override {}
@ -65,7 +66,7 @@ namespace DetourNavigator
SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& /*agentHalfExtents*/) const override SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& /*agentHalfExtents*/) const override
{ {
return SharedNavMeshCacheItem(); return mEmptyNavMeshCacheItem;
} }
std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const override std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const override
@ -73,10 +74,16 @@ namespace DetourNavigator
return std::map<osg::Vec3f, SharedNavMeshCacheItem>(); return std::map<osg::Vec3f, SharedNavMeshCacheItem>();
} }
Settings getSettings() const override const Settings& getSettings() const override
{ {
return Settings {}; return mDefaultSettings;
} }
void reportStats(unsigned int /*frameNumber*/, osg::Stats& /*stats*/) const override {}
private:
Settings mDefaultSettings {};
SharedNavMeshCacheItem mEmptyNavMeshCacheItem;
}; };
} }

View file

@ -4,29 +4,113 @@
#include "sharednavmesh.hpp" #include "sharednavmesh.hpp"
#include "tileposition.hpp" #include "tileposition.hpp"
#include "navmeshtilescache.hpp" #include "navmeshtilescache.hpp"
#include "dtstatus.hpp"
#include <components/misc/guarded.hpp> #include <components/misc/guarded.hpp>
#include <DetourNavMesh.h>
#include <map> #include <map>
namespace DetourNavigator namespace DetourNavigator
{ {
enum class UpdateNavMeshStatus : unsigned
{
ignored = 0,
removed = 1 << 0,
added = 1 << 1,
replaced = removed | added,
failed = 1 << 2,
lost = removed | failed,
};
inline bool isSuccess(UpdateNavMeshStatus value)
{
return (static_cast<unsigned>(value) & static_cast<unsigned>(UpdateNavMeshStatus::failed)) == 0;
}
class UpdateNavMeshStatusBuilder
{
public:
UpdateNavMeshStatusBuilder() = default;
UpdateNavMeshStatusBuilder removed(bool value)
{
if (value)
set(UpdateNavMeshStatus::removed);
else
unset(UpdateNavMeshStatus::removed);
return *this;
}
UpdateNavMeshStatusBuilder added(bool value)
{
if (value)
set(UpdateNavMeshStatus::added);
else
unset(UpdateNavMeshStatus::added);
return *this;
}
UpdateNavMeshStatusBuilder failed(bool value)
{
if (value)
set(UpdateNavMeshStatus::failed);
else
unset(UpdateNavMeshStatus::failed);
return *this;
}
UpdateNavMeshStatus getResult() const
{
return mResult;
}
private:
UpdateNavMeshStatus mResult = UpdateNavMeshStatus::ignored;
void set(UpdateNavMeshStatus value)
{
mResult = static_cast<UpdateNavMeshStatus>(static_cast<unsigned>(mResult) | static_cast<unsigned>(value));
}
void unset(UpdateNavMeshStatus value)
{
mResult = static_cast<UpdateNavMeshStatus>(static_cast<unsigned>(mResult) & ~static_cast<unsigned>(value));
}
};
inline unsigned char* getRawData(NavMeshData& navMeshData)
{
return navMeshData.mValue.get();
}
inline unsigned char* getRawData(NavMeshTilesCache::Value& cachedNavMeshData)
{
return cachedNavMeshData.get().mValue;
}
inline int getSize(const NavMeshData& navMeshData)
{
return navMeshData.mSize;
}
inline int getSize(const NavMeshTilesCache::Value& cachedNavMeshData)
{
return cachedNavMeshData.get().mSize;
}
class NavMeshCacheItem class NavMeshCacheItem
{ {
public: public:
NavMeshCacheItem(const NavMeshPtr& value, std::size_t generation) NavMeshCacheItem(const NavMeshPtr& impl, std::size_t generation)
: mValue(value), mGeneration(generation), mNavMeshRevision(0) : mImpl(impl), mGeneration(generation), mNavMeshRevision(0)
{ {
} }
const dtNavMesh& getValue() const const dtNavMesh& getImpl() const
{ {
return *mValue; return *mImpl;
}
dtNavMesh& getValue()
{
return *mValue;
} }
std::size_t getGeneration() const std::size_t getGeneration() const
@ -39,6 +123,38 @@ namespace DetourNavigator
return mNavMeshRevision; return mNavMeshRevision;
} }
template <class T>
UpdateNavMeshStatus updateTile(const TilePosition& position, T&& navMeshData)
{
const auto removed = removeTileImpl(position);
const auto addStatus = addTileImpl(getRawData(navMeshData), getSize(navMeshData));
if (dtStatusSucceed(addStatus))
{
setUsedTile(position, std::forward<T>(navMeshData));
return UpdateNavMeshStatusBuilder().added(true).removed(removed).getResult();
}
else
{
if (removed)
removeUsedTile(position);
return UpdateNavMeshStatusBuilder().removed(removed).failed((addStatus & DT_OUT_OF_MEMORY) != 0).getResult();
}
}
UpdateNavMeshStatus removeTile(const TilePosition& position)
{
const auto removed = dtStatusSucceed(removeTileImpl(position));
if (removed)
removeUsedTile(position);
return UpdateNavMeshStatusBuilder().removed(removed).getResult();
}
private:
NavMeshPtr mImpl;
std::size_t mGeneration;
std::size_t mNavMeshRevision;
std::map<TilePosition, std::pair<NavMeshTilesCache::Value, NavMeshData>> mUsedTiles;
void setUsedTile(const TilePosition& tilePosition, NavMeshTilesCache::Value value) void setUsedTile(const TilePosition& tilePosition, NavMeshTilesCache::Value value)
{ {
mUsedTiles[tilePosition] = std::make_pair(std::move(value), NavMeshData()); mUsedTiles[tilePosition] = std::make_pair(std::move(value), NavMeshData());
@ -57,14 +173,26 @@ namespace DetourNavigator
++mNavMeshRevision; ++mNavMeshRevision;
} }
private: dtStatus addTileImpl(unsigned char* data, int size)
NavMeshPtr mValue; {
std::size_t mGeneration; const int doNotTransferOwnership = 0;
std::size_t mNavMeshRevision; const dtTileRef lastRef = 0;
std::map<TilePosition, std::pair<NavMeshTilesCache::Value, NavMeshData>> mUsedTiles; dtTileRef* const result = nullptr;
return mImpl->addTile(data, size, doNotTransferOwnership, lastRef, result);
}
dtStatus removeTileImpl(const TilePosition& position)
{
const int layer = 0;
const auto tileRef = mImpl->getTileRefAt(position.x(), position.y(), layer);
unsigned char** const data = nullptr;
int* const dataSize = nullptr;
return mImpl->removeTile(tileRef, data, dataSize);
}
}; };
using SharedNavMeshCacheItem = Misc::SharedGuarded<NavMeshCacheItem>; using GuardedNavMeshCacheItem = Misc::ScopeGuarded<NavMeshCacheItem>;
using SharedNavMeshCacheItem = std::shared_ptr<GuardedNavMeshCacheItem>;
} }
#endif #endif

View file

@ -16,6 +16,21 @@ namespace
{ {
return current == add ? current : ChangeType::mixed; return current == add ? current : ChangeType::mixed;
} }
/// Safely reset shared_ptr with definite underlying object destrutor call.
/// Assuming there is another thread holding copy of this shared_ptr or weak_ptr to this shared_ptr.
template <class T>
bool resetIfUnique(std::shared_ptr<T>& ptr)
{
const std::weak_ptr<T> weak(ptr);
ptr.reset();
if (auto shared = weak.lock())
{
ptr = std::move(shared);
return false;
}
return true;
}
} }
namespace DetourNavigator namespace DetourNavigator
@ -79,13 +94,22 @@ namespace DetourNavigator
if (cached != mCache.end()) if (cached != mCache.end())
return; return;
mCache.insert(std::make_pair(agentHalfExtents, mCache.insert(std::make_pair(agentHalfExtents,
std::make_shared<NavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter))); std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter)));
log("cache add for agent=", agentHalfExtents); log("cache add for agent=", agentHalfExtents);
} }
void NavMeshManager::reset(const osg::Vec3f& agentHalfExtents) bool NavMeshManager::reset(const osg::Vec3f& agentHalfExtents)
{ {
const auto it = mCache.find(agentHalfExtents);
if (it == mCache.end())
return true;
if (!resetIfUnique(it->second))
return false;
mCache.erase(agentHalfExtents); mCache.erase(agentHalfExtents);
mChangedTiles.erase(agentHalfExtents);
mPlayerTile.erase(agentHalfExtents);
mLastRecastMeshManagerRevision.erase(agentHalfExtents);
return true;
} }
void NavMeshManager::addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end) void NavMeshManager::addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end)
@ -139,8 +163,8 @@ namespace DetourNavigator
} }
const auto changedTiles = mChangedTiles.find(agentHalfExtents); const auto changedTiles = mChangedTiles.find(agentHalfExtents);
{ {
const auto locked = cached.lock(); const auto locked = cached->lockConst();
const auto& navMesh = locked->getValue(); const auto& navMesh = locked->getImpl();
if (changedTiles != mChangedTiles.end()) if (changedTiles != mChangedTiles.end())
{ {
for (const auto& tile : changedTiles->second) for (const auto& tile : changedTiles->second)
@ -152,10 +176,6 @@ namespace DetourNavigator
else else
tileToPost->second = addChangeType(tileToPost->second, tile.second); tileToPost->second = addChangeType(tileToPost->second, tile.second);
} }
for (const auto& tile : tilesToPost)
changedTiles->second.erase(tile.first);
if (changedTiles->second.empty())
mChangedTiles.erase(changedTiles);
} }
const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles);
mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile) mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile)
@ -171,6 +191,8 @@ namespace DetourNavigator
}); });
} }
mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost);
if (changedTiles != mChangedTiles.end())
changedTiles->second.clear();
log("cache update posted for agent=", agentHalfExtents, log("cache update posted for agent=", agentHalfExtents,
" playerTile=", lastPlayerTile->second, " playerTile=", lastPlayerTile->second,
" recastMeshManagerRevision=", lastRevision); " recastMeshManagerRevision=", lastRevision);
@ -191,6 +213,11 @@ namespace DetourNavigator
return mCache; return mCache;
} }
void NavMeshManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{
mAsyncNavMeshUpdater.reportStats(frameNumber, stats);
}
void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform,
const ChangeType changeType) const ChangeType changeType)
{ {

View file

@ -36,7 +36,7 @@ namespace DetourNavigator
bool removeWater(const osg::Vec2i& cellPosition); bool removeWater(const osg::Vec2i& cellPosition);
void reset(const osg::Vec3f& agentHalfExtents); bool reset(const osg::Vec3f& agentHalfExtents);
void addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end); void addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end);
@ -50,6 +50,8 @@ namespace DetourNavigator
std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const; std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const;
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
private: private:
const Settings& mSettings; const Settings& mSettings;
TileCachedRecastMeshManager mRecastMeshManager; TileCachedRecastMeshManager mRecastMeshManager;

View file

@ -1,6 +1,10 @@
#include "navmeshtilescache.hpp" #include "navmeshtilescache.hpp"
#include "exceptions.hpp" #include "exceptions.hpp"
#include <osg/Stats>
#include <cstring>
namespace DetourNavigator namespace DetourNavigator
{ {
namespace namespace
@ -61,8 +65,7 @@ namespace DetourNavigator
if (tileValues == agentValues->second.end()) if (tileValues == agentValues->second.end())
return Value(); return Value();
// TODO: use different function to make key to avoid unnecessary std::string allocation const auto tile = tileValues->second.mMap.find(RecastMeshKeyView(recastMesh, offMeshConnections));
const auto tile = tileValues->second.mMap.find(makeNavMeshKey(recastMesh, offMeshConnections));
if (tile == tileValues->second.mMap.end()) if (tile == tileValues->second.mMap.end())
return Value(); return Value();
@ -85,7 +88,7 @@ namespace DetourNavigator
if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize))
return Value(); return Value();
const auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections); auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections);
const auto itemSize = navMeshSize + 2 * navMeshKey.size(); const auto itemSize = navMeshSize + 2 * navMeshKey.size();
if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize))
@ -94,9 +97,8 @@ namespace DetourNavigator
while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize) while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize)
removeLeastRecentlyUsed(); removeLeastRecentlyUsed();
const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, navMeshKey); const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey));
// TODO: use std::string_view or some alternative to avoid navMeshKey copy into both mFreeItems and mValues const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(iterator->mNavMeshKey, iterator);
const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(navMeshKey, iterator);
if (!emplaced.second) if (!emplaced.second)
{ {
@ -113,6 +115,24 @@ namespace DetourNavigator
return Value(*this, iterator); return Value(*this, iterator);
} }
void NavMeshTilesCache::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{
std::size_t navMeshCacheSize = 0;
std::size_t usedNavMeshTiles = 0;
std::size_t cachedNavMeshTiles = 0;
{
const std::lock_guard<std::mutex> lock(mMutex);
navMeshCacheSize = mUsedNavMeshDataSize;
usedNavMeshTiles = mBusyItems.size();
cachedNavMeshTiles = mFreeItems.size();
}
stats.setAttribute(frameNumber, "NavMesh CacheSize", navMeshCacheSize);
stats.setAttribute(frameNumber, "NavMesh UsedTiles", usedNavMeshTiles);
stats.setAttribute(frameNumber, "NavMesh CachedTiles", cachedNavMeshTiles);
}
void NavMeshTilesCache::removeLeastRecentlyUsed() void NavMeshTilesCache::removeLeastRecentlyUsed()
{ {
const auto& item = mFreeItems.back(); const auto& item = mFreeItems.back();
@ -131,9 +151,10 @@ namespace DetourNavigator
mUsedNavMeshDataSize -= getSize(item); mUsedNavMeshDataSize -= getSize(item);
mFreeNavMeshDataSize -= getSize(item); mFreeNavMeshDataSize -= getSize(item);
mFreeItems.pop_back();
tileValues->second.mMap.erase(value); tileValues->second.mMap.erase(value);
mFreeItems.pop_back();
if (!tileValues->second.mMap.empty()) if (!tileValues->second.mMap.empty())
return; return;
@ -163,4 +184,69 @@ namespace DetourNavigator
mFreeItems.splice(mFreeItems.begin(), mBusyItems, iterator); mFreeItems.splice(mFreeItems.begin(), mBusyItems, iterator);
mFreeNavMeshDataSize += getSize(*iterator); mFreeNavMeshDataSize += getSize(*iterator);
} }
namespace
{
struct CompareBytes
{
const char* mRhsIt;
const char* mRhsEnd;
template <class T>
int operator ()(const std::vector<T>& lhs)
{
const auto lhsBegin = reinterpret_cast<const char*>(lhs.data());
const auto lhsEnd = reinterpret_cast<const char*>(lhs.data() + lhs.size());
const auto lhsSize = static_cast<std::ptrdiff_t>(lhsEnd - lhsBegin);
const auto rhsSize = static_cast<std::ptrdiff_t>(mRhsEnd - mRhsIt);
if (lhsBegin == nullptr || mRhsIt == nullptr)
{
if (lhsSize < rhsSize)
return -1;
else if (lhsSize > rhsSize)
return 1;
else
return 0;
}
const auto size = std::min(lhsSize, rhsSize);
if (const auto result = std::memcmp(lhsBegin, mRhsIt, size))
return result;
if (lhsSize > rhsSize)
return 1;
mRhsIt += size;
return 0;
}
};
}
int NavMeshTilesCache::RecastMeshKeyView::compare(const std::string& other) const
{
CompareBytes compareBytes {other.data(), other.data() + other.size()};
if (const auto result = compareBytes(mRecastMesh.get().getIndices()))
return result;
if (const auto result = compareBytes(mRecastMesh.get().getVertices()))
return result;
if (const auto result = compareBytes(mRecastMesh.get().getAreaTypes()))
return result;
if (const auto result = compareBytes(mRecastMesh.get().getWater()))
return result;
if (const auto result = compareBytes(mOffMeshConnections.get()))
return result;
if (compareBytes.mRhsIt < compareBytes.mRhsEnd)
return -1;
return 0;
}
} }

View file

@ -10,6 +10,12 @@
#include <map> #include <map>
#include <list> #include <list>
#include <mutex> #include <mutex>
#include <cassert>
namespace osg
{
class Stats;
}
namespace DetourNavigator namespace DetourNavigator
{ {
@ -104,14 +110,68 @@ namespace DetourNavigator
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections, const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections,
NavMeshData&& value); NavMeshData&& value);
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
private: private:
class KeyView
{
public:
KeyView() = default;
KeyView(const std::string& value)
: mValue(&value) {}
const std::string& getValue() const
{
assert(mValue);
return *mValue;
}
virtual int compare(const std::string& other) const
{
assert(mValue);
return mValue->compare(other);
}
virtual bool isLess(const KeyView& other) const
{
assert(mValue);
return other.compare(*mValue) > 0;
}
friend bool operator <(const KeyView& lhs, const KeyView& rhs)
{
return lhs.isLess(rhs);
}
private:
const std::string* mValue = nullptr;
};
class RecastMeshKeyView : public KeyView
{
public:
RecastMeshKeyView(const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections)
: mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {}
int compare(const std::string& other) const override;
bool isLess(const KeyView& other) const override
{
return compare(other.getValue()) < 0;
}
private:
std::reference_wrapper<const RecastMesh> mRecastMesh;
std::reference_wrapper<const std::vector<OffMeshConnection>> mOffMeshConnections;
};
struct TileMap struct TileMap
{ {
std::map<std::string, ItemIterator> mMap; std::map<KeyView, ItemIterator> mMap;
}; };
std::mutex mMutex; mutable std::mutex mMutex;
std::size_t mMaxNavMeshDataSize; std::size_t mMaxNavMeshDataSize;
std::size_t mUsedNavMeshDataSize; std::size_t mUsedNavMeshDataSize;
std::size_t mFreeNavMeshDataSize; std::size_t mFreeNavMeshDataSize;

View file

@ -61,8 +61,8 @@ namespace DetourNavigator
for (const auto& tile : currentTiles) for (const auto& tile : currentTiles)
if (!newTiles.count(tile) && removeTile(id, tile, tiles.get())) if (!newTiles.count(tile) && removeTile(id, tile, tiles.get()))
changedTiles.push_back(tile); changedTiles.push_back(tile);
std::swap(currentTiles, newTiles);
} }
std::swap(currentTiles, newTiles);
if (!changedTiles.empty()) if (!changedTiles.empty())
++mRevision; ++mRevision;
return changedTiles; return changedTiles;

View file

@ -127,7 +127,7 @@ std::string ESMReader::getHString()
// them. For some reason, they break the rules, and contain a byte // them. For some reason, they break the rules, and contain a byte
// (value 0) even if the header says there is no data. If // (value 0) even if the header says there is no data. If
// Morrowind accepts it, so should we. // Morrowind accepts it, so should we.
if (mCtx.leftSub == 0) if (mCtx.leftSub == 0 && mCtx.leftRec != 0)
{ {
// Skip the following zero byte // Skip the following zero byte
mCtx.leftRec--; mCtx.leftRec--;

View file

@ -11,18 +11,9 @@ class ESMWriter;
/* /*
* Texture used for texturing landscape. * Texture used for texturing landscape.
* * They are indexed by 'num', but still use 'id' to override base records.
* They are probably indexed by 'num', not 'id', but I don't know for * Original editor even does not allow to create new records with existing ID's.
* sure. And num is not unique between files, so one option is to keep * TODO: currently OpenMW-CS does not allow to override LTEX records at all.
* a separate list for each input file (that has LTEX records, of
* course.) We also need to resolve references to already existing
* land textures to save space.
* I'm not sure if it is even possible to override existing land
* textures, probably not. I'll have to try it, and have to mimic the
* behaviour of morrowind. First, check what you are allowed to do in
* the editor. Then make an esp which changes a commonly used land
* texture, and see if it affects the game.
*/ */
struct LandTexture struct LandTexture

View file

@ -104,7 +104,7 @@ namespace ESM
} }
mScriptData.resize(subSize); mScriptData.resize(subSize);
esm.getExact(&mScriptData[0], mScriptData.size()); esm.getExact(mScriptData.data(), mScriptData.size());
break; break;
} }
case ESM::FourCC<'S','C','T','X'>::value: case ESM::FourCC<'S','C','T','X'>::value:
@ -156,7 +156,7 @@ namespace ESM
} }
esm.startSubRecord("SCDT"); esm.startSubRecord("SCDT");
esm.write(reinterpret_cast<const char * >(&mScriptData[0]), mData.mScriptDataSize); esm.write(reinterpret_cast<const char *>(mScriptData.data()), mData.mScriptDataSize);
esm.endRecord("SCDT"); esm.endRecord("SCDT");
esm.writeHNOString("SCTX", mScriptText); esm.writeHNOString("SCTX", mScriptText);

View file

@ -46,19 +46,6 @@ namespace ESMTerrain
{ {
} }
const ESM::Land::LandData *LandObject::getData(int flags) const
{
if ((mData.mDataLoaded & flags) != flags)
return nullptr;
return &mData;
}
int LandObject::getPlugin() const
{
return mLand->mPlugin;
}
const float defaultHeight = ESM::Land::DEFAULT_HEIGHT; const float defaultHeight = ESM::Land::DEFAULT_HEIGHT;
Storage::Storage(const VFS::Manager *vfs, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) Storage::Storage(const VFS::Manager *vfs, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps)
@ -158,7 +145,7 @@ namespace ESMTerrain
normal.normalize(); normal.normalize();
} }
void Storage::fixColour (osg::Vec4f& color, int cellX, int cellY, int col, int row, LandCache& cache) void Storage::fixColour (osg::Vec4ub& color, int cellX, int cellY, int col, int row, LandCache& cache)
{ {
if (col == ESM::Land::LAND_SIZE-1) if (col == ESM::Land::LAND_SIZE-1)
{ {
@ -175,22 +162,22 @@ namespace ESMTerrain
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0; const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0;
if (data) if (data)
{ {
color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3];
color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1];
color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2];
} }
else else
{ {
color.r() = 1; color.r() = 255;
color.g() = 1; color.g() = 255;
color.b() = 1; color.b() = 255;
} }
} }
void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
osg::ref_ptr<osg::Vec3Array> positions, osg::ref_ptr<osg::Vec3Array> positions,
osg::ref_ptr<osg::Vec3Array> normals, osg::ref_ptr<osg::Vec3Array> normals,
osg::ref_ptr<osg::Vec4Array> colours) osg::ref_ptr<osg::Vec4ubArray> colours)
{ {
// LOD level n means every 2^n-th vertex is kept // LOD level n means every 2^n-th vertex is kept
size_t increment = static_cast<size_t>(1) << lodLevel; size_t increment = static_cast<size_t>(1) << lodLevel;
@ -207,7 +194,7 @@ namespace ESMTerrain
colours->resize(numVerts*numVerts); colours->resize(numVerts*numVerts);
osg::Vec3f normal; osg::Vec3f normal;
osg::Vec4f color; osg::Vec4ub color;
float vertY = 0; float vertY = 0;
float vertX = 0; float vertX = 0;
@ -295,20 +282,20 @@ namespace ESMTerrain
if (colourData) if (colourData)
{ {
for (int i=0; i<3; ++i) for (int i=0; i<3; ++i)
color[i] = colourData->mColours[srcArrayIndex+i] / 255.f; color[i] = colourData->mColours[srcArrayIndex+i];
} }
else else
{ {
color.r() = 1; color.r() = 255;
color.g() = 1; color.g() = 255;
color.b() = 1; color.b() = 255;
} }
// Unlike normals, colors mostly connect seamlessly between cells, but not always... // Unlike normals, colors mostly connect seamlessly between cells, but not always...
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixColour(color, cellX, cellY, col, row, cache); fixColour(color, cellX, cellY, col, row, cache);
color.a() = 1; color.a() = 255;
(*colours)[static_cast<unsigned int>(vertX*numVerts + vertY)] = color; (*colours)[static_cast<unsigned int>(vertX*numVerts + vertY)] = color;
@ -388,8 +375,7 @@ namespace ESMTerrain
return texture; return texture;
} }
void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter, void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter, ImageVector &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
bool pack, ImageVector &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
{ {
osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f); osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f);
int cellX = static_cast<int>(std::floor(origin.x())); int cellX = static_cast<int>(std::floor(origin.x()));
@ -429,11 +415,8 @@ namespace ESMTerrain
layerList.push_back(getLayerInfo(getTextureName(*it))); layerList.push_back(getLayerInfo(getTextureName(*it)));
} }
int numTextures = textureIndices.size(); // size-1 since the base layer doesn't need blending
// numTextures-1 since the base layer doesn't need blending int numBlendmaps = textureIndices.size() - 1;
int numBlendmaps = pack ? static_cast<int>(std::ceil((numTextures - 1) / 4.f)) : (numTextures - 1);
int channels = pack ? 4 : 1;
// Second iteration - create and fill in the blend maps // Second iteration - create and fill in the blend maps
const int blendmapSize = (realTextureSize-1) * chunkSize + 1; const int blendmapSize = (realTextureSize-1) * chunkSize + 1;
@ -443,10 +426,8 @@ namespace ESMTerrain
for (int i=0; i<numBlendmaps; ++i) for (int i=0; i<numBlendmaps; ++i)
{ {
GLenum format = pack ? GL_RGBA : GL_ALPHA;
osg::ref_ptr<osg::Image> image (new osg::Image); osg::ref_ptr<osg::Image> image (new osg::Image);
image->allocateImage(blendmapImageSize, blendmapImageSize, 1, format, GL_UNSIGNED_BYTE); image->allocateImage(blendmapImageSize, blendmapImageSize, 1, GL_ALPHA, GL_UNSIGNED_BYTE);
unsigned char* pData = image->data(); unsigned char* pData = image->data();
for (int y=0; y<blendmapSize; ++y) for (int y=0; y<blendmapSize; ++y)
@ -456,18 +437,16 @@ namespace ESMTerrain
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x+rowStart, y+colStart, cache); UniqueTextureId id = getVtexIndexAt(cellX, cellY, x+rowStart, y+colStart, cache);
assert(textureIndicesMap.find(id) != textureIndicesMap.end()); assert(textureIndicesMap.find(id) != textureIndicesMap.end());
int layerIndex = textureIndicesMap.find(id)->second; int layerIndex = textureIndicesMap.find(id)->second;
int blendIndex = (pack ? static_cast<int>(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1);
int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0;
int alpha = (blendIndex == i) ? 255 : 0; int alpha = (layerIndex == i+1) ? 255 : 0;
int realY = (blendmapSize - y - 1)*imageScaleFactor; int realY = (blendmapSize - y - 1)*imageScaleFactor;
int realX = x*imageScaleFactor; int realX = x*imageScaleFactor;
pData[((realY+0)*blendmapImageSize + realX + 0)*channels + channel] = alpha; pData[(realY+0)*blendmapImageSize + realX + 0] = alpha;
pData[((realY+1)*blendmapImageSize + realX + 0)*channels + channel] = alpha; pData[(realY+1)*blendmapImageSize + realX + 0] = alpha;
pData[((realY+0)*blendmapImageSize + realX + 1)*channels + channel] = alpha; pData[(realY+0)*blendmapImageSize + realX + 1] = alpha;
pData[((realY+1)*blendmapImageSize + realX + 1)*channels + channel] = alpha; pData[(realY+1)*blendmapImageSize + realX + 1] = alpha;
} }
} }
blendmaps.push_back(image); blendmaps.push_back(image);

View file

@ -29,8 +29,17 @@ namespace ESMTerrain
META_Object(ESMTerrain, LandObject) META_Object(ESMTerrain, LandObject)
const ESM::Land::LandData* getData(int flags) const; inline const ESM::Land::LandData* getData(int flags) const
int getPlugin() const; {
if ((mData.mDataLoaded & flags) != flags)
return nullptr;
return &mData;
}
inline int getPlugin() const
{
return mLand->mPlugin;
}
private: private:
const ESM::Land* mLand; const ESM::Land* mLand;
@ -75,7 +84,7 @@ namespace ESMTerrain
virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
osg::ref_ptr<osg::Vec3Array> positions, osg::ref_ptr<osg::Vec3Array> positions,
osg::ref_ptr<osg::Vec3Array> normals, osg::ref_ptr<osg::Vec3Array> normals,
osg::ref_ptr<osg::Vec4Array> colours); osg::ref_ptr<osg::Vec4ubArray> colours);
/// Create textures holding layer blend values for a terrain chunk. /// Create textures holding layer blend values for a terrain chunk.
/// @note The terrain chunk shouldn't be larger than one cell since otherwise we might /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might
@ -83,14 +92,10 @@ namespace ESMTerrain
/// @note May be called from background threads. /// @note May be called from background threads.
/// @param chunkSize size of the terrain chunk in cell units /// @param chunkSize size of the terrain chunk in cell units
/// @param chunkCenter center of the chunk in cell units /// @param chunkCenter center of the chunk in cell units
/// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) -
/// otherwise, each texture contains blend values for one layer only. Shader-based rendering
/// can utilize packing, FFP can't.
/// @param blendmaps created blendmaps will be written here /// @param blendmaps created blendmaps will be written here
/// @param layerList names of the layer textures used will be written here /// @param layerList names of the layer textures used will be written here
virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, bool pack, virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps,
ImageVector& blendmaps, std::vector<Terrain::LayerInfo>& layerList);
std::vector<Terrain::LayerInfo>& layerList);
virtual float getHeightAt (const osg::Vec3f& worldPos); virtual float getHeightAt (const osg::Vec3f& worldPos);
@ -105,21 +110,20 @@ namespace ESMTerrain
private: private:
const VFS::Manager* mVFS; const VFS::Manager* mVFS;
void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); inline void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache);
void fixColour (osg::Vec4f& colour, int cellX, int cellY, int col, int row, LandCache& cache); inline void fixColour (osg::Vec4ub& colour, int cellX, int cellY, int col, int row, LandCache& cache);
void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); inline void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache);
float getVertexHeight (const ESM::Land::LandData* data, int x, int y); inline float getVertexHeight (const ESM::Land::LandData* data, int x, int y);
const LandObject* getLand(int cellX, int cellY, LandCache& cache); inline const LandObject* getLand(int cellX, int cellY, LandCache& cache);
// Since plugins can define new texture palettes, we need to know the plugin index too // Since plugins can define new texture palettes, we need to know the plugin index too
// in order to retrieve the correct texture name. // in order to retrieve the correct texture name.
// pair <texture id, plugin id> // pair <texture id, plugin id>
typedef std::pair<short, short> UniqueTextureId; typedef std::pair<short, short> UniqueTextureId;
UniqueTextureId getVtexIndexAt(int cellX, int cellY, inline UniqueTextureId getVtexIndexAt(int cellX, int cellY, int x, int y, LandCache&);
int x, int y, LandCache&);
std::string getTextureName (UniqueTextureId id); std::string getTextureName (UniqueTextureId id);
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap; std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;

View file

@ -83,38 +83,6 @@ namespace Misc
std::mutex mMutex; std::mutex mMutex;
T mValue; T mValue;
}; };
template <class T>
class SharedGuarded
{
public:
SharedGuarded()
: mMutex(std::make_shared<std::mutex>()), mValue()
{}
SharedGuarded(std::shared_ptr<T> value)
: mMutex(std::make_shared<std::mutex>()), mValue(std::move(value))
{}
Locked<T> lock() const
{
return Locked<T>(*mMutex, *mValue);
}
Locked<const T> lockConst() const
{
return Locked<const T>(*mMutex, *mValue);
}
operator bool() const
{
return static_cast<bool>(mValue);
}
private:
std::shared_ptr<std::mutex> mMutex;
std::shared_ptr<T> mValue;
};
} }
#endif #endif

View file

@ -120,7 +120,7 @@ void NiRotatingParticlesData::read(NIFStream *nif)
void NiPosData::read(NIFStream *nif) void NiPosData::read(NIFStream *nif)
{ {
mKeyList.reset(new Vector3KeyMap); mKeyList = std::make_shared<Vector3KeyMap>();
mKeyList->read(nif); mKeyList->read(nif);
} }
@ -128,14 +128,14 @@ void NiUVData::read(NIFStream *nif)
{ {
for(int i = 0;i < 4;i++) for(int i = 0;i < 4;i++)
{ {
mKeyList[i].reset(new FloatKeyMap); mKeyList[i] = std::make_shared<FloatKeyMap>();
mKeyList[i]->read(nif); mKeyList[i]->read(nif);
} }
} }
void NiFloatData::read(NIFStream *nif) void NiFloatData::read(NIFStream *nif)
{ {
mKeyList.reset(new FloatKeyMap); mKeyList = std::make_shared<FloatKeyMap>();
mKeyList->read(nif); mKeyList->read(nif);
} }
@ -177,7 +177,7 @@ void NiPixelData::read(NIFStream *nif)
void NiColorData::read(NIFStream *nif) void NiColorData::read(NIFStream *nif)
{ {
mKeyMap.reset(new Vector4KeyMap); mKeyMap = std::make_shared<Vector4KeyMap>();
mKeyMap->read(nif); mKeyMap->read(nif);
} }
@ -231,7 +231,7 @@ void NiMorphData::read(NIFStream *nif)
mMorphs.resize(morphCount); mMorphs.resize(morphCount);
for(int i = 0;i < morphCount;i++) for(int i = 0;i < morphCount;i++)
{ {
mMorphs[i].mKeyFrames.reset(new FloatKeyMap); mMorphs[i].mKeyFrames = std::make_shared<FloatKeyMap>();
mMorphs[i].mKeyFrames->read(nif, true); mMorphs[i].mKeyFrames->read(nif, true);
nif->getVector3s(mMorphs[i].mVertices, vertCount); nif->getVector3s(mMorphs[i].mVertices, vertCount);
} }
@ -239,22 +239,22 @@ void NiMorphData::read(NIFStream *nif)
void NiKeyframeData::read(NIFStream *nif) void NiKeyframeData::read(NIFStream *nif)
{ {
mRotations.reset(new QuaternionKeyMap); mRotations = std::make_shared<QuaternionKeyMap>();
mRotations->read(nif); mRotations->read(nif);
if(mRotations->mInterpolationType == Vector3KeyMap::sXYZInterpolation) if(mRotations->mInterpolationType == Vector3KeyMap::sXYZInterpolation)
{ {
//Chomp unused float //Chomp unused float
nif->getFloat(); nif->getFloat();
mXRotations.reset(new FloatKeyMap); mXRotations = std::make_shared<FloatKeyMap>();
mYRotations.reset(new FloatKeyMap); mYRotations = std::make_shared<FloatKeyMap>();
mZRotations.reset(new FloatKeyMap); mZRotations = std::make_shared<FloatKeyMap>();
mXRotations->read(nif, true); mXRotations->read(nif, true);
mYRotations->read(nif, true); mYRotations->read(nif, true);
mZRotations->read(nif, true); mZRotations->read(nif, true);
} }
mTranslations.reset(new Vector3KeyMap); mTranslations = std::make_shared<Vector3KeyMap>();
mTranslations->read(nif); mTranslations->read(nif);
mScales.reset(new FloatKeyMap); mScales = std::make_shared<FloatKeyMap>();
mScales->read(nif); mScales->read(nif);
} }

View file

@ -311,10 +311,11 @@ void VisController::operator() (osg::Node* node, osg::NodeVisitor* nv)
RollController::RollController(const Nif::NiFloatData *data) RollController::RollController(const Nif::NiFloatData *data)
: mData(data->mKeyList, 1.f) : mData(data->mKeyList, 1.f)
, mStartingTime(0)
{ {
} }
RollController::RollController() RollController::RollController() : mStartingTime(0)
{ {
} }

View file

@ -1,165 +0,0 @@
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* This library 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
* OpenSceneGraph Public License for more details.
*/
#include "objectcache.hpp"
#include <osg/Object>
#include <osg/Node>
namespace Resource
{
////////////////////////////////////////////////////////////////////////////////////////////
//
// ObjectCache
//
ObjectCache::ObjectCache():
osg::Referenced(true)
{
}
ObjectCache::~ObjectCache()
{
}
void ObjectCache::addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp)
{
if (!object)
{
OSG_ALWAYS << " trying to add NULL object to cache for " << filename << std::endl;
return;
}
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
_objectCache[filename]=ObjectTimeStampPair(object,timestamp);
}
osg::ref_ptr<osg::Object> ObjectCache::getRefFromObjectCache(const std::string& fileName)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
ObjectCacheMap::iterator itr = _objectCache.find(fileName);
if (itr!=_objectCache.end())
{
return itr->second.first;
}
else return 0;
}
bool ObjectCache::checkInObjectCache(const std::string &fileName, double timeStamp)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
ObjectCacheMap::iterator itr = _objectCache.find(fileName);
if (itr!=_objectCache.end())
{
itr->second.second = timeStamp;
return true;
}
else return false;
}
void ObjectCache::updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
// look for objects with external references and update their time stamp.
for(ObjectCacheMap::iterator itr=_objectCache.begin();
itr!=_objectCache.end();
++itr)
{
// If ref count is greater than 1, the object has an external reference.
// If the timestamp is yet to be initialized, it needs to be updated too.
if (itr->second.first->referenceCount()>1 || itr->second.second == 0.0)
{
// So update it.
itr->second.second = referenceTime;
}
}
}
void ObjectCache::removeExpiredObjectsInCache(double expiryTime)
{
std::vector<osg::ref_ptr<osg::Object> > objectsToRemove;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
// Remove expired entries from object cache
ObjectCacheMap::iterator oitr = _objectCache.begin();
while(oitr != _objectCache.end())
{
if (oitr->second.second<=expiryTime)
{
objectsToRemove.push_back(oitr->second.first);
_objectCache.erase(oitr++);
}
else
{
++oitr;
}
}
}
// note, actual unref happens outside of the lock
objectsToRemove.clear();
}
void ObjectCache::removeFromObjectCache(const std::string& fileName)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
ObjectCacheMap::iterator itr = _objectCache.find(fileName);
if (itr!=_objectCache.end()) _objectCache.erase(itr);
}
void ObjectCache::clear()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
_objectCache.clear();
}
void ObjectCache::releaseGLObjects(osg::State* state)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
for(ObjectCacheMap::iterator itr = _objectCache.begin();
itr != _objectCache.end();
++itr)
{
osg::Object* object = itr->second.first.get();
object->releaseGLObjects(state);
}
}
void ObjectCache::accept(osg::NodeVisitor &nv)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
for(ObjectCacheMap::iterator itr = _objectCache.begin();
itr != _objectCache.end();
++itr)
{
osg::Object* object = itr->second.first.get();
if (object)
{
osg::Node* node = dynamic_cast<osg::Node*>(object);
if (node)
node->accept(nv);
}
}
}
unsigned int ObjectCache::getCacheSize() const
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
return _objectCache.size();
}
}

View file

@ -1,5 +1,8 @@
// Resource ObjectCache for OpenMW, forked from osgDB ObjectCache by Robert Osfield, see copyright notice below. // Resource ObjectCache for OpenMW, forked from osgDB ObjectCache by Robert Osfield, see copyright notice below.
// The main change from the upstream version is that removeExpiredObjectsInCache no longer keeps a lock while the unref happens. // Changes:
// - removeExpiredObjectsInCache no longer keeps a lock while the unref happens.
// - template allows customized KeyType.
// - objects with uninitialized time stamp are not removed.
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield /* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
* *
@ -19,6 +22,7 @@
#include <osg/Referenced> #include <osg/Referenced>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <osg/Node>
#include <string> #include <string>
#include <map> #include <map>
@ -32,11 +36,13 @@ namespace osg
namespace Resource { namespace Resource {
class ObjectCache : public osg::Referenced template <typename KeyType>
class GenericObjectCache : public osg::Referenced
{ {
public: public:
ObjectCache(); GenericObjectCache()
: osg::Referenced(true) {}
/** For each object in the cache which has an reference count greater than 1 /** For each object in the cache which has an reference count greater than 1
* (and therefore referenced by elsewhere in the application) set the time stamp * (and therefore referenced by elsewhere in the application) set the time stamp
@ -44,59 +50,149 @@ class ObjectCache : public osg::Referenced
* This would typically be called once per frame by applications which are doing database paging, * This would typically be called once per frame by applications which are doing database paging,
* and need to prune objects that are no longer required. * and need to prune objects that are no longer required.
* The time used should be taken from the FrameStamp::getReferenceTime().*/ * The time used should be taken from the FrameStamp::getReferenceTime().*/
void updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime); void updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime)
{
// look for objects with external references and update their time stamp.
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
for(typename ObjectCacheMap::iterator itr=_objectCache.begin(); itr!=_objectCache.end(); ++itr)
{
// If ref count is greater than 1, the object has an external reference.
// If the timestamp is yet to be initialized, it needs to be updated too.
if (itr->second.first->referenceCount()>1 || itr->second.second == 0.0)
itr->second.second = referenceTime;
}
}
/** Removed object in the cache which have a time stamp at or before the specified expiry time. /** Removed object in the cache which have a time stamp at or before the specified expiry time.
* This would typically be called once per frame by applications which are doing database paging, * This would typically be called once per frame by applications which are doing database paging,
* and need to prune objects that are no longer required, and called after the a called * and need to prune objects that are no longer required, and called after the a called
* after the call to updateTimeStampOfObjectsInCacheWithExternalReferences(expirtyTime).*/ * after the call to updateTimeStampOfObjectsInCacheWithExternalReferences(expirtyTime).*/
void removeExpiredObjectsInCache(double expiryTime); void removeExpiredObjectsInCache(double expiryTime)
{
std::vector<osg::ref_ptr<osg::Object> > objectsToRemove;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
// Remove expired entries from object cache
typename ObjectCacheMap::iterator oitr = _objectCache.begin();
while(oitr != _objectCache.end())
{
if (oitr->second.second<=expiryTime)
{
objectsToRemove.push_back(oitr->second.first);
_objectCache.erase(oitr++);
}
else
++oitr;
}
}
// note, actual unref happens outside of the lock
objectsToRemove.clear();
}
/** Remove all objects in the cache regardless of having external references or expiry times.*/ /** Remove all objects in the cache regardless of having external references or expiry times.*/
void clear(); void clear()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
_objectCache.clear();
}
/** Add a filename,object,timestamp triple to the Registry::ObjectCache.*/ /** Add a key,object,timestamp triple to the Registry::ObjectCache.*/
void addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp = 0.0); void addEntryToObjectCache(const KeyType& key, osg::Object* object, double timestamp = 0.0)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
_objectCache[key]=ObjectTimeStampPair(object,timestamp);
}
/** Remove Object from cache.*/ /** Remove Object from cache.*/
void removeFromObjectCache(const std::string& fileName); void removeFromObjectCache(const KeyType& key)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
typename ObjectCacheMap::iterator itr = _objectCache.find(key);
if (itr!=_objectCache.end()) _objectCache.erase(itr);
}
/** Get an ref_ptr<Object> from the object cache*/ /** Get an ref_ptr<Object> from the object cache*/
osg::ref_ptr<osg::Object> getRefFromObjectCache(const std::string& fileName); osg::ref_ptr<osg::Object> getRefFromObjectCache(const KeyType& key)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
typename ObjectCacheMap::iterator itr = _objectCache.find(key);
if (itr!=_objectCache.end())
return itr->second.first;
else return 0;
}
/** Check if an object is in the cache, and if it is, update its usage time stamp. */ /** Check if an object is in the cache, and if it is, update its usage time stamp. */
bool checkInObjectCache(const std::string& fileName, double timeStamp); bool checkInObjectCache(const KeyType& key, double timeStamp)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
typename ObjectCacheMap::iterator itr = _objectCache.find(key);
if (itr!=_objectCache.end())
{
itr->second.second = timeStamp;
return true;
}
else return false;
}
/** call releaseGLObjects on all objects attached to the object cache.*/ /** call releaseGLObjects on all objects attached to the object cache.*/
void releaseGLObjects(osg::State* state); void releaseGLObjects(osg::State* state)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
for(typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr)
{
osg::Object* object = itr->second.first.get();
object->releaseGLObjects(state);
}
}
/** call node->accept(nv); for all nodes in the objectCache. */ /** call node->accept(nv); for all nodes in the objectCache. */
void accept(osg::NodeVisitor& nv); void accept(osg::NodeVisitor& nv)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
for(typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr)
{
osg::Object* object = itr->second.first.get();
if (object)
{
osg::Node* node = dynamic_cast<osg::Node*>(object);
if (node)
node->accept(nv);
}
}
}
/** call operator()(osg::Object*) for each object in the cache. */ /** call operator()(osg::Object*) for each object in the cache. */
template <class Functor> template <class Functor>
void call(Functor& f) void call(Functor& f)
{ {
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex); OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
for (ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it) for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it)
f(it->second.first.get()); f(it->second.first.get());
} }
/** Get the number of objects in the cache. */ /** Get the number of objects in the cache. */
unsigned int getCacheSize() const; unsigned int getCacheSize() const
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
return _objectCache.size();
}
protected: protected:
virtual ~ObjectCache(); virtual ~GenericObjectCache() {}
typedef std::pair<osg::ref_ptr<osg::Object>, double > ObjectTimeStampPair; typedef std::pair<osg::ref_ptr<osg::Object>, double > ObjectTimeStampPair;
typedef std::map<std::string, ObjectTimeStampPair > ObjectCacheMap; typedef std::map<KeyType, ObjectTimeStampPair > ObjectCacheMap;
ObjectCacheMap _objectCache; ObjectCacheMap _objectCache;
mutable OpenThreads::Mutex _objectCacheMutex; mutable OpenThreads::Mutex _objectCacheMutex;
}; };
class ObjectCache : public GenericObjectCache<std::string>
{
};
} }
#endif #endif

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