mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-04-01 06:06:42 +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:
commit
f671c0bddc
135 changed files with 1785 additions and 1303 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -3,6 +3,9 @@
|
|||
|
||||
Bug #2969: Scripted items can stack
|
||||
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 #3733: Normal maps are inverted on mirrored UVs
|
||||
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 #4723: ResetActors command works incorrectly
|
||||
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 #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
|
||||
|
@ -32,15 +36,26 @@
|
|||
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 #4820: Spell absorption is broken
|
||||
Bug #4823: Jail progress bar works incorrectly
|
||||
Bug #4827: NiUVController is handled incorrectly
|
||||
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 #4841: Russian localization ignores implicit keywords
|
||||
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 #4877: Startup script executes only on a new game start
|
||||
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 #3442: Default values for fallbacks from ini file
|
||||
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 #4209: Editor: Faction rank sub-table
|
||||
Feature #4673: Weapon sheathing
|
||||
|
@ -52,6 +67,7 @@
|
|||
Feature #4887: Add openmw command option to set initial random seed
|
||||
Feature #4890: Make Distant Terrain configurable
|
||||
Task #4686: Upgrade media decoder to a more current FFmpeg API
|
||||
Task #4695: Optimize Distant Terrain memory consumption
|
||||
|
||||
0.45.0
|
||||
------
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
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 install qt
|
||||
brew install ccache
|
||||
|
||||
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
|
||||
|
|
|
@ -4,12 +4,15 @@ export CXX=clang++
|
|||
export CC=clang
|
||||
|
||||
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
|
||||
cd build
|
||||
|
||||
cmake \
|
||||
-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_SYSROOT="macosx10.14" \
|
||||
-D CMAKE_BUILD_TYPE=Release \
|
||||
|
|
|
@ -267,11 +267,12 @@ endif()
|
|||
IF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
|
||||
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
|
||||
osgdb_bmp
|
||||
osgdb_dds
|
||||
osgdb_freetype
|
||||
osgdb_jpeg
|
||||
osgdb_osg
|
||||
osgdb_png
|
||||
|
@ -858,8 +859,8 @@ endif()
|
|||
|
||||
# Apple bundling
|
||||
if (OPENMW_OSX_DEPLOYMENT AND APPLE AND DESIRED_QT_VERSION MATCHES 5)
|
||||
if (${CMAKE_MAJOR_VERSION} STREQUAL "3" AND ${CMAKE_MINOR_VERSION} STREQUAL "13")
|
||||
message(FATAL_ERROR "macOS packaging is broken in CMake 3.13.*, see https://gitlab.com/OpenMW/openmw/issues/4767. Please use an older version like 3.12.4")
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.13 AND CMAKE_VERSION VERSION_LESS 3.13.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 ()
|
||||
|
||||
get_property(QT_COCOA_PLUGIN_PATH TARGET Qt5::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE)
|
||||
|
|
|
@ -739,7 +739,7 @@ void Record<ESM::Faction>::print()
|
|||
std::cout << " Faction Reaction: "
|
||||
<< 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 << " Deleted: " << mIsDeleted << std::endl;
|
||||
}
|
||||
|
|
|
@ -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).
|
||||
options(desc).positional(p).run();
|
||||
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)
|
||||
{
|
||||
|
@ -110,18 +121,6 @@ bool parseOptions (int argc, char** argv, std::vector<std::string>& files)
|
|||
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 << desc << std::endl;
|
||||
return false;
|
||||
|
|
|
@ -195,7 +195,7 @@ namespace CSMPrefs
|
|||
// Only activate the best match; in exact conflicts, this will favor the first shortcut added.
|
||||
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;
|
||||
|
||||
if (shortcut->getModifierStatus() && shortcut->getSecondaryMode() == Shortcut::SM_Replace)
|
||||
|
@ -325,7 +325,7 @@ namespace CSMPrefs
|
|||
if (left.first == Matches_WithMod && right.first == Matches_NoMod)
|
||||
return true;
|
||||
else
|
||||
return left.second->getPosition() >= right.second->getPosition();
|
||||
return left.second->getPosition() > right.second->getPosition();
|
||||
}
|
||||
|
||||
void ShortcutEventHandler::widgetDestroyed()
|
||||
|
|
|
@ -1006,7 +1006,7 @@ void CSMWorld::Data::loadFallbackEntries()
|
|||
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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QApplication>
|
||||
|
@ -615,7 +616,39 @@ void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint)
|
|||
}
|
||||
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);
|
||||
|
|
|
@ -18,10 +18,10 @@ namespace CSVRender
|
|||
private:
|
||||
const CSMWorld::Data& mData;
|
||||
|
||||
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY);
|
||||
virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
|
||||
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY) override;
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@
|
|||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
#include <components/detournavigator/navigator.hpp>
|
||||
|
||||
#include "mwinput/inputmanagerimp.hpp"
|
||||
|
||||
#include "mwgui/windowmanagerimp.hpp"
|
||||
|
@ -289,6 +291,8 @@ bool OMW::Engine::frame(float frametime)
|
|||
|
||||
stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems());
|
||||
stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads());
|
||||
|
||||
mEnvironment.getWorld()->getNavigator()->reportStats(frameNumber, *stats);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -313,6 +317,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
|
|||
, mActivationDistanceOverride(-1)
|
||||
, mGrab(true)
|
||||
, mExportFonts(false)
|
||||
, mRandomSeed(0)
|
||||
, mScriptContext (0)
|
||||
, mFSStrict (false)
|
||||
, mScriptBlacklistUse (true)
|
||||
|
@ -667,7 +672,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
|||
// Create the world
|
||||
mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(),
|
||||
mFileCollections, mContentFiles, mEncoder, mFallbackMap,
|
||||
mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()));
|
||||
mActivationDistanceOverride, mCellName, mResDir.string(), mCfgMgr.getUserDataPath().string()));
|
||||
mEnvironment.getWorld()->setupPlayer();
|
||||
input->setPlayer(&mEnvironment.getWorld()->getPlayer());
|
||||
|
||||
|
@ -783,6 +788,9 @@ void OMW::Engine::go()
|
|||
*/
|
||||
|
||||
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);
|
||||
|
||||
|
@ -855,22 +863,21 @@ void OMW::Engine::go()
|
|||
{
|
||||
// start in main menu
|
||||
mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu);
|
||||
try
|
||||
{
|
||||
// Is there an ini setting for this filename or something?
|
||||
mEnvironment.getSoundManager()->streamMusic("Special/morrowind title.mp3");
|
||||
|
||||
std::string logo = mFallbackMap["Movies_Morrowind_Logo"];
|
||||
if (!logo.empty())
|
||||
mEnvironment.getWindowManager()->playVideo(logo, true);
|
||||
}
|
||||
catch (...) {}
|
||||
mEnvironment.getSoundManager()->playTitleMusic();
|
||||
std::string logo = mFallbackMap["Movies_Morrowind_Logo"];
|
||||
if (!logo.empty())
|
||||
mEnvironment.getWindowManager()->playVideo(logo, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
mEnvironment.getStateManager()->newGame (!mNewGame);
|
||||
}
|
||||
|
||||
if (!mStartupScript.empty() && mEnvironment.getStateManager()->getState() == MWState::StateManager::State_Running)
|
||||
{
|
||||
mEnvironment.getWindowManager()->executeInConsole(mStartupScript);
|
||||
}
|
||||
|
||||
// Start the main rendering loop
|
||||
osg::Timer frameTimer;
|
||||
double simulationTime = 0.0;
|
||||
|
|
|
@ -90,9 +90,9 @@ namespace MWBase
|
|||
virtual void setPlayerClass (const ESM::Class& class_) = 0;
|
||||
///< 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.
|
||||
/// @param sleep is the player sleeping or waiting?
|
||||
|
||||
|
|
|
@ -89,6 +89,9 @@ namespace MWBase
|
|||
///< Start playing music from the selected folder
|
||||
/// \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;
|
||||
///< Make an actor say some text.
|
||||
/// \param filename name of a sound file in "Sound/" in the data directory.
|
||||
|
|
|
@ -406,7 +406,7 @@ namespace MWBase
|
|||
virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
|
||||
virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0;
|
||||
|
||||
virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 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
|
||||
|
||||
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 void rest() = 0;
|
||||
virtual void rest(double hours) = 0;
|
||||
|
||||
virtual void setPlayerTraveling(bool traveling) = 0;
|
||||
virtual bool isPlayerTraveling() const = 0;
|
||||
|
|
|
@ -131,9 +131,6 @@ namespace MWDialogue
|
|||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
if (tok->isImplicitKeyword() && mTranslationDataStorage.hasTranslation())
|
||||
continue;
|
||||
|
||||
if (mActorKnownTopics.count( topicId ))
|
||||
mKnownTopics.insert( topicId );
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ namespace MWDialogue
|
|||
Token(const std::string & text, Type type) : mText(text), mType(type) {}
|
||||
|
||||
bool isExplicitLink() { return mType == ExplicitLink; }
|
||||
bool isImplicitKeyword() { return mType == ImplicitKeyword; }
|
||||
|
||||
std::string mText;
|
||||
Type mType;
|
||||
|
|
|
@ -11,10 +11,12 @@
|
|||
#include "../mwscript/extensions.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/scriptmanager.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
|
@ -173,6 +175,12 @@ namespace MWGui
|
|||
print("> " + command + "\n");
|
||||
|
||||
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);
|
||||
|
||||
if (compile (command + "\n", output))
|
||||
|
|
|
@ -122,8 +122,7 @@ namespace MWGui
|
|||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
for (int i=0; i<mDays*24; ++i)
|
||||
MWBase::Environment::get().getMechanicsManager()->rest(true);
|
||||
MWBase::Environment::get().getMechanicsManager()->rest(mDays * 24, true);
|
||||
|
||||
/*
|
||||
Start of tes3mp change (major)
|
||||
|
|
|
@ -304,11 +304,10 @@ namespace MWGui
|
|||
|
||||
MyGUI::IntSize requested = button->getRequestedSize();
|
||||
|
||||
button->setImageCoord(MyGUI::IntCoord(0, 0, requested.width, requested.height));
|
||||
// Trim off some of the excessive padding
|
||||
// TODO: perhaps do this within ImageButton?
|
||||
int trim = 8;
|
||||
button->setImageCoord(MyGUI::IntCoord(0, trim, requested.width, requested.height-trim));
|
||||
int height = requested.height-trim*2;
|
||||
int height = requested.height-16;
|
||||
button->setImageTile(MyGUI::IntSize(requested.width, height));
|
||||
button->setCoord((maxwidth-requested.width) / 2, curH, requested.width, height);
|
||||
curH += height;
|
||||
|
|
|
@ -244,8 +244,7 @@ namespace MWGui
|
|||
map->setNeedMouseFocus(false);
|
||||
fog->setNeedMouseFocus(false);
|
||||
|
||||
mMapWidgets.push_back(map);
|
||||
mFogWidgets.push_back(fog);
|
||||
mMaps.emplace_back(map, fog);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -265,36 +264,37 @@ namespace MWGui
|
|||
|
||||
void LocalMapBase::applyFogOfWar()
|
||||
{
|
||||
TextureVector fogTextures;
|
||||
for (int mx=0; mx<mNumCells; ++mx)
|
||||
{
|
||||
for (int my=0; my<mNumCells; ++my)
|
||||
{
|
||||
int x = mCurX + (mx - 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)
|
||||
{
|
||||
fog->setImageTexture("");
|
||||
entry.mFogTexture.reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> tex = mLocalMapRender->getFogOfWarTexture(x, y);
|
||||
if (tex)
|
||||
{
|
||||
std::shared_ptr<MyGUI::ITexture> myguitex (new osgMyGUI::OSGTexture(tex));
|
||||
fog->setRenderItemTexture(myguitex.get());
|
||||
entry.mFogTexture.reset(new osgMyGUI::OSGTexture(tex));
|
||||
fog->setRenderItemTexture(entry.mFogTexture.get());
|
||||
fog->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f));
|
||||
fogTextures.push_back(myguitex);
|
||||
}
|
||||
else
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
@ -428,7 +428,6 @@ namespace MWGui
|
|||
applyFogOfWar();
|
||||
|
||||
// Update the map textures
|
||||
TextureVector textures;
|
||||
for (int mx=0; mx<mNumCells; ++mx)
|
||||
{
|
||||
for (int my=0; my<mNumCells; ++my)
|
||||
|
@ -436,21 +435,23 @@ namespace MWGui
|
|||
int mapX = x + (mx - 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);
|
||||
if (texture)
|
||||
{
|
||||
std::shared_ptr<MyGUI::ITexture> guiTex (new osgMyGUI::OSGTexture(texture));
|
||||
textures.push_back(guiTex);
|
||||
box->setRenderItemTexture(guiTex.get());
|
||||
entry.mMapTexture.reset(new osgMyGUI::OSGTexture(texture));
|
||||
box->setRenderItemTexture(entry.mMapTexture.get());
|
||||
box->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f));
|
||||
}
|
||||
else
|
||||
{
|
||||
box->setRenderItemTexture(nullptr);
|
||||
entry.mMapTexture.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
mMapTextures.swap(textures);
|
||||
|
||||
// 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.
|
||||
|
@ -896,22 +897,13 @@ namespace MWGui
|
|||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -1128,8 +1120,8 @@ namespace MWGui
|
|||
NoDrop::setAlpha(alpha);
|
||||
// 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
|
||||
for (MyGUI::ImageBox* widget : mMapWidgets)
|
||||
widget->setVisible(alpha == 1);
|
||||
for (MapEntry& entry : mMaps)
|
||||
entry.mMapWidget->setVisible(alpha == 1);
|
||||
}
|
||||
|
||||
void MapWindow::customMarkerCreated(MyGUI::Widget *marker)
|
||||
|
|
|
@ -148,12 +148,17 @@ namespace MWGui
|
|||
// Stores markers that were placed by a player. May be shared between multiple map views.
|
||||
CustomMarkerCollection& mCustomMarkers;
|
||||
|
||||
std::vector<MyGUI::ImageBox*> mMapWidgets;
|
||||
std::vector<MyGUI::ImageBox*> mFogWidgets;
|
||||
struct MapEntry
|
||||
{
|
||||
MapEntry(MyGUI::ImageBox* mapWidget, MyGUI::ImageBox* fogWidget)
|
||||
: mMapWidget(mapWidget), mFogWidget(fogWidget) {}
|
||||
|
||||
typedef std::vector<std::shared_ptr<MyGUI::ITexture> > TextureVector;
|
||||
TextureVector mMapTextures;
|
||||
TextureVector mFogTextures;
|
||||
MyGUI::ImageBox* mMapWidget;
|
||||
MyGUI::ImageBox* mFogWidget;
|
||||
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.
|
||||
std::vector<MyGUI::Widget*> mDoorMarkerWidgets;
|
||||
|
@ -333,10 +338,6 @@ namespace MWGui
|
|||
typedef std::pair<int, int> CellId;
|
||||
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* mEventBoxLocal;
|
||||
|
||||
|
|
|
@ -141,7 +141,8 @@ namespace MWGui
|
|||
|
||||
MyGUI::ScrollBar* scroll = current->castType<MyGUI::ScrollBar>();
|
||||
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
|
||||
float min,max;
|
||||
|
@ -438,14 +439,18 @@ namespace MWGui
|
|||
if (getSettingType(scroller) == "Slider")
|
||||
{
|
||||
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 min,max;
|
||||
getSettingMinMax(scroller, min, max);
|
||||
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));
|
||||
}
|
||||
else
|
||||
|
|
|
@ -79,14 +79,11 @@ namespace MWGui
|
|||
static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fMagicStartIconBlink")->mValue.getFloat();
|
||||
|
||||
std::vector<MagicEffectInfo>& effectInfos = effectInfoPair.second;
|
||||
bool addNewLine = true;
|
||||
bool addNewLine = false;
|
||||
for (const MagicEffectInfo& effectInfo : effectInfos)
|
||||
{
|
||||
if (addNewLine)
|
||||
{
|
||||
sourcesDescription += "\n";
|
||||
addNewLine = false;
|
||||
}
|
||||
|
||||
// if at least one of the effect sources is permanent, the effect will never wear off
|
||||
if (effectInfo.mPermanent)
|
||||
|
@ -161,6 +158,8 @@ namespace MWGui
|
|||
sourcesDescription += MWGui::ToolTips::toString(duration) + "s";
|
||||
}
|
||||
}
|
||||
|
||||
addNewLine = true;
|
||||
}
|
||||
|
||||
if (remainingDuration > 0.f)
|
||||
|
|
|
@ -169,8 +169,7 @@ namespace MWGui
|
|||
npcStats.setGoldPool(npcStats.getGoldPool() + price);
|
||||
|
||||
// advance time
|
||||
MWBase::Environment::get().getMechanicsManager()->rest(false);
|
||||
MWBase::Environment::get().getMechanicsManager()->rest(false);
|
||||
MWBase::Environment::get().getMechanicsManager()->rest(2, false);
|
||||
|
||||
/*
|
||||
Start of tes3mp change (major)
|
||||
|
|
|
@ -174,10 +174,7 @@ namespace MWGui
|
|||
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();
|
||||
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 (true);
|
||||
}
|
||||
MWBase::Environment::get().getMechanicsManager()->rest(hours, true);
|
||||
|
||||
/*
|
||||
Start of tes3mp change (major)
|
||||
|
|
|
@ -271,7 +271,7 @@ namespace MWGui
|
|||
void WaitDialog::onWaitingProgressChanged(int cur, int total)
|
||||
{
|
||||
mProgressBar.setProgress(cur, total);
|
||||
MWBase::Environment::get().getMechanicsManager()->rest(mSleeping);
|
||||
MWBase::Environment::get().getMechanicsManager()->rest(1, mSleeping);
|
||||
|
||||
/*
|
||||
Start of tes3mp change (major)
|
||||
|
|
|
@ -1070,9 +1070,6 @@ namespace MWGui
|
|||
|
||||
updateMap();
|
||||
|
||||
if (!mMap->isVisible())
|
||||
mMap->onFrame(frameDuration);
|
||||
|
||||
mHud->onFrame(frameDuration);
|
||||
|
||||
mDebugWindow->onFrame(frameDuration);
|
||||
|
|
|
@ -208,8 +208,6 @@ namespace MWInput
|
|||
|
||||
void InputManager::handleGuiArrowKey(int action)
|
||||
{
|
||||
// Temporary shut-down of this function until deemed necessary.
|
||||
return;
|
||||
if (SDL_IsTextInputActive())
|
||||
return;
|
||||
|
||||
|
@ -414,7 +412,8 @@ namespace MWInput
|
|||
case A_MoveRight:
|
||||
case A_MoveForward:
|
||||
case A_MoveBackward:
|
||||
handleGuiArrowKey(action);
|
||||
// Temporary shut-down of this function until deemed necessary.
|
||||
//handleGuiArrowKey(action);
|
||||
break;
|
||||
case A_Journal:
|
||||
toggleJournal ();
|
||||
|
@ -1905,6 +1904,17 @@ namespace MWInput
|
|||
MWBase::Environment::get().getWindowManager ()->notifyInputActionBound ();
|
||||
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)
|
||||
return;
|
||||
|
||||
|
|
|
@ -147,23 +147,45 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
|
|||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
|
||||
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 ();
|
||||
|
||||
health = 0.1f * endurance;
|
||||
|
||||
magicka = 0;
|
||||
if (!stunted)
|
||||
{
|
||||
float fRestMagicMult = settings.find("fRestMagicMult")->mValue.getFloat ();
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
MWWorld::Ptr mCreature;
|
||||
|
@ -611,7 +633,7 @@ namespace MWMechanics
|
|||
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);
|
||||
if (stats.isDead())
|
||||
|
@ -625,12 +647,36 @@ namespace MWMechanics
|
|||
getRestorationPerHourOfSleep(ptr, health, magicka);
|
||||
|
||||
DynamicStat<float> stat = stats.getHealth();
|
||||
stat.setCurrent(stat.getCurrent() + health);
|
||||
stat.setCurrent(stat.getCurrent() + health * hours);
|
||||
stats.setHealth(stat);
|
||||
|
||||
stat = stats.getMagicka();
|
||||
stat.setCurrent(stat.getCurrent() + magicka);
|
||||
stats.setMagicka(stat);
|
||||
double restoreHours = hours;
|
||||
bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0;
|
||||
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.
|
||||
|
@ -653,7 +699,7 @@ namespace MWMechanics
|
|||
float x = fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance);
|
||||
x *= fEndFatigueMult * endurance;
|
||||
|
||||
fatigue.setCurrent (fatigue.getCurrent() + 3600 * x);
|
||||
fatigue.setCurrent (fatigue.getCurrent() + 3600 * x * hours);
|
||||
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 osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
|
||||
|
||||
|
@ -1937,7 +1983,7 @@ namespace MWMechanics
|
|||
continue;
|
||||
|
||||
if (!sleep || iter->first == player)
|
||||
restoreDynamicStats(iter->first, sleep);
|
||||
restoreDynamicStats(iter->first, hours, sleep);
|
||||
|
||||
if ((!iter->first.getRefData().getBaseNode()) ||
|
||||
(playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange)
|
||||
|
@ -2049,11 +2095,12 @@ namespace MWMechanics
|
|||
getRestorationPerHourOfSleep(ptr, healthPerHour, magickaPerHour);
|
||||
|
||||
CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0;
|
||||
|
||||
float healthHours = healthPerHour > 0
|
||||
? (stats.getHealth().getModified() - stats.getHealth().getCurrent()) / healthPerHour
|
||||
: 1.0f;
|
||||
float magickaHours = magickaPerHour > 0
|
||||
float magickaHours = magickaPerHour > 0 && !stunted
|
||||
? (stats.getMagicka().getModified() - stats.getMagicka().getCurrent()) / magickaPerHour
|
||||
: 1.0f;
|
||||
|
||||
|
|
|
@ -121,13 +121,13 @@ namespace MWMechanics
|
|||
void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor,
|
||||
MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance);
|
||||
|
||||
void rest(bool sleep);
|
||||
///< Update actors while the player is waiting or sleeping. This should be called every hour.
|
||||
void rest(double hours, bool sleep);
|
||||
///< Update actors while the player is waiting or sleeping.
|
||||
|
||||
void updateSneaking(CharacterController* ctrl, float duration);
|
||||
///< 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;
|
||||
///< Calculate how many hours the given actor needs to rest in order to be fully healed
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
#include "../mwphysics/collisiontype.hpp"
|
||||
|
||||
#include "pathgrid.hpp"
|
||||
#include "creaturestats.hpp"
|
||||
#include "steering.hpp"
|
||||
|
@ -45,6 +47,16 @@ namespace MWMechanics
|
|||
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):
|
||||
mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle),
|
||||
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)),
|
||||
|
@ -298,7 +310,8 @@ namespace MWMechanics
|
|||
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
|
||||
bool isWaterCreature = actor.getClass().isPureWaterCreature(actor);
|
||||
const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor);
|
||||
const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor);
|
||||
do {
|
||||
// Determine a random location within radius of original position
|
||||
const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance;
|
||||
|
@ -309,22 +322,31 @@ namespace MWMechanics
|
|||
mDestination = osg::Vec3f(destinationX, destinationY, destinationZ);
|
||||
|
||||
// Check if land creature will walk onto water or if water creature will swim onto land
|
||||
if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) ||
|
||||
(isWaterCreature && !destinationThroughGround(currentPosition, mDestination)))
|
||||
if (!isWaterCreature && destinationIsAtWater(actor, 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);
|
||||
mPathFinder.buildPath(actor, currentPosition, mDestination, actor.getCell(),
|
||||
getPathGridGraph(actor.getCell()), halfExtents, getNavigatorFlags(actor));
|
||||
mPathFinder.addPointToPath(mDestination);
|
||||
|
||||
if (mPathFinder.isPathConstructed())
|
||||
{
|
||||
storage.setState(AiWanderStorage::Wander_Walking, true);
|
||||
mHasDestination = true;
|
||||
mUsePathgrid = false;
|
||||
}
|
||||
return;
|
||||
mPathFinder.buildPathByNavMesh(actor, currentPosition, mDestination, halfExtents,
|
||||
getNavigatorFlags(actor));
|
||||
}
|
||||
|
||||
if (mPathFinder.isPathConstructed())
|
||||
{
|
||||
storage.setState(AiWanderStorage::Wander_Walking, true);
|
||||
mHasDestination = true;
|
||||
mUsePathgrid = false;
|
||||
}
|
||||
|
||||
break;
|
||||
} while (--attempts);
|
||||
}
|
||||
|
||||
|
@ -342,8 +364,10 @@ namespace MWMechanics
|
|||
* 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) {
|
||||
const int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door;
|
||||
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) {
|
||||
|
@ -479,7 +503,7 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
// 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();
|
||||
stopWalking(actor, storage);
|
||||
|
|
|
@ -1720,20 +1720,22 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
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);
|
||||
mAttackType = getBestAttack(weapon->get<ESM::Weapon>()->mBase);
|
||||
}
|
||||
else
|
||||
setAttackTypeBasedOnMovement();
|
||||
{
|
||||
// There is no "best attack" for Hand-to-Hand
|
||||
setAttackTypeRandomly(mAttackType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// There is no "best attack" for Hand-to-Hand
|
||||
setAttackTypeRandomly(mAttackType);
|
||||
setAttackTypeBasedOnMovement();
|
||||
}
|
||||
}
|
||||
// else if (mPtr != getPlayer()) use mAttackType set by AiCombat
|
||||
|
@ -2697,6 +2699,13 @@ void CharacterController::forceStateUpdate()
|
|||
return;
|
||||
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);
|
||||
|
||||
if(mDeathState != CharState_None)
|
||||
|
|
|
@ -303,7 +303,9 @@ namespace MWMechanics
|
|||
void MechanicsManager::advanceTime (float duration)
|
||||
{
|
||||
// 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();
|
||||
player.getClass().getInventoryStore(player).rechargeItems(duration);
|
||||
}
|
||||
|
@ -491,17 +493,17 @@ namespace MWMechanics
|
|||
return mActors.isSneaking(ptr);
|
||||
}
|
||||
|
||||
void MechanicsManager::rest(bool sleep)
|
||||
void MechanicsManager::rest(double hours, bool 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
|
||||
|
|
|
@ -95,9 +95,9 @@ namespace MWMechanics
|
|||
virtual void setPlayerClass (const ESM::Class& class_) override;
|
||||
///< 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.
|
||||
/// @param sleep is the player sleeping or waiting?
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
|
@ -121,17 +123,19 @@ namespace MWMechanics
|
|||
* 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();
|
||||
ESM::Position pos = actor.getRefData().getPosition();
|
||||
const ESM::Position pos = actor.getRefData().getPosition();
|
||||
|
||||
if(mDistSameSpot == -1)
|
||||
mDistSameSpot = DIST_SAME_SPOT * cls.getSpeed(actor) * scaleMinimumDistance;
|
||||
if (mDistSameSpot == -1)
|
||||
{
|
||||
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;
|
||||
|
||||
bool samePosition = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2() < distSameSpot * distSameSpot;
|
||||
const float distSameSpot = mDistSameSpot * duration;
|
||||
const float squaredMovedDistance = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2();
|
||||
const bool samePosition = squaredMovedDistance < distSameSpot * distSameSpot;
|
||||
|
||||
// update position
|
||||
mPrevX = pos.pos[0];
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace MWMechanics
|
|||
bool isEvading() const;
|
||||
|
||||
// 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
|
||||
void takeEvasiveAction(MWMechanics::Movement& actorMovement) const;
|
||||
|
|
|
@ -269,6 +269,13 @@ namespace MWMechanics
|
|||
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,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
|
||||
{
|
||||
|
@ -280,6 +287,16 @@ namespace MWMechanics
|
|||
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,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
|
||||
const DetourNavigator::Flags flags)
|
||||
|
|
|
@ -72,9 +72,14 @@ namespace MWMechanics
|
|||
mCell = nullptr;
|
||||
}
|
||||
|
||||
void buildStraightPath(const osg::Vec3f& endPoint);
|
||||
|
||||
void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
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,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
|
||||
const DetourNavigator::Flags flags);
|
||||
|
|
|
@ -170,7 +170,7 @@ namespace MWMechanics
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -127,7 +127,9 @@ namespace MWMechanics
|
|||
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)
|
||||
rating *= weapon->mData.mSpeed;
|
||||
|
|
|
@ -242,7 +242,8 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
|
|||
{
|
||||
osg::Vec4f glowColor = getEnchantmentColor(*weapon);
|
||||
mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor);
|
||||
resetControllers(mScabbard->getNode());
|
||||
if (mScabbard)
|
||||
resetControllers(mScabbard->getNode());
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
|
@ -1798,17 +1798,13 @@ namespace MWRender
|
|||
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));
|
||||
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);
|
||||
|
||||
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mObjectRoot->setStateSet(nullptr);
|
||||
|
||||
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
|
||||
}
|
||||
|
||||
setRenderBin();
|
||||
|
|
|
@ -329,7 +329,7 @@ namespace MWRender
|
|||
camera->setViewMatrix(osg::Matrix::identity());
|
||||
camera->setProjectionMatrix(osg::Matrix::identity());
|
||||
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
|
||||
camera->setViewport(x, y, width, height);
|
||||
|
||||
|
|
|
@ -12,16 +12,15 @@ namespace MWRender
|
|||
{
|
||||
|
||||
LandManager::LandManager(int loadFlags)
|
||||
: ResourceManager(nullptr)
|
||||
: GenericResourceManager<std::pair<int, int> >(nullptr)
|
||||
, mLoadFlags(loadFlags)
|
||||
{
|
||||
mCache = new CacheType;
|
||||
}
|
||||
|
||||
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(idstr);
|
||||
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(std::make_pair(x,y));
|
||||
if (obj)
|
||||
return static_cast<ESMTerrain::LandObject*>(obj.get());
|
||||
else
|
||||
|
@ -30,7 +29,7 @@ osg::ref_ptr<ESMTerrain::LandObject> LandManager::getLand(int x, int y)
|
|||
if (!land)
|
||||
return nullptr;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <osg/Object>
|
||||
|
||||
#include <components/resource/objectcache.hpp>
|
||||
#include <components/resource/resourcemanager.hpp>
|
||||
#include <components/esmterrain/storage.hpp>
|
||||
|
||||
|
@ -14,7 +15,7 @@ namespace ESM
|
|||
namespace MWRender
|
||||
{
|
||||
|
||||
class LandManager : public Resource::ResourceManager
|
||||
class LandManager : public Resource::GenericResourceManager<std::pair<int, int> >
|
||||
{
|
||||
public:
|
||||
LandManager(int loadFlags);
|
||||
|
|
|
@ -613,7 +613,8 @@ void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orient
|
|||
if (!segment.mFogOfWarImage || !segment.mMapTexture)
|
||||
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 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);
|
||||
|
||||
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;
|
||||
segment.mFogOfWarImage->dirty();
|
||||
if (changed)
|
||||
{
|
||||
segment.mHasFogState = true;
|
||||
segment.mFogOfWarImage->dirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,10 +54,8 @@ std::string getVampireHead(const std::string& race, bool female)
|
|||
if (sVampireMapping.find(thisCombination) == sVampireMapping.end())
|
||||
{
|
||||
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>())
|
||||
{
|
||||
const ESM::BodyPart& bodypart = *it;
|
||||
if (!bodypart.mData.mVampire)
|
||||
continue;
|
||||
if (bodypart.mData.mType != ESM::BodyPart::MT_Skin)
|
||||
|
@ -68,12 +66,11 @@ std::string getVampireHead(const std::string& race, bool female)
|
|||
continue;
|
||||
if (!Misc::StringUtils::ciEqual(bodypart.mRace, race))
|
||||
continue;
|
||||
sVampireMapping[thisCombination] = &*it;
|
||||
sVampireMapping[thisCombination] = &bodypart;
|
||||
}
|
||||
}
|
||||
|
||||
if (sVampireMapping.find(thisCombination) == sVampireMapping.end())
|
||||
sVampireMapping[thisCombination] = nullptr;
|
||||
sVampireMapping.emplace(thisCombination, nullptr);
|
||||
|
||||
const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination];
|
||||
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()
|
||||
{
|
||||
NpcAnimation::PartBoneMap result;
|
||||
|
@ -283,7 +292,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> par
|
|||
mViewMode(viewMode),
|
||||
mShowWeapons(false),
|
||||
mShowCarriedLeft(true),
|
||||
mNpcType(Type_Normal),
|
||||
mNpcType(getNpcType()),
|
||||
mFirstPersonFieldOfView(firstPersonFieldOfView),
|
||||
mSoundsDisabled(disableSounds),
|
||||
mAccurateAiming(false),
|
||||
|
@ -431,41 +440,39 @@ void NpcAnimation::updateNpcBase()
|
|||
|
||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||
const ESM::Race *race = store.get<ESM::Race>().find(mNpc->mRace);
|
||||
bool isWerewolf = (mNpcType == Type_Werewolf);
|
||||
bool isVampire = (mNpcType == Type_Vampire);
|
||||
NpcType curType = getNpcType();
|
||||
bool isWerewolf = (curType == Type_Werewolf);
|
||||
bool isVampire = (curType == Type_Vampire);
|
||||
bool isFemale = !mNpc->isMale();
|
||||
|
||||
if (isWerewolf)
|
||||
{
|
||||
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 << "'";
|
||||
}
|
||||
mHeadModel.clear();
|
||||
mHairModel.clear();
|
||||
|
||||
mHairModel = "";
|
||||
if (!mNpc->mHair.empty())
|
||||
{
|
||||
const ESM::BodyPart* bp = store.get<ESM::BodyPart>().search(mNpc->mHair);
|
||||
if (bp)
|
||||
mHairModel = "meshes\\" + bp->mModel;
|
||||
else
|
||||
Log(Debug::Warning) << "Warning: Failed to load body part '" << mNpc->mHair << "'";
|
||||
}
|
||||
std::string headName = isWerewolf ? "WerewolfHead" : mNpc->mHead;
|
||||
std::string hairName = isWerewolf ? "WerewolfHair" : mNpc->mHair;
|
||||
|
||||
if (!headName.empty())
|
||||
{
|
||||
const ESM::BodyPart* bp = store.get<ESM::BodyPart>().search(headName);
|
||||
if (bp)
|
||||
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 isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0;
|
||||
|
||||
|
@ -473,7 +480,7 @@ void NpcAnimation::updateNpcBase()
|
|||
defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS());
|
||||
|
||||
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());
|
||||
|
||||
setObjectRoot(smodel, true, true, false);
|
||||
|
@ -517,14 +524,7 @@ void NpcAnimation::updateParts()
|
|||
if (!mObjectRoot.get())
|
||||
return;
|
||||
|
||||
const MWWorld::Class &cls = mPtr.getClass();
|
||||
|
||||
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;
|
||||
|
||||
NpcType curType = getNpcType();
|
||||
if (curType != mNpcType)
|
||||
{
|
||||
mNpcType = curType;
|
||||
|
@ -632,7 +632,7 @@ void NpcAnimation::updateParts()
|
|||
showWeapons(mShowWeapons);
|
||||
showCarriedLeft(mShowCarriedLeft);
|
||||
|
||||
bool isWerewolf = (mNpcType == Type_Werewolf);
|
||||
bool isWerewolf = (getNpcType() == Type_Werewolf);
|
||||
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);
|
||||
|
@ -649,9 +649,6 @@ void NpcAnimation::updateParts()
|
|||
|
||||
if (wasArrowAttached)
|
||||
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)
|
||||
{
|
||||
return (bodypart->mId.size() >= 3)
|
||||
&& bodypart->mId[bodypart->mId.size()-3] == '1'
|
||||
&& bodypart->mId[bodypart->mId.size()-2] == 's'
|
||||
&& bodypart->mId[bodypart->mId.size()-1] == 't';
|
||||
return bodypart->mId.size() >= 3 && bodypart->mId.substr(bodypart->mId.size()-3, 3) == "1st";
|
||||
}
|
||||
|
||||
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);
|
||||
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"))
|
||||
mHeadAnimationTime->setTalkStart(it->first);
|
||||
if (Misc::StringUtils::ciEqual(it->second, "talk: stop"))
|
||||
mHeadAnimationTime->setTalkStop(it->first);
|
||||
if (Misc::StringUtils::ciEqual(it->second, "blink: start"))
|
||||
mHeadAnimationTime->setBlinkStart(it->first);
|
||||
if (Misc::StringUtils::ciEqual(it->second, "blink: stop"))
|
||||
mHeadAnimationTime->setBlinkStop(it->first);
|
||||
if (Misc::StringUtils::ciEqual(key.second, "talk: start"))
|
||||
mHeadAnimationTime->setTalkStart(key.first);
|
||||
if (Misc::StringUtils::ciEqual(key.second, "talk: stop"))
|
||||
mHeadAnimationTime->setTalkStop(key.first);
|
||||
if (Misc::StringUtils::ciEqual(key.second, "blink: start"))
|
||||
mHeadAnimationTime->setBlinkStart(key.first);
|
||||
if (Misc::StringUtils::ciEqual(key.second, "blink: stop"))
|
||||
mHeadAnimationTime->setBlinkStop(key.first);
|
||||
}
|
||||
|
||||
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 char *ext = (mViewMode == VM_FirstPerson) ? ".1st" : "";
|
||||
std::vector<ESM::PartReference>::const_iterator part(parts.begin());
|
||||
for(;part != parts.end();++part)
|
||||
for(const ESM::PartReference& part : parts)
|
||||
{
|
||||
const ESM::BodyPart *bodypart = 0;
|
||||
if(!mNpc->isMale() && !part->mFemale.empty())
|
||||
const ESM::BodyPart *bodypart = nullptr;
|
||||
if(!mNpc->isMale() && !part.mFemale.empty())
|
||||
{
|
||||
bodypart = partStore.search(part->mFemale+ext);
|
||||
bodypart = partStore.search(part.mFemale+ext);
|
||||
if(!bodypart && mViewMode == VM_FirstPerson)
|
||||
{
|
||||
bodypart = partStore.search(part->mFemale);
|
||||
bodypart = partStore.search(part.mFemale);
|
||||
if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand ||
|
||||
bodypart->mData.mPart == ESM::BodyPart::MP_Wrist ||
|
||||
bodypart->mData.mPart == ESM::BodyPart::MP_Forearm ||
|
||||
|
@ -845,14 +838,14 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM::
|
|||
bodypart = nullptr;
|
||||
}
|
||||
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)
|
||||
{
|
||||
bodypart = partStore.search(part->mMale);
|
||||
bodypart = partStore.search(part.mMale);
|
||||
if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand ||
|
||||
bodypart->mData.mPart == ESM::BodyPart::MP_Wrist ||
|
||||
bodypart->mData.mPart == ESM::BodyPart::MP_Forearm ||
|
||||
|
@ -860,13 +853,13 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM::
|
|||
bodypart = nullptr;
|
||||
}
|
||||
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)
|
||||
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
|
||||
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();
|
||||
mFirstPersonNeckController = new NeckController(mObjectRoot.get());
|
||||
node->addUpdateCallback(mFirstPersonNeckController);
|
||||
mActiveControllers.insert(std::make_pair(node, mFirstPersonNeckController));
|
||||
mActiveControllers.emplace(node, mFirstPersonNeckController);
|
||||
}
|
||||
}
|
||||
else if (mViewMode == VM_Normal)
|
||||
|
@ -918,9 +911,6 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
|||
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
|
||||
{
|
||||
|
@ -932,10 +922,6 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
|||
|
||||
updateHolsteredWeapon(!mShowWeapons);
|
||||
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)
|
||||
|
@ -953,8 +939,6 @@ void NpcAnimation::showCarriedLeft(bool show)
|
|||
if (iter->getTypeName() == typeid(ESM::Light).name() && mObjectParts[ESM::PRT_Shield])
|
||||
addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), iter->get<ESM::Light>()->mBase);
|
||||
}
|
||||
if (mAlpha != 1.f)
|
||||
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
|
||||
}
|
||||
else
|
||||
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)];
|
||||
|
||||
typedef std::multimap<ESM::BodyPart::MeshPart,ESM::PartReferenceType> BodyPartMapType;
|
||||
static BodyPartMapType sBodyPartMap;
|
||||
if(sBodyPartMap.empty())
|
||||
static const BodyPartMapType sBodyPartMap =
|
||||
{
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Neck, ESM::PRT_Neck));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Groin, ESM::PRT_Groin));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_RHand));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_LHand));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_RFoot));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_LFoot));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_RKnee));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_LKnee));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg));
|
||||
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Tail, ESM::PRT_Tail));
|
||||
}
|
||||
{ESM::BodyPart::MP_Neck, ESM::PRT_Neck},
|
||||
{ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass},
|
||||
{ESM::BodyPart::MP_Groin, ESM::PRT_Groin},
|
||||
{ESM::BodyPart::MP_Hand, ESM::PRT_RHand},
|
||||
{ESM::BodyPart::MP_Hand, ESM::PRT_LHand},
|
||||
{ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist},
|
||||
{ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist},
|
||||
{ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm},
|
||||
{ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm},
|
||||
{ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm},
|
||||
{ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm},
|
||||
{ESM::BodyPart::MP_Foot, ESM::PRT_RFoot},
|
||||
{ESM::BodyPart::MP_Foot, ESM::PRT_LFoot},
|
||||
{ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle},
|
||||
{ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle},
|
||||
{ESM::BodyPart::MP_Knee, ESM::PRT_RKnee},
|
||||
{ESM::BodyPart::MP_Knee, ESM::PRT_LKnee},
|
||||
{ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg},
|
||||
{ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg},
|
||||
{ESM::BodyPart::MP_Tail, ESM::PRT_Tail}
|
||||
};
|
||||
|
||||
parts.resize(ESM::PRT_Count, nullptr);
|
||||
|
||||
if (werewolf)
|
||||
return parts;
|
||||
|
||||
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)
|
||||
continue;
|
||||
if (bodypart.mData.mType != ESM::BodyPart::MT_Skin)
|
||||
|
|
|
@ -74,6 +74,8 @@ private:
|
|||
|
||||
void updateNpcBase();
|
||||
|
||||
NpcType getNpcType();
|
||||
|
||||
PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename,
|
||||
const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor=nullptr);
|
||||
|
||||
|
|
|
@ -314,6 +314,7 @@ namespace MWRender
|
|||
|
||||
mTerrain->setDefaultViewer(mViewer->getCamera());
|
||||
mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells"));
|
||||
mTerrain->setWorkQueue(mWorkQueue.get());
|
||||
|
||||
mCamera.reset(new Camera(mViewer->getCamera()));
|
||||
|
||||
|
@ -371,8 +372,10 @@ namespace MWRender
|
|||
|
||||
mNearClip = Settings::Manager::getFloat("near clip", "Camera");
|
||||
mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera");
|
||||
mFieldOfView = Settings::Manager::getFloat("field of view", "Camera");
|
||||
mFirstPersonFieldOfView = Settings::Manager::getFloat("first person field of view", "Camera");
|
||||
float fov = Settings::Manager::getFloat("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);
|
||||
|
||||
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip));
|
||||
|
@ -1198,6 +1201,12 @@ namespace MWRender
|
|||
|
||||
mUniformNear->set(mNearClip);
|
||||
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()
|
||||
|
@ -1446,8 +1455,8 @@ namespace MWRender
|
|||
{
|
||||
try
|
||||
{
|
||||
const auto locked = it->second.lockConst();
|
||||
mNavMesh->update(locked->getValue(), mNavMeshNumber, locked->getGeneration(),
|
||||
const auto locked = it->second->lockConst();
|
||||
mNavMesh->update(locked->getImpl(), mNavMeshNumber, locked->getGeneration(),
|
||||
locked->getNavMeshRevision(), mNavigator.getSettings());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
|
|
|
@ -22,6 +22,15 @@ namespace MWRender
|
|||
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)
|
||||
{
|
||||
minX = 0, minY = 0, maxX = 0, maxY = 0;
|
||||
|
|
|
@ -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();
|
||||
|
||||
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY);
|
||||
virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
|
||||
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY) override;
|
||||
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
|
||||
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;
|
||||
|
||||
|
|
|
@ -1070,7 +1070,7 @@ namespace MWScript
|
|||
|
||||
const std::string script = ptr.getClass().getScript(ptr);
|
||||
if(script.empty())
|
||||
str<< ptr.getCellRef().getRefId()<<" does not have a script.";
|
||||
str<< ptr.getCellRef().getRefId()<<" does not have a script.";
|
||||
else
|
||||
{
|
||||
str<< "Local variables for "<<ptr.getCellRef().getRefId();
|
||||
|
|
|
@ -285,11 +285,11 @@ namespace MWScript
|
|||
MWWorld::Ptr updated = ptr;
|
||||
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")
|
||||
{
|
||||
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")
|
||||
{
|
||||
|
@ -304,7 +304,7 @@ namespace MWScript
|
|||
pos = terrainHeight;
|
||||
}
|
||||
|
||||
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos);
|
||||
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos,true);
|
||||
}
|
||||
else
|
||||
throw std::runtime_error ("invalid axis: " + axis);
|
||||
|
@ -447,7 +447,7 @@ namespace MWScript
|
|||
}
|
||||
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);
|
||||
|
||||
|
|
|
@ -96,25 +96,25 @@ bool FFmpeg_Decoder::getAVAudioData()
|
|||
return false;
|
||||
|
||||
do {
|
||||
if(mPacket.size == 0 && !getNextPacket())
|
||||
return false;
|
||||
|
||||
/* Decode some data, and check for errors */
|
||||
int ret = 0;
|
||||
ret = avcodec_receive_frame(mCodecCtx, mFrame);
|
||||
if (ret == 0)
|
||||
got_frame = true;
|
||||
int ret = avcodec_receive_frame(mCodecCtx, mFrame);
|
||||
if (ret == AVERROR(EAGAIN))
|
||||
ret = 0;
|
||||
if (ret == 0)
|
||||
{
|
||||
if (mPacket.size == 0 && !getNextPacket())
|
||||
return false;
|
||||
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;
|
||||
|
||||
av_packet_unref(&mPacket);
|
||||
|
||||
if (!got_frame || mFrame->nb_samples == 0)
|
||||
if (mFrame->nb_samples == 0)
|
||||
continue;
|
||||
got_frame = true;
|
||||
|
||||
if(mSwr)
|
||||
{
|
||||
|
@ -138,7 +138,7 @@ bool FFmpeg_Decoder::getAVAudioData()
|
|||
else
|
||||
mFrameData = &mFrame->data[0];
|
||||
|
||||
} while(!got_frame || mFrame->nb_samples == 0);
|
||||
} while(!got_frame);
|
||||
mNextPts += (double)mFrame->nb_samples / mCodecCtx->sample_rate;
|
||||
|
||||
return true;
|
||||
|
|
|
@ -471,6 +471,36 @@ namespace MWSound
|
|||
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)
|
||||
{
|
||||
|
@ -1122,10 +1152,10 @@ namespace MWSound
|
|||
if(!mOutput->isInitialized())
|
||||
return;
|
||||
|
||||
updateSounds(duration);
|
||||
if (MWBase::Environment::get().getStateManager()->getState()!=
|
||||
MWBase::StateManager::State_NoGame)
|
||||
{
|
||||
updateSounds(duration);
|
||||
updateRegionSound(duration);
|
||||
updateWaterSound(duration);
|
||||
}
|
||||
|
|
|
@ -168,6 +168,9 @@ namespace MWSound
|
|||
///< Start playing music from the selected folder
|
||||
/// \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);
|
||||
///< Make an actor say some text.
|
||||
/// \param filename name of a sound file in "Sound/" in the data directory.
|
||||
|
|
|
@ -381,7 +381,7 @@ namespace MWWorld
|
|||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,16 +150,16 @@ MWWorld::CellStore *MWWorld::Cells::getInterior (const std::string& name)
|
|||
return &result->second;
|
||||
}
|
||||
|
||||
void MWWorld::Cells::rest ()
|
||||
void MWWorld::Cells::rest (double hours)
|
||||
{
|
||||
for (auto &interior : mInteriors)
|
||||
{
|
||||
interior.second.rest();
|
||||
interior.second.rest(hours);
|
||||
}
|
||||
|
||||
for (auto &exterior : mExteriors)
|
||||
{
|
||||
exterior.second.rest();
|
||||
exterior.second.rest(hours);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ namespace MWWorld
|
|||
|
||||
/// @note name must be lower case
|
||||
Ptr getPtr (const std::string& name);
|
||||
void rest ();
|
||||
void rest (double hours);
|
||||
|
||||
/// Get all Ptrs referencing \a name in exterior cells
|
||||
/// @note Due to the current implementation of getPtr this only supports one Ptr per cell.
|
||||
|
|
|
@ -1152,7 +1152,7 @@ namespace MWWorld
|
|||
}
|
||||
}
|
||||
|
||||
void CellStore::rest()
|
||||
void CellStore::rest(double hours)
|
||||
{
|
||||
if (mState == State_Loaded)
|
||||
{
|
||||
|
@ -1161,7 +1161,7 @@ namespace MWWorld
|
|||
Ptr ptr = getCurrentPtr(&*it);
|
||||
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)
|
||||
|
@ -1169,7 +1169,7 @@ namespace MWWorld
|
|||
Ptr ptr = getCurrentPtr(&*it);
|
||||
if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
|
||||
{
|
||||
MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, true);
|
||||
MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -183,7 +183,7 @@ namespace MWWorld
|
|||
/// @return updated MWWorld::Ptr with the new CellStore pointer set.
|
||||
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.
|
||||
/// @note If you get a linker error here, this means the given type can not be inserted into a cell.
|
||||
|
|
|
@ -360,7 +360,7 @@ namespace MWWorld
|
|||
{
|
||||
if (const auto object = mPhysics->getObject(ptr))
|
||||
navigator->removeObject(DetourNavigator::ObjectId(object));
|
||||
else if (const auto actor = mPhysics->getActor(ptr))
|
||||
else if (mPhysics->getActor(ptr))
|
||||
{
|
||||
navigator->removeAgent(world->getPathfindingHalfExtents(ptr));
|
||||
mRendering.removeActorPath(ptr);
|
||||
|
@ -892,7 +892,7 @@ namespace MWWorld
|
|||
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
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));
|
||||
}
|
||||
|
|
|
@ -387,6 +387,21 @@ namespace MWWorld
|
|||
|
||||
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 <exl = mStatic[plugin];
|
||||
if(lt.mIndex + 1 > (int)ltexl.size())
|
||||
ltexl.resize(lt.mIndex+1);
|
||||
|
|
|
@ -178,12 +178,12 @@ namespace MWWorld
|
|||
const Files::Collections& fileCollections,
|
||||
const std::vector<std::string>& contentFiles,
|
||||
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)
|
||||
: mResourceSystem(resourceSystem), mFallback(fallbackMap), mLocalScripts (mStore),
|
||||
mSky (true), mCells (mStore, mEsm),
|
||||
mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles), mUserDataPath(userDataPath),
|
||||
mActivationDistanceOverride (activationDistanceOverride), mStartupScript(startupScript),
|
||||
mActivationDistanceOverride (activationDistanceOverride),
|
||||
mStartCell (startCell), mDistanceToFacedObject(-1), mTeleportEnabled(true),
|
||||
mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0),
|
||||
mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f)
|
||||
|
@ -337,9 +337,6 @@ namespace MWWorld
|
|||
if (!mPhysics->toggleCollisionMode())
|
||||
mPhysics->toggleCollisionMode();
|
||||
|
||||
if (!mStartupScript.empty())
|
||||
MWBase::Environment::get().getWindowManager()->executeInConsole(mStartupScript);
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->updatePlayer();
|
||||
}
|
||||
|
||||
|
@ -503,7 +500,7 @@ namespace MWWorld
|
|||
gmst["iWereWolfBounty"] = ESM::Variant(1000);
|
||||
gmst["fCombatDistanceWerewolfMod"] = ESM::Variant(0.3f);
|
||||
|
||||
for (const std::pair<std::string, ESM::Variant> ¶ms : gmst)
|
||||
for (const auto ¶ms : gmst)
|
||||
{
|
||||
if (!mStore.get<ESM::GameSetting>().search(params.first))
|
||||
{
|
||||
|
@ -533,7 +530,7 @@ namespace MWWorld
|
|||
globals["crimegoldturnin"] = ESM::Variant(0);
|
||||
globals["pchasturnin"] = ESM::Variant(0);
|
||||
|
||||
for (const std::pair<std::string, ESM::Variant> ¶ms : globals)
|
||||
for (const auto ¶ms : globals)
|
||||
{
|
||||
if (!mStore.get<ESM::Global>().search(params.first))
|
||||
{
|
||||
|
@ -552,7 +549,7 @@ namespace MWWorld
|
|||
statics["templemarker"] = "marker_temple.nif";
|
||||
statics["travelmarker"] = "marker_travel.nif";
|
||||
|
||||
for (const std::pair<std::string, std::string> ¶ms : statics)
|
||||
for (const auto ¶ms : statics)
|
||||
{
|
||||
if (!mStore.get<ESM::Static>().search(params.first))
|
||||
{
|
||||
|
@ -566,7 +563,7 @@ namespace MWWorld
|
|||
std::map<std::string, std::string> doors;
|
||||
doors["prisonmarker"] = "marker_prison.nif";
|
||||
|
||||
for (const std::pair<std::string, std::string> ¶ms : doors)
|
||||
for (const auto ¶ms : doors)
|
||||
{
|
||||
if (!mStore.get<ESM::Door>().search(params.first))
|
||||
{
|
||||
|
@ -1512,23 +1509,24 @@ namespace MWWorld
|
|||
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()) {
|
||||
int cellX, cellY;
|
||||
positionToIndex(x, y, cellX, cellY);
|
||||
CellStore* cell = ptr.getCell();
|
||||
CellStore* newCell = getExterior(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);
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -3698,9 +3696,9 @@ namespace MWWorld
|
|||
return closestMarker;
|
||||
}
|
||||
|
||||
void World::rest()
|
||||
void World::rest(double hours)
|
||||
{
|
||||
mCells.rest();
|
||||
mCells.rest(hours);
|
||||
}
|
||||
|
||||
void World::teleportToClosestMarker (const MWWorld::Ptr& ptr,
|
||||
|
|
|
@ -120,8 +120,6 @@ namespace MWWorld
|
|||
|
||||
int mActivationDistanceOverride;
|
||||
|
||||
std::string mStartupScript;
|
||||
|
||||
std::map<MWWorld::Ptr, int> mDoorStates;
|
||||
///< 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);
|
||||
|
||||
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
|
||||
|
||||
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 std::vector<std::string>& contentFiles,
|
||||
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();
|
||||
|
||||
|
@ -499,7 +497,7 @@ namespace MWWorld
|
|||
|
||||
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
|
||||
|
||||
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;
|
||||
///< check if the player is allowed to rest
|
||||
|
||||
void rest() override;
|
||||
void rest(double hours) override;
|
||||
|
||||
/// \todo Probably shouldn't be here
|
||||
MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) override;
|
||||
|
|
|
@ -80,14 +80,6 @@ namespace
|
|||
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)
|
||||
{
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
|
|
|
@ -125,7 +125,7 @@ namespace Compiler
|
|||
|
||||
if (loop.size()!=loop2.size())
|
||||
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));
|
||||
|
||||
|
@ -179,6 +179,14 @@ namespace Compiler
|
|||
scanner.scan (mLineParser);
|
||||
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);
|
||||
}
|
||||
|
@ -207,8 +215,7 @@ namespace Compiler
|
|||
return true;
|
||||
}
|
||||
}
|
||||
else if (mState==IfBodyState || mState==IfElseifBodyState || mState==IfElseBodyState ||
|
||||
mState==IfElseJunkState)
|
||||
else if (mState==IfBodyState || mState==IfElseifBodyState || mState==IfElseBodyState)
|
||||
{
|
||||
if (parseIfBody (keyword, loc, scanner))
|
||||
return true;
|
||||
|
@ -218,6 +225,14 @@ namespace Compiler
|
|||
if ( parseWhileBody (keyword, loc, scanner))
|
||||
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);
|
||||
}
|
||||
|
@ -250,8 +265,9 @@ namespace Compiler
|
|||
default: ;
|
||||
}
|
||||
}
|
||||
else if (code==Scanner::S_open && mState==IfElseJunkState)
|
||||
else if (mState==IfElseJunkState)
|
||||
{
|
||||
getErrorHandler().warning ("Extra text after else", loc);
|
||||
SkipParser skip (getErrorHandler(), getContext());
|
||||
scanner.scan (skip);
|
||||
mState = IfElseBodyState;
|
||||
|
|
|
@ -22,20 +22,20 @@ bool Compiler::DeclarationParser::parseName (const std::string& name, const Toke
|
|||
char type = mLocals.getType (name2);
|
||||
|
||||
if (type!=' ')
|
||||
{
|
||||
/// \todo add option to make re-declared local variables an error
|
||||
getErrorHandler().warning ("ignoring local variable re-declaration",
|
||||
loc);
|
||||
|
||||
mState = State_End;
|
||||
return true;
|
||||
}
|
||||
|
||||
mLocals.declare (mType, name2);
|
||||
getErrorHandler().warning ("Local variable re-declaration", loc);
|
||||
else
|
||||
mLocals.declare (mType, name2);
|
||||
|
||||
mState = State_End;
|
||||
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);
|
||||
}
|
||||
|
@ -61,17 +61,31 @@ bool Compiler::DeclarationParser::parseKeyword (int keyword, const TokenLoc& loc
|
|||
else if (mState==State_Name)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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 Parser::parseSpecial (code, loc, scanner);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace Compiler
|
|||
{
|
||||
public:
|
||||
|
||||
virtual const char *what() const throw() { return "compile error";}
|
||||
virtual const char *what() const throw() { return "Compile error";}
|
||||
///< Return error message
|
||||
};
|
||||
|
||||
|
@ -21,7 +21,7 @@ namespace Compiler
|
|||
{
|
||||
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
|
||||
};
|
||||
|
||||
|
@ -31,7 +31,7 @@ namespace Compiler
|
|||
{
|
||||
public:
|
||||
|
||||
virtual const char *what() const throw() { return "end of file"; }
|
||||
virtual const char *what() const throw() { return "End of file"; }
|
||||
///< Return error message
|
||||
};
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ namespace Compiler
|
|||
else if (t1=='f' || t2=='f')
|
||||
mOperands.push_back ('f');
|
||||
else
|
||||
throw std::logic_error ("failed to determine result operand type");
|
||||
throw std::logic_error ("Failed to determine result operand type");
|
||||
}
|
||||
|
||||
void ExprParser::pop()
|
||||
|
@ -158,7 +158,7 @@ namespace Compiler
|
|||
|
||||
default:
|
||||
|
||||
throw std::logic_error ("unknown operator");
|
||||
throw std::logic_error ("Unknown operator");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,7 +287,7 @@ namespace Compiler
|
|||
else
|
||||
{
|
||||
mExplicit.clear();
|
||||
getErrorHandler().warning ("Ignoring stray explicit reference", loc);
|
||||
getErrorHandler().warning ("Stray explicit reference", loc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -430,7 +430,7 @@ namespace Compiler
|
|||
{
|
||||
if (!hasExplicit)
|
||||
{
|
||||
getErrorHandler().warning ("ignoring stray explicit reference", loc);
|
||||
getErrorHandler().warning ("Stray explicit reference", loc);
|
||||
mExplicit.clear();
|
||||
}
|
||||
|
||||
|
@ -735,13 +735,13 @@ namespace Compiler
|
|||
{
|
||||
if (mOperands.empty() && mOperators.empty())
|
||||
{
|
||||
getErrorHandler().error ("missing expression", mTokenLoc);
|
||||
getErrorHandler().error ("Missing expression", mTokenLoc);
|
||||
return 'l';
|
||||
}
|
||||
|
||||
if (mNextOperand || mOperands.empty())
|
||||
{
|
||||
getErrorHandler().error ("syntax error in expression", mTokenLoc);
|
||||
getErrorHandler().error ("Syntax error in expression", mTokenLoc);
|
||||
return 'l';
|
||||
}
|
||||
|
||||
|
@ -799,7 +799,7 @@ namespace Compiler
|
|||
++optionalCount;
|
||||
}
|
||||
else
|
||||
getErrorHandler().warning ("ignoring extra argument",
|
||||
getErrorHandler().warning ("Extra argument",
|
||||
stringParser.getTokenLoc());
|
||||
}
|
||||
else if (*iter=='X')
|
||||
|
@ -813,7 +813,7 @@ namespace Compiler
|
|||
if (parser.isEmpty())
|
||||
break;
|
||||
else
|
||||
getErrorHandler().warning("ignoring extra argument", parser.getTokenLoc());
|
||||
getErrorHandler().warning("Extra argument", parser.getTokenLoc());
|
||||
}
|
||||
else if (*iter=='z')
|
||||
{
|
||||
|
@ -825,7 +825,7 @@ namespace Compiler
|
|||
if (discardParser.isEmpty())
|
||||
break;
|
||||
else
|
||||
getErrorHandler().warning("ignoring extra argument", discardParser.getTokenLoc());
|
||||
getErrorHandler().warning("Extra argument", discardParser.getTokenLoc());
|
||||
}
|
||||
else if (*iter=='j')
|
||||
{
|
||||
|
|
|
@ -356,7 +356,7 @@ namespace Compiler
|
|||
opcodePlaySound3DExplicit);
|
||||
extensions.registerInstruction ("playsound3dvp", "cff", opcodePlaySound3DVP,
|
||||
opcodePlaySound3DVPExplicit);
|
||||
extensions.registerInstruction ("playloopsound3d", "c", opcodePlayLoopSound3D,
|
||||
extensions.registerInstruction ("playloopsound3d", "cXX", opcodePlayLoopSound3D,
|
||||
opcodePlayLoopSound3DExplicit);
|
||||
extensions.registerInstruction ("playloopsound3dvp", "cff", opcodePlayLoopSound3DVP,
|
||||
opcodePlayLoopSound3DVPExplicit);
|
||||
|
|
|
@ -98,7 +98,7 @@ namespace Compiler
|
|||
if (mState == BeginState)
|
||||
{
|
||||
if (code != Scanner::S_newline)
|
||||
reportWarning ("Ignoring stray special character before begin statement", loc);
|
||||
reportWarning ("Stray special character before begin statement", loc);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
if (keyword==mIgnoreKeyword)
|
||||
reportWarning ("ignoring found junk", loc);
|
||||
reportWarning ("Ignoring found junk", loc);
|
||||
else
|
||||
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)
|
||||
{
|
||||
if (code==Scanner::S_member)
|
||||
reportWarning ("ignoring found junk", loc);
|
||||
reportWarning ("Ignoring found junk", loc);
|
||||
else
|
||||
scanner.putbackSpecial (code, loc);
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace Compiler
|
|||
|
||||
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)
|
||||
{
|
||||
getErrorHandler().warning ("ignoring stray string argument", loc);
|
||||
getErrorHandler().warning ("Stray string argument", loc);
|
||||
mState = EndState;
|
||||
return true;
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ namespace Compiler
|
|||
return true;
|
||||
}
|
||||
|
||||
getErrorHandler().error ("unknown variable", loc);
|
||||
getErrorHandler().error ("Unknown variable", loc);
|
||||
SkipParser skip (getErrorHandler(), getContext());
|
||||
scanner.scan (skip);
|
||||
return false;
|
||||
|
@ -233,7 +233,7 @@ namespace Compiler
|
|||
|
||||
if (mState==SetPotentialMemberVarState && keyword==Scanner::K_to)
|
||||
{
|
||||
getErrorHandler().warning ("unknown variable, ignoring set instruction", loc);
|
||||
getErrorHandler().warning ("Unknown variable", loc);
|
||||
SkipParser skip (getErrorHandler(), getContext());
|
||||
scanner.scan (skip);
|
||||
return false;
|
||||
|
@ -286,7 +286,7 @@ namespace Compiler
|
|||
{
|
||||
if (!hasExplicit && mState==ExplicitState)
|
||||
{
|
||||
getErrorHandler().warning ("ignoring stray explicit reference", loc);
|
||||
getErrorHandler().warning ("Stray explicit reference", loc);
|
||||
mExplicit.clear();
|
||||
}
|
||||
|
||||
|
@ -344,7 +344,7 @@ namespace Compiler
|
|||
{
|
||||
if (!hasExplicit && !mExplicit.empty())
|
||||
{
|
||||
getErrorHandler().warning ("ignoring stray explicit reference", loc);
|
||||
getErrorHandler().warning ("Stray explicit reference", loc);
|
||||
mExplicit.clear();
|
||||
}
|
||||
|
||||
|
@ -360,7 +360,7 @@ namespace Compiler
|
|||
if (mState==ExplicitState)
|
||||
{
|
||||
// drop stray explicit reference
|
||||
getErrorHandler().warning ("ignoring stray explicit reference", loc);
|
||||
getErrorHandler().warning ("Stray explicit reference", loc);
|
||||
mState = BeginState;
|
||||
mExplicit.clear();
|
||||
}
|
||||
|
@ -375,8 +375,7 @@ namespace Compiler
|
|||
{
|
||||
if (!getContext().canDeclareLocals())
|
||||
{
|
||||
getErrorHandler().error (
|
||||
"local variables can't be declared in this context", loc);
|
||||
getErrorHandler().error("Local variables cannot be declared in this context", loc);
|
||||
SkipParser skip (getErrorHandler(), getContext());
|
||||
scanner.scan (skip);
|
||||
return true;
|
||||
|
@ -412,19 +411,19 @@ namespace Compiler
|
|||
|
||||
case Scanner::K_else:
|
||||
|
||||
getErrorHandler().warning ("ignoring stray else", loc);
|
||||
getErrorHandler().warning ("Stray else", loc);
|
||||
mState = EndState;
|
||||
return true;
|
||||
|
||||
case Scanner::K_endif:
|
||||
|
||||
getErrorHandler().warning ("ignoring stray endif", loc);
|
||||
getErrorHandler().warning ("Stray endif", loc);
|
||||
mState = EndState;
|
||||
return true;
|
||||
|
||||
case Scanner::K_begin:
|
||||
|
||||
getErrorHandler().warning ("ignoring stray begin", loc);
|
||||
getErrorHandler().warning ("Stray begin", loc);
|
||||
mState = EndState;
|
||||
return true;
|
||||
}
|
||||
|
@ -491,7 +490,7 @@ namespace Compiler
|
|||
{
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
@ -508,7 +507,7 @@ namespace Compiler
|
|||
|
||||
if (code==Scanner::S_ref && mState==SetPotentialMemberVarState)
|
||||
{
|
||||
getErrorHandler().warning ("Ignoring stray explicit reference", loc);
|
||||
getErrorHandler().warning ("Stray explicit reference", loc);
|
||||
mState = SetState;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace Compiler
|
|||
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
|
||||
|
@ -48,7 +48,7 @@ namespace Compiler
|
|||
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
|
||||
|
|
|
@ -158,7 +158,7 @@ namespace Compiler
|
|||
TokenLoc loc (mLoc);
|
||||
mLoc.mLiteral.clear();
|
||||
|
||||
mErrorHandler.error ("syntax error", loc);
|
||||
mErrorHandler.error ("Syntax error", loc);
|
||||
throw SourceException();
|
||||
}
|
||||
|
||||
|
@ -521,7 +521,7 @@ namespace Compiler
|
|||
else if (c == '<' || c == '>') // Treat <> and << as <
|
||||
{
|
||||
special = S_cmpLT;
|
||||
mErrorHandler.warning (std::string("invalid operator <") + c + ", treating it as <", mLoc);
|
||||
mErrorHandler.warning ("Invalid operator, treating it as <", mLoc);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -549,7 +549,7 @@ namespace Compiler
|
|||
else if (c == '<' || c == '>') // Treat >< and >> as >
|
||||
{
|
||||
special = S_cmpGT;
|
||||
mErrorHandler.warning (std::string("invalid operator >") + c + ", treating it as >", mLoc);
|
||||
mErrorHandler.warning ("Invalid operator, treating it as >", mLoc);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <osg/Stats>
|
||||
|
||||
namespace
|
||||
{
|
||||
using DetourNavigator::ChangeType;
|
||||
|
@ -102,6 +104,20 @@ namespace DetourNavigator
|
|||
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()
|
||||
{
|
||||
log("start process jobs");
|
||||
|
@ -129,12 +145,17 @@ namespace DetourNavigator
|
|||
|
||||
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 playerTile = *mPlayerTile.lockConst();
|
||||
const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile);
|
||||
|
||||
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();
|
||||
|
||||
|
@ -143,7 +164,7 @@ namespace DetourNavigator
|
|||
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,
|
||||
" generation=", locked->getGeneration(),
|
||||
" revision=", locked->getNavMeshRevision(),
|
||||
|
@ -157,9 +178,7 @@ namespace DetourNavigator
|
|||
boost::optional<AsyncNavMeshUpdater::Job> AsyncNavMeshUpdater::getNextJob()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (mJobs.empty())
|
||||
mHasJob.wait_for(lock, std::chrono::milliseconds(10));
|
||||
if (mJobs.empty())
|
||||
if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), [&] { return !mJobs.empty(); }))
|
||||
{
|
||||
mFirstStart.lock()->reset();
|
||||
mDone.notify_all();
|
||||
|
@ -194,7 +213,8 @@ namespace DetourNavigator
|
|||
writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x())
|
||||
+ "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision);
|
||||
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)
|
||||
|
|
|
@ -44,11 +44,13 @@ namespace DetourNavigator
|
|||
|
||||
void wait();
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
||||
|
||||
private:
|
||||
struct Job
|
||||
{
|
||||
osg::Vec3f mAgentHalfExtents;
|
||||
SharedNavMeshCacheItem mNavMeshCacheItem;
|
||||
std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem;
|
||||
TilePosition mChangedTile;
|
||||
unsigned mTryNumber;
|
||||
ChangeType mChangeType;
|
||||
|
@ -72,7 +74,7 @@ namespace DetourNavigator
|
|||
std::reference_wrapper<TileCachedRecastMeshManager> mRecastMeshManager;
|
||||
std::reference_wrapper<OffMeshConnectionsManager> mOffMeshConnectionsManager;
|
||||
std::atomic_bool mShouldStop;
|
||||
std::mutex mMutex;
|
||||
mutable std::mutex mMutex;
|
||||
std::condition_variable mHasJob;
|
||||
std::condition_variable mDone;
|
||||
Jobs mJobs;
|
||||
|
|
|
@ -25,8 +25,6 @@ namespace
|
|||
{
|
||||
using namespace DetourNavigator;
|
||||
|
||||
static const int doNotTransferOwnership = 0;
|
||||
|
||||
void initPolyMeshDetail(rcPolyMeshDetail& value)
|
||||
{
|
||||
value.meshes = nullptr;
|
||||
|
@ -441,56 +439,7 @@ namespace
|
|||
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>
|
||||
unsigned long getMinValuableBitsNumber(const T value)
|
||||
|
@ -500,49 +449,6 @@ namespace
|
|||
++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
|
||||
|
@ -591,26 +497,13 @@ namespace DetourNavigator
|
|||
" playerTile=", 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 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)
|
||||
{
|
||||
log("ignore add tile: recastMesh is null");
|
||||
return removeTile();
|
||||
return navMeshCacheItem->lock()->removeTile(changedTile);
|
||||
}
|
||||
|
||||
auto recastMeshBounds = recastMesh->getBounds();
|
||||
|
@ -625,13 +518,13 @@ namespace DetourNavigator
|
|||
if (isEmpty(recastMeshBounds))
|
||||
{
|
||||
log("ignore add tile: recastMesh is empty");
|
||||
return removeTile();
|
||||
return navMeshCacheItem->lock()->removeTile(changedTile);
|
||||
}
|
||||
|
||||
if (!shouldAddTile(changedTile, playerTile, std::min(settings.mMaxTilesNumber, params.maxTiles)))
|
||||
{
|
||||
log("ignore add tile: too far from player");
|
||||
return removeTile();
|
||||
return navMeshCacheItem->lock()->removeTile(changedTile);
|
||||
}
|
||||
|
||||
auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections);
|
||||
|
@ -648,7 +541,7 @@ namespace DetourNavigator
|
|||
if (!navMeshData.mValue)
|
||||
{
|
||||
log("ignore add tile: NavMeshData is null");
|
||||
return removeTile();
|
||||
return navMeshCacheItem->lock()->removeTile(changedTile);
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -665,10 +558,10 @@ namespace DetourNavigator
|
|||
if (!cachedNavMeshData)
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,21 +20,6 @@ namespace DetourNavigator
|
|||
class RecastMesh;
|
||||
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)
|
||||
{
|
||||
return std::sqrt(float(osg::square(value.x()) + osg::square(value.y())));
|
||||
|
|
|
@ -174,7 +174,7 @@ namespace DetourNavigator
|
|||
if (!navMesh)
|
||||
return out;
|
||||
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, end), includeFlags, settings, out);
|
||||
}
|
||||
|
@ -191,7 +191,9 @@ namespace DetourNavigator
|
|||
*/
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,10 @@ namespace DetourNavigator
|
|||
void NavigatorImpl::removeAgent(const osg::Vec3f& agentHalfExtents)
|
||||
{
|
||||
const auto it = mAgents.find(agentHalfExtents);
|
||||
if (it == mAgents.end() || --it->second)
|
||||
if (it == mAgents.end())
|
||||
return;
|
||||
mAgents.erase(it);
|
||||
mNavMeshManager.reset(agentHalfExtents);
|
||||
if (it->second > 0)
|
||||
--it->second;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
removeUnusedNavMeshes();
|
||||
for (const auto& v : mAgents)
|
||||
mNavMeshManager.update(playerPosition, v.first);
|
||||
}
|
||||
|
@ -132,11 +133,16 @@ namespace DetourNavigator
|
|||
return mNavMeshManager.getNavMeshes();
|
||||
}
|
||||
|
||||
Settings NavigatorImpl::getSettings() const
|
||||
const Settings& NavigatorImpl::getSettings() const
|
||||
{
|
||||
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)
|
||||
{
|
||||
updateId(id, avoidId, mWaterIds);
|
||||
|
@ -156,4 +162,15 @@ namespace DetourNavigator
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,9 @@ namespace DetourNavigator
|
|||
|
||||
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:
|
||||
Settings mSettings;
|
||||
|
@ -58,6 +60,7 @@ namespace DetourNavigator
|
|||
void updateAvoidShapeId(const ObjectId id, const ObjectId avoidId);
|
||||
void updateWaterShapeId(const ObjectId id, const ObjectId waterId);
|
||||
void updateId(const ObjectId id, const ObjectId waterId, std::unordered_map<ObjectId, ObjectId>& ids);
|
||||
void removeUnusedNavMeshes();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
struct NavigatorStub final : public Navigator
|
||||
class NavigatorStub final : public Navigator
|
||||
{
|
||||
public:
|
||||
NavigatorStub() = default;
|
||||
|
||||
void addAgent(const osg::Vec3f& /*agentHalfExtents*/) override {}
|
||||
|
@ -65,7 +66,7 @@ namespace DetourNavigator
|
|||
|
||||
SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& /*agentHalfExtents*/) const override
|
||||
{
|
||||
return SharedNavMeshCacheItem();
|
||||
return mEmptyNavMeshCacheItem;
|
||||
}
|
||||
|
||||
std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const override
|
||||
|
@ -73,10 +74,16 @@ namespace DetourNavigator
|
|||
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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -4,29 +4,113 @@
|
|||
#include "sharednavmesh.hpp"
|
||||
#include "tileposition.hpp"
|
||||
#include "navmeshtilescache.hpp"
|
||||
#include "dtstatus.hpp"
|
||||
|
||||
#include <components/misc/guarded.hpp>
|
||||
|
||||
#include <DetourNavMesh.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
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
|
||||
{
|
||||
public:
|
||||
NavMeshCacheItem(const NavMeshPtr& value, std::size_t generation)
|
||||
: mValue(value), mGeneration(generation), mNavMeshRevision(0)
|
||||
NavMeshCacheItem(const NavMeshPtr& impl, std::size_t generation)
|
||||
: mImpl(impl), mGeneration(generation), mNavMeshRevision(0)
|
||||
{
|
||||
}
|
||||
|
||||
const dtNavMesh& getValue() const
|
||||
const dtNavMesh& getImpl() const
|
||||
{
|
||||
return *mValue;
|
||||
}
|
||||
|
||||
dtNavMesh& getValue()
|
||||
{
|
||||
return *mValue;
|
||||
return *mImpl;
|
||||
}
|
||||
|
||||
std::size_t getGeneration() const
|
||||
|
@ -39,6 +123,38 @@ namespace DetourNavigator
|
|||
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)
|
||||
{
|
||||
mUsedTiles[tilePosition] = std::make_pair(std::move(value), NavMeshData());
|
||||
|
@ -57,14 +173,26 @@ namespace DetourNavigator
|
|||
++mNavMeshRevision;
|
||||
}
|
||||
|
||||
private:
|
||||
NavMeshPtr mValue;
|
||||
std::size_t mGeneration;
|
||||
std::size_t mNavMeshRevision;
|
||||
std::map<TilePosition, std::pair<NavMeshTilesCache::Value, NavMeshData>> mUsedTiles;
|
||||
dtStatus addTileImpl(unsigned char* data, int size)
|
||||
{
|
||||
const int doNotTransferOwnership = 0;
|
||||
const dtTileRef lastRef = 0;
|
||||
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
|
||||
|
|
|
@ -16,6 +16,21 @@ namespace
|
|||
{
|
||||
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
|
||||
|
@ -79,13 +94,22 @@ namespace DetourNavigator
|
|||
if (cached != mCache.end())
|
||||
return;
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
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)
|
||||
|
@ -139,8 +163,8 @@ namespace DetourNavigator
|
|||
}
|
||||
const auto changedTiles = mChangedTiles.find(agentHalfExtents);
|
||||
{
|
||||
const auto locked = cached.lock();
|
||||
const auto& navMesh = locked->getValue();
|
||||
const auto locked = cached->lockConst();
|
||||
const auto& navMesh = locked->getImpl();
|
||||
if (changedTiles != mChangedTiles.end())
|
||||
{
|
||||
for (const auto& tile : changedTiles->second)
|
||||
|
@ -152,10 +176,6 @@ namespace DetourNavigator
|
|||
else
|
||||
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);
|
||||
mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile)
|
||||
|
@ -171,6 +191,8 @@ namespace DetourNavigator
|
|||
});
|
||||
}
|
||||
mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost);
|
||||
if (changedTiles != mChangedTiles.end())
|
||||
changedTiles->second.clear();
|
||||
log("cache update posted for agent=", agentHalfExtents,
|
||||
" playerTile=", lastPlayerTile->second,
|
||||
" recastMeshManagerRevision=", lastRevision);
|
||||
|
@ -191,6 +213,11 @@ namespace DetourNavigator
|
|||
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,
|
||||
const ChangeType changeType)
|
||||
{
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace DetourNavigator
|
|||
|
||||
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);
|
||||
|
||||
|
@ -50,6 +50,8 @@ namespace DetourNavigator
|
|||
|
||||
std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const;
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
||||
|
||||
private:
|
||||
const Settings& mSettings;
|
||||
TileCachedRecastMeshManager mRecastMeshManager;
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
#include "navmeshtilescache.hpp"
|
||||
#include "exceptions.hpp"
|
||||
|
||||
#include <osg/Stats>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
namespace
|
||||
|
@ -61,8 +65,7 @@ namespace DetourNavigator
|
|||
if (tileValues == agentValues->second.end())
|
||||
return Value();
|
||||
|
||||
// TODO: use different function to make key to avoid unnecessary std::string allocation
|
||||
const auto tile = tileValues->second.mMap.find(makeNavMeshKey(recastMesh, offMeshConnections));
|
||||
const auto tile = tileValues->second.mMap.find(RecastMeshKeyView(recastMesh, offMeshConnections));
|
||||
if (tile == tileValues->second.mMap.end())
|
||||
return Value();
|
||||
|
||||
|
@ -85,7 +88,7 @@ namespace DetourNavigator
|
|||
if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize))
|
||||
return Value();
|
||||
|
||||
const auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections);
|
||||
auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections);
|
||||
const auto itemSize = navMeshSize + 2 * navMeshKey.size();
|
||||
|
||||
if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize))
|
||||
|
@ -94,9 +97,8 @@ namespace DetourNavigator
|
|||
while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize)
|
||||
removeLeastRecentlyUsed();
|
||||
|
||||
const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, 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(navMeshKey, iterator);
|
||||
const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey));
|
||||
const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(iterator->mNavMeshKey, iterator);
|
||||
|
||||
if (!emplaced.second)
|
||||
{
|
||||
|
@ -113,6 +115,24 @@ namespace DetourNavigator
|
|||
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()
|
||||
{
|
||||
const auto& item = mFreeItems.back();
|
||||
|
@ -131,9 +151,10 @@ namespace DetourNavigator
|
|||
|
||||
mUsedNavMeshDataSize -= getSize(item);
|
||||
mFreeNavMeshDataSize -= getSize(item);
|
||||
mFreeItems.pop_back();
|
||||
|
||||
tileValues->second.mMap.erase(value);
|
||||
mFreeItems.pop_back();
|
||||
|
||||
if (!tileValues->second.mMap.empty())
|
||||
return;
|
||||
|
||||
|
@ -163,4 +184,69 @@ namespace DetourNavigator
|
|||
mFreeItems.splice(mFreeItems.begin(), mBusyItems, 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,12 @@
|
|||
#include <map>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <cassert>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class Stats;
|
||||
}
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
|
@ -104,14 +110,68 @@ namespace DetourNavigator
|
|||
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections,
|
||||
NavMeshData&& value);
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
||||
|
||||
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
|
||||
{
|
||||
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 mUsedNavMeshDataSize;
|
||||
std::size_t mFreeNavMeshDataSize;
|
||||
|
|
|
@ -61,8 +61,8 @@ namespace DetourNavigator
|
|||
for (const auto& tile : currentTiles)
|
||||
if (!newTiles.count(tile) && removeTile(id, tile, tiles.get()))
|
||||
changedTiles.push_back(tile);
|
||||
std::swap(currentTiles, newTiles);
|
||||
}
|
||||
std::swap(currentTiles, newTiles);
|
||||
if (!changedTiles.empty())
|
||||
++mRevision;
|
||||
return changedTiles;
|
||||
|
|
|
@ -127,7 +127,7 @@ std::string ESMReader::getHString()
|
|||
// them. For some reason, they break the rules, and contain a byte
|
||||
// (value 0) even if the header says there is no data. If
|
||||
// Morrowind accepts it, so should we.
|
||||
if (mCtx.leftSub == 0)
|
||||
if (mCtx.leftSub == 0 && mCtx.leftRec != 0)
|
||||
{
|
||||
// Skip the following zero byte
|
||||
mCtx.leftRec--;
|
||||
|
|
|
@ -11,18 +11,9 @@ class ESMWriter;
|
|||
|
||||
/*
|
||||
* Texture used for texturing landscape.
|
||||
*
|
||||
* They are probably indexed by 'num', not 'id', but I don't know for
|
||||
* sure. And num is not unique between files, so one option is to keep
|
||||
* 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.
|
||||
* They are indexed by 'num', but still use 'id' to override base records.
|
||||
* Original editor even does not allow to create new records with existing ID's.
|
||||
* TODO: currently OpenMW-CS does not allow to override LTEX records at all.
|
||||
*/
|
||||
|
||||
struct LandTexture
|
||||
|
|
|
@ -104,7 +104,7 @@ namespace ESM
|
|||
}
|
||||
|
||||
mScriptData.resize(subSize);
|
||||
esm.getExact(&mScriptData[0], mScriptData.size());
|
||||
esm.getExact(mScriptData.data(), mScriptData.size());
|
||||
break;
|
||||
}
|
||||
case ESM::FourCC<'S','C','T','X'>::value:
|
||||
|
@ -156,7 +156,7 @@ namespace ESM
|
|||
}
|
||||
|
||||
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.writeHNOString("SCTX", mScriptText);
|
||||
|
|
|
@ -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;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -175,22 +162,22 @@ namespace ESMTerrain
|
|||
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0;
|
||||
if (data)
|
||||
{
|
||||
color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
|
||||
color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
|
||||
color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 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];
|
||||
color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2];
|
||||
}
|
||||
else
|
||||
{
|
||||
color.r() = 1;
|
||||
color.g() = 1;
|
||||
color.b() = 1;
|
||||
color.r() = 255;
|
||||
color.g() = 255;
|
||||
color.b() = 255;
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
|
||||
osg::ref_ptr<osg::Vec3Array> positions,
|
||||
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
|
||||
size_t increment = static_cast<size_t>(1) << lodLevel;
|
||||
|
@ -207,7 +194,7 @@ namespace ESMTerrain
|
|||
colours->resize(numVerts*numVerts);
|
||||
|
||||
osg::Vec3f normal;
|
||||
osg::Vec4f color;
|
||||
osg::Vec4ub color;
|
||||
|
||||
float vertY = 0;
|
||||
float vertX = 0;
|
||||
|
@ -295,20 +282,20 @@ namespace ESMTerrain
|
|||
if (colourData)
|
||||
{
|
||||
for (int i=0; i<3; ++i)
|
||||
color[i] = colourData->mColours[srcArrayIndex+i] / 255.f;
|
||||
color[i] = colourData->mColours[srcArrayIndex+i];
|
||||
}
|
||||
else
|
||||
{
|
||||
color.r() = 1;
|
||||
color.g() = 1;
|
||||
color.b() = 1;
|
||||
color.r() = 255;
|
||||
color.g() = 255;
|
||||
color.b() = 255;
|
||||
}
|
||||
|
||||
// Unlike normals, colors mostly connect seamlessly between cells, but not always...
|
||||
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
|
||||
fixColour(color, cellX, cellY, col, row, cache);
|
||||
|
||||
color.a() = 1;
|
||||
color.a() = 255;
|
||||
|
||||
(*colours)[static_cast<unsigned int>(vertX*numVerts + vertY)] = color;
|
||||
|
||||
|
@ -388,8 +375,7 @@ namespace ESMTerrain
|
|||
return texture;
|
||||
}
|
||||
|
||||
void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter,
|
||||
bool pack, ImageVector &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
|
||||
void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter, ImageVector &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
|
||||
{
|
||||
osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f);
|
||||
int cellX = static_cast<int>(std::floor(origin.x()));
|
||||
|
@ -429,11 +415,8 @@ namespace ESMTerrain
|
|||
layerList.push_back(getLayerInfo(getTextureName(*it)));
|
||||
}
|
||||
|
||||
int numTextures = textureIndices.size();
|
||||
// numTextures-1 since the base layer doesn't need blending
|
||||
int numBlendmaps = pack ? static_cast<int>(std::ceil((numTextures - 1) / 4.f)) : (numTextures - 1);
|
||||
|
||||
int channels = pack ? 4 : 1;
|
||||
// size-1 since the base layer doesn't need blending
|
||||
int numBlendmaps = textureIndices.size() - 1;
|
||||
|
||||
// Second iteration - create and fill in the blend maps
|
||||
const int blendmapSize = (realTextureSize-1) * chunkSize + 1;
|
||||
|
@ -443,10 +426,8 @@ namespace ESMTerrain
|
|||
|
||||
for (int i=0; i<numBlendmaps; ++i)
|
||||
{
|
||||
GLenum format = pack ? GL_RGBA : GL_ALPHA;
|
||||
|
||||
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();
|
||||
|
||||
for (int y=0; y<blendmapSize; ++y)
|
||||
|
@ -456,18 +437,16 @@ namespace ESMTerrain
|
|||
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x+rowStart, y+colStart, cache);
|
||||
assert(textureIndicesMap.find(id) != textureIndicesMap.end());
|
||||
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 realX = x*imageScaleFactor;
|
||||
|
||||
pData[((realY+0)*blendmapImageSize + realX + 0)*channels + channel] = alpha;
|
||||
pData[((realY+1)*blendmapImageSize + realX + 0)*channels + channel] = alpha;
|
||||
pData[((realY+0)*blendmapImageSize + realX + 1)*channels + channel] = alpha;
|
||||
pData[((realY+1)*blendmapImageSize + realX + 1)*channels + channel] = alpha;
|
||||
pData[(realY+0)*blendmapImageSize + realX + 0] = alpha;
|
||||
pData[(realY+1)*blendmapImageSize + realX + 0] = alpha;
|
||||
pData[(realY+0)*blendmapImageSize + realX + 1] = alpha;
|
||||
pData[(realY+1)*blendmapImageSize + realX + 1] = alpha;
|
||||
}
|
||||
}
|
||||
blendmaps.push_back(image);
|
||||
|
|
|
@ -29,8 +29,17 @@ namespace ESMTerrain
|
|||
|
||||
META_Object(ESMTerrain, LandObject)
|
||||
|
||||
const ESM::Land::LandData* getData(int flags) const;
|
||||
int getPlugin() const;
|
||||
inline const ESM::Land::LandData* getData(int flags) const
|
||||
{
|
||||
if ((mData.mDataLoaded & flags) != flags)
|
||||
return nullptr;
|
||||
return &mData;
|
||||
}
|
||||
|
||||
inline int getPlugin() const
|
||||
{
|
||||
return mLand->mPlugin;
|
||||
}
|
||||
|
||||
private:
|
||||
const ESM::Land* mLand;
|
||||
|
@ -75,7 +84,7 @@ namespace ESMTerrain
|
|||
virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
|
||||
osg::ref_ptr<osg::Vec3Array> positions,
|
||||
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.
|
||||
/// @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.
|
||||
/// @param chunkSize size of the terrain 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 layerList names of the layer textures used will be written here
|
||||
virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, bool pack,
|
||||
ImageVector& blendmaps,
|
||||
std::vector<Terrain::LayerInfo>& layerList);
|
||||
virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps,
|
||||
std::vector<Terrain::LayerInfo>& layerList);
|
||||
|
||||
virtual float getHeightAt (const osg::Vec3f& worldPos);
|
||||
|
||||
|
@ -105,21 +110,20 @@ namespace ESMTerrain
|
|||
private:
|
||||
const VFS::Manager* mVFS;
|
||||
|
||||
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);
|
||||
void averageNormal (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);
|
||||
inline void fixColour (osg::Vec4ub& colour, 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
|
||||
// in order to retrieve the correct texture name.
|
||||
// pair <texture id, plugin id>
|
||||
typedef std::pair<short, short> UniqueTextureId;
|
||||
|
||||
UniqueTextureId getVtexIndexAt(int cellX, int cellY,
|
||||
int x, int y, LandCache&);
|
||||
inline UniqueTextureId getVtexIndexAt(int cellX, int cellY, int x, int y, LandCache&);
|
||||
std::string getTextureName (UniqueTextureId id);
|
||||
|
||||
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;
|
||||
|
|
|
@ -83,38 +83,6 @@ namespace Misc
|
|||
std::mutex mMutex;
|
||||
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
|
||||
|
|
|
@ -120,7 +120,7 @@ void NiRotatingParticlesData::read(NIFStream *nif)
|
|||
|
||||
void NiPosData::read(NIFStream *nif)
|
||||
{
|
||||
mKeyList.reset(new Vector3KeyMap);
|
||||
mKeyList = std::make_shared<Vector3KeyMap>();
|
||||
mKeyList->read(nif);
|
||||
}
|
||||
|
||||
|
@ -128,14 +128,14 @@ void NiUVData::read(NIFStream *nif)
|
|||
{
|
||||
for(int i = 0;i < 4;i++)
|
||||
{
|
||||
mKeyList[i].reset(new FloatKeyMap);
|
||||
mKeyList[i] = std::make_shared<FloatKeyMap>();
|
||||
mKeyList[i]->read(nif);
|
||||
}
|
||||
}
|
||||
|
||||
void NiFloatData::read(NIFStream *nif)
|
||||
{
|
||||
mKeyList.reset(new FloatKeyMap);
|
||||
mKeyList = std::make_shared<FloatKeyMap>();
|
||||
mKeyList->read(nif);
|
||||
}
|
||||
|
||||
|
@ -177,7 +177,7 @@ void NiPixelData::read(NIFStream *nif)
|
|||
|
||||
void NiColorData::read(NIFStream *nif)
|
||||
{
|
||||
mKeyMap.reset(new Vector4KeyMap);
|
||||
mKeyMap = std::make_shared<Vector4KeyMap>();
|
||||
mKeyMap->read(nif);
|
||||
}
|
||||
|
||||
|
@ -231,7 +231,7 @@ void NiMorphData::read(NIFStream *nif)
|
|||
mMorphs.resize(morphCount);
|
||||
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);
|
||||
nif->getVector3s(mMorphs[i].mVertices, vertCount);
|
||||
}
|
||||
|
@ -239,22 +239,22 @@ void NiMorphData::read(NIFStream *nif)
|
|||
|
||||
void NiKeyframeData::read(NIFStream *nif)
|
||||
{
|
||||
mRotations.reset(new QuaternionKeyMap);
|
||||
mRotations = std::make_shared<QuaternionKeyMap>();
|
||||
mRotations->read(nif);
|
||||
if(mRotations->mInterpolationType == Vector3KeyMap::sXYZInterpolation)
|
||||
{
|
||||
//Chomp unused float
|
||||
nif->getFloat();
|
||||
mXRotations.reset(new FloatKeyMap);
|
||||
mYRotations.reset(new FloatKeyMap);
|
||||
mZRotations.reset(new FloatKeyMap);
|
||||
mXRotations = std::make_shared<FloatKeyMap>();
|
||||
mYRotations = std::make_shared<FloatKeyMap>();
|
||||
mZRotations = std::make_shared<FloatKeyMap>();
|
||||
mXRotations->read(nif, true);
|
||||
mYRotations->read(nif, true);
|
||||
mZRotations->read(nif, true);
|
||||
}
|
||||
mTranslations.reset(new Vector3KeyMap);
|
||||
mTranslations = std::make_shared<Vector3KeyMap>();
|
||||
mTranslations->read(nif);
|
||||
mScales.reset(new FloatKeyMap);
|
||||
mScales = std::make_shared<FloatKeyMap>();
|
||||
mScales->read(nif);
|
||||
}
|
||||
|
||||
|
|
|
@ -311,10 +311,11 @@ void VisController::operator() (osg::Node* node, osg::NodeVisitor* nv)
|
|||
|
||||
RollController::RollController(const Nif::NiFloatData *data)
|
||||
: mData(data->mKeyList, 1.f)
|
||||
, mStartingTime(0)
|
||||
{
|
||||
}
|
||||
|
||||
RollController::RollController()
|
||||
RollController::RollController() : mStartingTime(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
// 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
|
||||
*
|
||||
|
@ -19,6 +22,7 @@
|
|||
|
||||
#include <osg/Referenced>
|
||||
#include <osg/ref_ptr>
|
||||
#include <osg/Node>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
@ -32,11 +36,13 @@ namespace osg
|
|||
|
||||
namespace Resource {
|
||||
|
||||
class ObjectCache : public osg::Referenced
|
||||
template <typename KeyType>
|
||||
class GenericObjectCache : public osg::Referenced
|
||||
{
|
||||
public:
|
||||
|
||||
ObjectCache();
|
||||
GenericObjectCache()
|
||||
: osg::Referenced(true) {}
|
||||
|
||||
/** 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
|
||||
|
@ -44,59 +50,149 @@ class ObjectCache : public osg::Referenced
|
|||
* 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.
|
||||
* 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.
|
||||
* 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
|
||||
* 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.*/
|
||||
void clear();
|
||||
void clear()
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
|
||||
_objectCache.clear();
|
||||
}
|
||||
|
||||
/** Add a filename,object,timestamp triple to the Registry::ObjectCache.*/
|
||||
void addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp = 0.0);
|
||||
/** Add a key,object,timestamp triple to the Registry::ObjectCache.*/
|
||||
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.*/
|
||||
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*/
|
||||
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. */
|
||||
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.*/
|
||||
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. */
|
||||
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. */
|
||||
template <class Functor>
|
||||
void call(Functor& f)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
/** 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:
|
||||
|
||||
virtual ~ObjectCache();
|
||||
virtual ~GenericObjectCache() {}
|
||||
|
||||
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;
|
||||
mutable OpenThreads::Mutex _objectCacheMutex;
|
||||
|
||||
};
|
||||
|
||||
class ObjectCache : public GenericObjectCache<std::string>
|
||||
{
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue