merge in master

C++20
Bret Curtis 3 years ago
commit 0f43455dc3

@ -218,6 +218,7 @@ Programmers
tlmullis
tri4ng1e
Thoronador
Tobias Tribble (zackogenic)
Tom Lowe (Vulpen)
Tom Mason (wheybags)
Torben Leif Carrington (TorbenC)
@ -234,7 +235,6 @@ Programmers
Yuri Krupenin
zelurker
Noah Gooder
Documentation
-------------
@ -249,6 +249,7 @@ Documentation
Joakim Berg (lysol90)
Ryan Tucker (Ravenwing)
sir_herrbatka
David Nagy (zuzaman)
Packagers
---------

@ -19,6 +19,7 @@
Bug #4700: Editor: Incorrect command implementation
Bug #4744: Invisible particles must still be processed
Bug #4949: Incorrect particle lighting
Bug #5054: Non-biped creatures don't use spellcast equip/unequip animations
Bug #5088: Sky abruptly changes direction during certain weather transitions
Bug #5100: Persuasion doesn't always clamp the resulting disposition
Bug #5120: Scripted object spawning updates physics system
@ -36,7 +37,6 @@
Bug #5788: Texture editing parses the selected indexes wrongly
Bug #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention
Bug #5842: GetDisposition adds temporary disposition change from different actors
Bug #5858: Animated model freezes the game
Bug #5863: GetEffect should return true after the player has teleported
Bug #5913: Failed assertion during Ritual of Trees quest
Bug #5928: Glow in the Dahrk functionality used without mod installed
@ -101,8 +101,10 @@
Bug #6519: Effects tooltips for ingredients work incorrectly
Bug #6523: Disintegrate Weapon is resisted by Resist Magicka instead of Sanctuary
Bug #6544: Far from world origin objects jitter when camera is still
Bug #6579: OpenMW compilation error when using OSG doubles for BoundingSphere
Feature #890: OpenMW-CS: Column filtering
Feature #1465: "Reset" argument for AI functions
Feature #2491: Ability to make OpenMW "portable"
Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record
Feature #2780: A way to see current OpenMW version in the console
Feature #3616: Allow Zoom levels on the World Map
@ -128,7 +130,9 @@
Feature #6288: Preserve the "blocked" record flag for referenceable objects.
Feature #6380: Commas are treated as whitespace in vanilla
Feature #6419: Topics shouldn't be greyed out if they can produce another topic reference
Feature #6443: NiStencilProperty is not fully supported
Feature #6534: Shader-based object texture blending
Feature #6592: Missing support for NiTriShape particle emitters
Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings
Task #6264: Remove the old classes in animation.cpp
Task #6553: Simplify interpreter instruction registration
@ -270,6 +274,7 @@
Bug #6142: Groundcover plugins change cells flags
Bug #6276: Deleted groundcover instances are not deleted in game
Bug #6294: Game crashes with empty pathgrid
Bug #6606: Quests with multiple IDs cannot always be restarted
Feature #390: 3rd person look "over the shoulder"
Feature #832: OpenMW-CS: Handle deleted references
Feature #1536: Show more information about level on menu

@ -484,9 +484,9 @@ int clone(Arguments& info)
if (i <= 0)
break;
const ESM::NAME& typeName = record->getType();
const ESM::NAME typeName = record->getType();
esm.startRecord(typeName.toString(), record->getFlags());
esm.startRecord(typeName, record->getFlags());
record->save(esm);
if (typeName.toInt() == ESM::REC_CELL) {
@ -498,7 +498,7 @@ int clone(Arguments& info)
}
}
esm.endRecord(typeName.toString());
esm.endRecord(typeName);
saved++;
int perc = recordCount == 0 ? 100 : (int)((saved / (float)recordCount)*100);

@ -25,6 +25,7 @@ int main(int argc, char** argv)
("encoding", boost::program_options::value<std::string>()->default_value("win1252"), "encoding of the save file")
;
p_desc.add("mwsave", 1).add("output", 1);
Files::ConfigurationManager::addCommonOptions(desc);
bpo::variables_map variables;

@ -414,57 +414,23 @@ bool Launcher::MainDialog::setupGameData()
bool Launcher::MainDialog::setupGraphicsSettings()
{
// This method is almost a copy of OMW::Engine::loadSettings(). They should definitely
// remain consistent, and possibly be merged into a shared component. At the very least
// the filenames should be in the CfgMgr component.
// Ensure to clear previous settings in case we had already loaded settings.
mEngineSettings.clear();
// Create the settings manager and load default settings file
const std::string localDefault = (mCfgMgr.getLocalPath() / "defaults.bin").string();
const std::string globalDefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string();
std::string defaultPath;
// Prefer the defaults.bin in the current directory.
if (boost::filesystem::exists(localDefault))
defaultPath = localDefault;
else if (boost::filesystem::exists(globalDefault))
defaultPath = globalDefault;
// Something's very wrong if we can't find the file at all.
else {
cfgError(tr("Error reading OpenMW configuration file"),
tr("<br><b>Could not find defaults.bin</b><br><br> \
The problem may be due to an incomplete installation of OpenMW.<br> \
Reinstalling OpenMW may resolve the problem."));
return false;
}
// Load the default settings, report any parsing errors.
try {
mEngineSettings.loadDefault(defaultPath);
}
catch (std::exception& e) {
std::string msg = std::string("<br><b>Error reading defaults.bin</b><br><br>") + e.what();
cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str()));
return false;
}
// Load user settings if they exist
const std::string userPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string();
// User settings are not required to exist, so if they don't we're done.
if (!boost::filesystem::exists(userPath)) return true;
try {
mEngineSettings.loadUser(userPath);
mEngineSettings.clear(); // Ensure to clear previous settings in case we had already loaded settings.
try
{
boost::program_options::variables_map variables;
boost::program_options::options_description desc;
mCfgMgr.addCommonOptions(desc);
mCfgMgr.readConfiguration(variables, desc, true);
mEngineSettings.load(mCfgMgr);
return true;
}
catch (std::exception& e) {
std::string msg = std::string("<br><b>Error reading settings.cfg</b><br><br>") + e.what();
cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str()));
catch (std::exception& e)
{
cfgError(tr("Error reading OpenMW configuration files"),
tr("<br>The problem may be due to an incomplete installation of OpenMW.<br> \
Reinstalling OpenMW may resolve the problem.<br>") + e.what());
return false;
}
return true;
}
void Launcher::MainDialog::loadSettings()

@ -84,27 +84,11 @@ namespace NavMeshTool
("process-interior-cells", bpo::value<bool>()->implicit_value(true)
->default_value(false), "build navmesh for interior cells")
;
Files::ConfigurationManager::addCommonOptions(result);
return result;
}
void loadSettings(const Files::ConfigurationManager& config, Settings::Manager& settings)
{
const std::string localDefault = (config.getLocalPath() / "defaults.bin").string();
const std::string globalDefault = (config.getGlobalPath() / "defaults.bin").string();
if (boost::filesystem::exists(localDefault))
settings.loadDefault(localDefault);
else if (boost::filesystem::exists(globalDefault))
settings.loadDefault(globalDefault);
else
throw std::runtime_error("No default settings file found! Make sure the file \"defaults.bin\" was properly installed.");
const std::string settingsPath = (config.getUserConfigPath() / "settings.cfg").string();
if (boost::filesystem::exists(settingsPath))
settings.loadUser(settingsPath);
}
int runNavMeshTool(int argc, char *argv[])
{
bpo::options_description desc = makeOptionsDescription();
@ -165,7 +149,7 @@ namespace NavMeshTool
VFS::registerArchives(&vfs, fileCollections, archives, true);
Settings::Manager settings;
loadSettings(config, settings);
settings.load(config);
const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game");

@ -177,8 +177,8 @@ namespace NavMeshTool
DetourNavigator::getTilesPositions(
DetourNavigator::makeTilesPositionsRange(
Misc::Convert::toOsg(input->mAabb.m_min),
Misc::Convert::toOsg(input->mAabb.m_max),
Misc::Convert::toOsgXY(input->mAabb.m_min),
Misc::Convert::toOsgXY(input->mAabb.m_max),
settings.mRecast
),
[&] (const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); }

@ -178,7 +178,7 @@ namespace NavMeshTool
static_cast<btScalar>(cellPosition.y() * ESM::Land::REAL_SIZE),
minHeight
);
aabb.m_min = btVector3(
aabb.m_max = btVector3(
static_cast<btScalar>((cellPosition.x() + 1) * ESM::Land::REAL_SIZE),
static_cast<btScalar>((cellPosition.y() + 1) * ESM::Land::REAL_SIZE),
maxHeight
@ -298,12 +298,14 @@ namespace NavMeshTool
const ObjectId objectId(++objectsCounter);
const CollisionShape shape(object.getShapeInstance(), *object.getCollisionObject().getCollisionShape(), object.getObjectTransform());
navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform, DetourNavigator::AreaType_ground);
navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform,
DetourNavigator::AreaType_ground, [] (const auto&) {});
if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get())
{
const CollisionShape avoidShape(object.getShapeInstance(), *avoid, object.getObjectTransform());
navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, avoidShape, transform, DetourNavigator::AreaType_null);
navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, avoidShape, transform,
DetourNavigator::AreaType_null, [] (const auto&) {});
}
data.mObjects.emplace_back(std::move(object));

@ -5,6 +5,7 @@
#include <QLocalSocket>
#include <QMessageBox>
#include <components/debug/debugging.hpp>
#include <components/debug/debuglog.hpp>
#include <components/fallback/validate.hpp>
#include <components/misc/rng.hpp>
@ -20,7 +21,7 @@
using namespace Fallback;
CS::Editor::Editor (int argc, char **argv)
: mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr),
: mConfigVariables(readConfiguration()), mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr),
mPid(""), mLock(), mMerge (mDocumentManager),
mIpcServerName ("org.openmw.OpenCS"), mServer(nullptr), mClientSocket(nullptr)
{
@ -82,7 +83,7 @@ CS::Editor::~Editor ()
remove(mPid.string().c_str())); // ignore any error
}
std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfig(bool quiet)
boost::program_options::variables_map CS::Editor::readConfiguration()
{
boost::program_options::variables_map variables;
boost::program_options::options_description desc("Syntax: openmw-cs <options>\nAllowed options");
@ -101,10 +102,19 @@ std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfi
->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)")
("script-blacklist-use", boost::program_options::value<bool>()->implicit_value(true)
->default_value(true), "enable script blacklisting");
Files::ConfigurationManager::addCommonOptions(desc);
boost::program_options::notify(variables);
mCfgMgr.readConfiguration(variables, desc, false);
setupLogging(mCfgMgr.getLogPath().string(), "OpenMW-CS");
return variables;
}
std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfig(bool quiet)
{
boost::program_options::variables_map& variables = mConfigVariables;
Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap);
@ -360,7 +370,7 @@ int CS::Editor::run()
else
{
ESM::ESMReader fileReader;
ToUTF8::Utf8Encoder encoder = ToUTF8::calculateEncoding(mEncodingName);
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncodingName));
fileReader.setEncoder(&encoder);
fileReader.open(mFileToLoad.string());

@ -40,6 +40,7 @@ namespace CS
Q_OBJECT
Files::ConfigurationManager mCfgMgr;
boost::program_options::variables_map mConfigVariables;
CSMPrefs::State mSettingsState;
CSMDoc::DocumentManager mDocumentManager;
CSVDoc::StartupDialogue mStartup;
@ -58,6 +59,8 @@ namespace CS
Files::PathContainer mDataDirs;
std::string mEncodingName;
boost::program_options::variables_map readConfiguration();
///< Calls mCfgMgr.readConfiguration; should be used before initialization of mSettingsState as it depends on the configuration.
std::pair<Files::PathContainer, std::vector<std::string> > readConfig(bool quiet=false);
///< \return data paths

@ -79,5 +79,5 @@ int runApplication(int argc, char *argv[])
int main(int argc, char *argv[])
{
return wrapApplication(&runApplication, argc, argv, "OpenMW-CS");
return wrapApplication(&runApplication, argc, argv, "OpenMW-CS", false);
}

@ -16,22 +16,7 @@ CSMPrefs::State *CSMPrefs::State::sThis = nullptr;
void CSMPrefs::State::load()
{
// default settings file
boost::filesystem::path local = mConfigurationManager.getLocalPath() / mDefaultConfigFile;
boost::filesystem::path global = mConfigurationManager.getGlobalPath() / mDefaultConfigFile;
if (boost::filesystem::exists (local))
mSettings.loadDefault (local.string());
else if (boost::filesystem::exists (global))
mSettings.loadDefault (global.string());
else
throw std::runtime_error ("No default settings file found! Make sure the file \"" + mDefaultConfigFile + "\" was properly installed.");
// user settings file
boost::filesystem::path user = mConfigurationManager.getUserConfigPath() / mConfigFile;
if (boost::filesystem::exists (user))
mSettings.loadUser (user.string());
mSettings.load(mConfigurationManager);
}
void CSMPrefs::State::declare()

@ -518,28 +518,6 @@ void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame)
mNewGame = newGame;
}
std::string OMW::Engine::loadSettings (Settings::Manager & settings)
{
// Create the settings manager and load default settings file
const std::string localdefault = (mCfgMgr.getLocalPath() / "defaults.bin").string();
const std::string globaldefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string();
// prefer local
if (boost::filesystem::exists(localdefault))
settings.loadDefault(localdefault);
else if (boost::filesystem::exists(globaldefault))
settings.loadDefault(globaldefault);
else
throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed.");
// load user settings if they exist
std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string();
if (boost::filesystem::exists(settingspath))
settings.loadUser(settingspath);
return settingspath;
}
void OMW::Engine::createWindow(Settings::Manager& settings)
{
int screen = settings.getInt("screen", "Video");
@ -694,18 +672,18 @@ void OMW::Engine::setWindowIcon()
void OMW::Engine::prepareEngine (Settings::Manager & settings)
{
mEnvironment.setStateManager (
new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles));
std::make_unique<MWState::StateManager> (mCfgMgr.getUserDataPath() / "saves", mContentFiles));
createWindow(settings);
osg::ref_ptr<osg::Group> rootNode (new osg::Group);
mViewer->setSceneData(rootNode);
mVFS.reset(new VFS::Manager(mFSStrict));
mVFS = std::make_unique<VFS::Manager>(mFSStrict);
VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true);
mResourceSystem.reset(new Resource::ResourceSystem(mVFS.get()));
mResourceSystem = std::make_unique<Resource::ResourceSystem>(mVFS.get());
mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply(false); // keep to Off for now to allow better state sharing
mResourceSystem->getSceneManager()->setFilterSettings(
Settings::Manager::getString("texture mag filter", "General"),
@ -734,8 +712,9 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
mViewer->addEventHandler(mScreenCaptureHandler);
mLuaManager = new MWLua::LuaManager(mVFS.get(), (mResDir / "lua_libs").string());
mEnvironment.setLuaManager(mLuaManager);
auto luaMgr = std::make_unique<MWLua::LuaManager>(mVFS.get(), (mResDir / "lua_libs").string());
mLuaManager = luaMgr.get();
mEnvironment.setLuaManager(std::move(luaMgr));
// Create input and UI first to set up a bootstrapping environment for
// showing a loading screen and keeping the window responsive while doing so
@ -806,33 +785,35 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
guiRoot->setName("GUI Root");
guiRoot->setNodeMask(MWRender::Mask_GUI);
rootNode->addChild(guiRoot);
MWGui::WindowManager* window = new MWGui::WindowManager(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(),
auto windowMgr = std::make_unique<MWGui::WindowManager>(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(),
mCfgMgr.getLogPath().string() + std::string("/"), myguiResources,
mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts,
Version::getOpenmwVersionDescription(mResDir.string()), mCfgMgr.getUserConfigPath().string(), shadersSupported);
mEnvironment.setWindowManager (window);
auto* windowMgrInternal = windowMgr.get();
mEnvironment.setWindowManager (std::move(windowMgr));
MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
mEnvironment.setInputManager (input);
auto inputMgr = std::make_unique<MWInput::InputManager>(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
mEnvironment.setInputManager (std::move(inputMgr));
// Create sound system
mEnvironment.setSoundManager (new MWSound::SoundManager(mVFS.get(), mUseSound));
mEnvironment.setSoundManager (std::make_unique<MWSound::SoundManager>(mVFS.get(), mUseSound));
if (!mSkipMenu)
{
const std::string& logo = Fallback::Map::getString("Movies_Company_Logo");
if (!logo.empty())
window->playVideo(logo, true);
mEnvironment.getWindowManager()->playVideo(logo, true);
}
// Create the world
mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(),
mEnvironment.setWorld(std::make_unique<MWWorld::World>(mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(),
mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName,
mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()));
mEnvironment.getWorld()->setupPlayer();
window->setStore(mEnvironment.getWorld()->getStore());
window->initUI();
windowMgrInternal->setStore(mEnvironment.getWorld()->getStore());
windowMgrInternal->initUI();
//Load translation data
mTranslationDataStorage.setEncoder(mEncoder);
@ -845,16 +826,15 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full);
mScriptContext->setExtensions (&mExtensions);
mEnvironment.setScriptManager (new MWScript::ScriptManager (mEnvironment.getWorld()->getStore(), *mScriptContext, mWarningsMode,
mEnvironment.setScriptManager (std::make_unique<MWScript::ScriptManager>(mEnvironment.getWorld()->getStore(), *mScriptContext, mWarningsMode,
mScriptBlacklistUse ? mScriptBlacklist : std::vector<std::string>()));
// Create game mechanics system
MWMechanics::MechanicsManager* mechanics = new MWMechanics::MechanicsManager;
mEnvironment.setMechanicsManager (mechanics);
mEnvironment.setMechanicsManager (std::make_unique<MWMechanics::MechanicsManager>());
// Create dialog system
mEnvironment.setJournal (new MWDialogue::Journal);
mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage));
mEnvironment.setJournal (std::make_unique<MWDialogue::Journal>());
mEnvironment.setDialogueManager (std::make_unique<MWDialogue::DialogueManager>(mExtensions, mTranslationDataStorage));
mEnvironment.setResourceSystem(mResourceSystem.get());
// scripts
@ -975,8 +955,7 @@ void OMW::Engine::go()
// Load settings
Settings::Manager settings;
std::string settingspath;
settingspath = loadSettings (settings);
std::string settingspath = settings.load(mCfgMgr);
MWClass::registerClasses();

@ -40,6 +40,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
typedef std::vector<std::string> StringsVector;
bpo::options_description desc = OpenMW::makeOptionsDescription();
Files::ConfigurationManager::addCommonOptions(desc);
bpo::variables_map variables;
@ -61,9 +62,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
return false;
}
bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc);
cfgMgr.readConfiguration(variables, desc);
Files::mergeComposingVariables(variables, composingVariables, desc);
setupLogging(cfgMgr.getLogPath().string(), "OpenMW");
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::MaybeQuotedPath>().string());
Log(Debug::Info) << v.describe();
@ -230,7 +231,7 @@ extern "C" int SDL_main(int argc, char**argv)
int main(int argc, char**argv)
#endif
{
return wrapApplication(&runApplication, argc, argv, "OpenMW");
return wrapApplication(&runApplication, argc, argv, "OpenMW", false);
}
// Platform specific for Windows when there is no console built into the executable.

@ -18,68 +18,64 @@
MWBase::Environment *MWBase::Environment::sThis = nullptr;
MWBase::Environment::Environment()
: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr),
mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr),
mStateManager (nullptr), mLuaManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f)
{
assert (!sThis);
assert(!sThis);
sThis = this;
}
MWBase::Environment::~Environment()
{
cleanup();
sThis = nullptr;
}
void MWBase::Environment::setWorld (World *world)
void MWBase::Environment::setWorld (std::unique_ptr<World>&& world)
{
mWorld = world;
mWorld = std::move(world);
}
void MWBase::Environment::setSoundManager (SoundManager *soundManager)
void MWBase::Environment::setSoundManager (std::unique_ptr<SoundManager>&& soundManager)
{
mSoundManager = soundManager;
mSoundManager = std::move(soundManager);
}
void MWBase::Environment::setScriptManager (ScriptManager *scriptManager)
void MWBase::Environment::setScriptManager (std::unique_ptr<ScriptManager>&& scriptManager)
{
mScriptManager = scriptManager;
mScriptManager = std::move(scriptManager);
}
void MWBase::Environment::setWindowManager (WindowManager *windowManager)
void MWBase::Environment::setWindowManager (std::unique_ptr<WindowManager>&& windowManager)
{
mWindowManager = windowManager;
mWindowManager = std::move(windowManager);
}
void MWBase::Environment::setMechanicsManager (MechanicsManager *mechanicsManager)
void MWBase::Environment::setMechanicsManager (std::unique_ptr<MechanicsManager>&& mechanicsManager)
{
mMechanicsManager = mechanicsManager;
mMechanicsManager = std::move(mechanicsManager);
}
void MWBase::Environment::setDialogueManager (DialogueManager *dialogueManager)
void MWBase::Environment::setDialogueManager (std::unique_ptr<DialogueManager>&& dialogueManager)
{
mDialogueManager = dialogueManager;
mDialogueManager = std::move(dialogueManager);
}
void MWBase::Environment::setJournal (Journal *journal)
void MWBase::Environment::setJournal (std::unique_ptr<Journal>&& journal)
{
mJournal = journal;
mJournal = std::move(journal);
}
void MWBase::Environment::setInputManager (InputManager *inputManager)
void MWBase::Environment::setInputManager (std::unique_ptr<InputManager>&& inputManager)
{
mInputManager = inputManager;
mInputManager = std::move(inputManager);
}
void MWBase::Environment::setStateManager (StateManager *stateManager)
void MWBase::Environment::setStateManager (std::unique_ptr<StateManager>&& stateManager)
{
mStateManager = stateManager;
mStateManager = std::move(stateManager);
}
void MWBase::Environment::setLuaManager (LuaManager *luaManager)
void MWBase::Environment::setLuaManager (std::unique_ptr<LuaManager>&& luaManager)
{
mLuaManager = luaManager;
mLuaManager = std::move(luaManager);
}
void MWBase::Environment::setResourceSystem (Resource::ResourceSystem *resourceSystem)
@ -105,61 +101,61 @@ float MWBase::Environment::getFrameRateLimit() const
MWBase::World *MWBase::Environment::getWorld() const
{
assert (mWorld);
return mWorld;
return mWorld.get();
}
MWBase::SoundManager *MWBase::Environment::getSoundManager() const
{
assert (mSoundManager);
return mSoundManager;
return mSoundManager.get();
}
MWBase::ScriptManager *MWBase::Environment::getScriptManager() const
{
assert (mScriptManager);
return mScriptManager;
return mScriptManager.get();
}
MWBase::WindowManager *MWBase::Environment::getWindowManager() const
{
assert (mWindowManager);
return mWindowManager;
return mWindowManager.get();
}
MWBase::MechanicsManager *MWBase::Environment::getMechanicsManager() const
{
assert (mMechanicsManager);
return mMechanicsManager;
return mMechanicsManager.get();
}
MWBase::DialogueManager *MWBase::Environment::getDialogueManager() const
{
assert (mDialogueManager);
return mDialogueManager;
return mDialogueManager.get();
}
MWBase::Journal *MWBase::Environment::getJournal() const
{
assert (mJournal);
return mJournal;
return mJournal.get();
}
MWBase::InputManager *MWBase::Environment::getInputManager() const
{
assert (mInputManager);
return mInputManager;
return mInputManager.get();
}
MWBase::StateManager *MWBase::Environment::getStateManager() const
{
assert (mStateManager);
return mStateManager;
return mStateManager.get();
}
MWBase::LuaManager *MWBase::Environment::getLuaManager() const
{
assert (mLuaManager);
return mLuaManager;
return mLuaManager.get();
}
Resource::ResourceSystem *MWBase::Environment::getResourceSystem() const
@ -174,35 +170,17 @@ float MWBase::Environment::getFrameDuration() const
void MWBase::Environment::cleanup()
{
delete mMechanicsManager;
mMechanicsManager = nullptr;
delete mDialogueManager;
mDialogueManager = nullptr;
delete mJournal;
mJournal = nullptr;
delete mScriptManager;
mScriptManager = nullptr;
delete mWindowManager;
mWindowManager = nullptr;
delete mWorld;
mWorld = nullptr;
delete mSoundManager;
mSoundManager = nullptr;
delete mInputManager;
mInputManager = nullptr;
delete mStateManager;
mStateManager = nullptr;
delete mLuaManager;
mLuaManager = nullptr;
mMechanicsManager.reset();
mDialogueManager.reset();
mJournal.reset();
mScriptManager.reset();
mWindowManager.reset();
mWorld.reset();
mSoundManager.reset();
mInputManager.reset();
mStateManager.reset();
mLuaManager.reset();
mResourceSystem = nullptr;
}
const MWBase::Environment& MWBase::Environment::get()

@ -1,6 +1,8 @@
#ifndef GAME_BASE_ENVIRONMENT_H
#define GAME_BASE_ENVIRONMENT_H
#include <memory>
namespace osg
{
class Stats;
@ -28,25 +30,23 @@ namespace MWBase
///
/// This class allows each mw-subsystem to access any others subsystem's top-level manager class.
///
/// \attention Environment takes ownership of the manager class instances it is handed over in
/// the set* functions.
class Environment
{
static Environment *sThis;
World *mWorld;
SoundManager *mSoundManager;
ScriptManager *mScriptManager;
WindowManager *mWindowManager;
MechanicsManager *mMechanicsManager;
DialogueManager *mDialogueManager;
Journal *mJournal;
InputManager *mInputManager;
StateManager *mStateManager;
LuaManager *mLuaManager;
Resource::ResourceSystem *mResourceSystem;
float mFrameDuration;
float mFrameRateLimit;
std::unique_ptr<World> mWorld;
std::unique_ptr<SoundManager> mSoundManager;
std::unique_ptr<ScriptManager> mScriptManager;
std::unique_ptr<WindowManager> mWindowManager;
std::unique_ptr<MechanicsManager> mMechanicsManager;
std::unique_ptr<DialogueManager> mDialogueManager;
std::unique_ptr<Journal> mJournal;
std::unique_ptr<InputManager> mInputManager;
std::unique_ptr<StateManager> mStateManager;
std::unique_ptr<LuaManager> mLuaManager;
Resource::ResourceSystem* mResourceSystem{};
float mFrameDuration{};
float mFrameRateLimit{};
Environment (const Environment&);
///< not implemented
@ -60,25 +60,25 @@ namespace MWBase
~Environment();
void setWorld (World *world);
void setWorld (std::unique_ptr<World>&& world);
void setSoundManager (SoundManager *soundManager);
void setSoundManager (std::unique_ptr<SoundManager>&& soundManager);
void setScriptManager (MWBase::ScriptManager *scriptManager);
void setScriptManager (std::unique_ptr<ScriptManager>&& scriptManager);
void setWindowManager (WindowManager *windowManager);
void setWindowManager (std::unique_ptr<WindowManager>&& windowManager);
void setMechanicsManager (MechanicsManager *mechanicsManager);
void setMechanicsManager (std::unique_ptr<MechanicsManager>&& mechanicsManager);
void setDialogueManager (DialogueManager *dialogueManager);
void setDialogueManager (std::unique_ptr<DialogueManager>&& dialogueManager);
void setJournal (Journal *journal);
void setJournal (std::unique_ptr<Journal>&& journal);
void setInputManager (InputManager *inputManager);
void setInputManager (std::unique_ptr<InputManager>&& inputManager);
void setStateManager (StateManager *stateManager);
void setStateManager (std::unique_ptr<StateManager>&& stateManager);
void setLuaManager (LuaManager *luaManager);
void setLuaManager (std::unique_ptr<LuaManager>&& luaManager);
void setResourceSystem (Resource::ResourceSystem *resourceSystem);

@ -36,6 +36,7 @@ namespace MWBase
virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0;
virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0;
virtual void appliedToObject(const MWWorld::Ptr& toPtr, std::string_view recordId, const MWWorld::Ptr& fromPtr) = 0;
virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0;
// TODO: notify LuaManager about other events
// virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object,
// const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) = 0;
@ -56,7 +57,9 @@ namespace MWBase
bool mRun = false;
float mMovement = 0;
float mSideMovement = 0;
float mTurn = 0;
float mPitchChange = 0;
float mYawChange = 0;
int mUse = 0;
};
virtual ActorControls* getActorControls(const MWWorld::Ptr&) const = 0;

@ -257,11 +257,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
case SelectWrapper::Function_PcHealthPercent:
{
MWWorld::Ptr player = MWMechanics::getPlayer();
float ratio = player.getClass().getCreatureStats (player).getHealth().getCurrent() /
player.getClass().getCreatureStats (player).getHealth().getModified();
return select.selectCompare (static_cast<int>(ratio*100));
return select.selectCompare(static_cast<int>(player.getClass().getCreatureStats(player).getHealth().getRatio() * 100));
}
case SelectWrapper::Function_PcDynamicStat:
@ -276,10 +272,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
case SelectWrapper::Function_HealthPercent:
{
float ratio = mActor.getClass().getCreatureStats (mActor).getHealth().getCurrent() /
mActor.getClass().getCreatureStats (mActor).getHealth().getModified();
return select.selectCompare (static_cast<int>(ratio*100));
return select.selectCompare(static_cast<int>(mActor.getClass().getCreatureStats(mActor).getHealth().getRatio() * 100));
}
default:

@ -7,6 +7,8 @@
#include <components/esm3/queststate.hpp>
#include <components/esm3/journalentry.hpp>
#include <components/misc/stringops.hpp>
#include "../mwworld/esmstore.hpp"
#include "../mwworld/class.hpp"
@ -93,7 +95,16 @@ namespace MWDialogue
StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index, actor);
Quest& quest = getQuest (id);
quest.addEntry (entry); // we are doing slicing on purpose here
if(quest.addEntry(entry)) // we are doing slicing on purpose here
{
// Restart all "other" quests with the same name as well
std::string name = quest.getName();
for(auto& it : mQuests)
{
if(it.second.isFinished() && Misc::StringUtils::ciEqual(it.second.getName(), name))
it.second.setFinished(false);
}
}
// there is no need to show empty entries in journal
if (!entry.getText().empty())

@ -1,5 +1,7 @@
#include "quest.hpp"
#include <algorithm>
#include <components/esm3/queststate.hpp>
#include "../mwworld/esmstore.hpp"
@ -50,42 +52,33 @@ namespace MWDialogue
return mFinished;
}
void Quest::addEntry (const JournalEntry& entry)
void Quest::setFinished(bool finished)
{
int index = -1;
mFinished = finished;
}
bool Quest::addEntry (const JournalEntry& entry)
{
const ESM::Dialogue *dialogue =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (entry.mTopic);
for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin());
iter!=dialogue->mInfo.end(); ++iter)
if (iter->mId == entry.mInfoId)
{
index = iter->mData.mJournalIndex;
break;
}
auto info = std::find_if(dialogue->mInfo.begin(), dialogue->mInfo.end(), [&](const auto& info) { return info.mId == entry.mInfoId; });
if (index==-1)
if (info == dialogue->mInfo.end() || info->mData.mJournalIndex == -1)
throw std::runtime_error ("unknown journal entry for topic " + mTopic);
for (auto &info : dialogue->mInfo)
{
if (info.mData.mJournalIndex == index
&& (info.mQuestStatus == ESM::DialInfo::QS_Finished || info.mQuestStatus == ESM::DialInfo::QS_Restart))
{
mFinished = (info.mQuestStatus == ESM::DialInfo::QS_Finished);
break;
}
}
if (info->mQuestStatus == ESM::DialInfo::QS_Finished || info->mQuestStatus == ESM::DialInfo::QS_Restart)
mFinished = info->mQuestStatus == ESM::DialInfo::QS_Finished;
if (index > mIndex)
mIndex = index;
if (info->mData.mJournalIndex > mIndex)
mIndex = info->mData.mJournalIndex;
for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter)
if (iter->mInfoId==entry.mInfoId)
return;
return info->mQuestStatus == ESM::DialInfo::QS_Restart;
mEntries.push_back (entry); // we want slicing here
return info->mQuestStatus == ESM::DialInfo::QS_Restart;
}
void Quest::write (ESM::QuestState& state) const

@ -33,9 +33,10 @@ namespace MWDialogue
///< Calling this function with a non-existent index will throw an exception.
bool isFinished() const;
void setFinished(bool finished);
void addEntry (const JournalEntry& entry) override;
///< Add entry and adjust index accordingly.
bool addEntry (const JournalEntry& entry) override;
///< Add entry and adjust index accordingly. Returns true if the quest should be restarted.
///
/// \note Redundant entries are ignored, but the index is still adjusted.

@ -18,7 +18,7 @@ namespace MWDialogue
Topic::~Topic()
{}
void Topic::addEntry (const JournalEntry& entry)
bool Topic::addEntry (const JournalEntry& entry)
{
if (entry.mTopic!=mTopic)
throw std::runtime_error ("topic does not match: " + mTopic);
@ -27,10 +27,11 @@ namespace MWDialogue
for (Topic::TEntryIter it = mEntries.begin(); it != mEntries.end(); ++it)
{
if (it->mInfoId == entry.mInfoId)
return;
return false;
}
mEntries.push_back (entry); // we want slicing here
return false;
}
void Topic::insertEntry (const ESM::JournalEntry& entry)

@ -35,7 +35,7 @@ namespace MWDialogue
virtual ~Topic();
virtual void addEntry (const JournalEntry& entry);
virtual bool addEntry (const JournalEntry& entry);
///< Add entry
///
/// \note Redundant entries are ignored.

@ -608,7 +608,7 @@ namespace MWGui
mEnemyHealth->setProgressRange(100);
// Health is usually cast to int before displaying. Actors die whenever they are < 1 health.
// Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :)
mEnemyHealth->setProgressPosition(static_cast<size_t>(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100));
mEnemyHealth->setProgressPosition(static_cast<size_t>(stats.getHealth().getRatio() * 100));
static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fNPCHealthBarFade")->mValue.getFloat();
if (fNPCHealthBarFade > 0.f)

@ -399,7 +399,7 @@ namespace MWGui
skillWidget = mSkillList->createWidget<Widgets::MWSkill>("MW_StatNameValue", coord1, MyGUI::Align::Default,
std::string("Skill") + MyGUI::utility::toString(i));
skillWidget->setSkillNumber(skillId);
skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(static_cast<float>(race->mData.mBonus[i].mBonus)));
skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(static_cast<float>(race->mData.mBonus[i].mBonus), 0.f));
ToolTips::createSkillToolTip(skillWidget, skillId);

@ -1,18 +1,20 @@
#include "settingswindow.hpp"
#include <regex>
#include <iomanip>
#include <numeric>
#include <array>
#include <MyGUI_ScrollBar.h>
#include <MyGUI_Window.h>
#include <MyGUI_ComboBox.h>
#include <MyGUI_ScrollView.h>
#include <MyGUI_Gui.h>
#include <MyGUI_TabControl.h>
#include <MyGUI_TabItem.h>
#include <SDL_video.h>
#include <iomanip>
#include <numeric>
#include <array>
#include <components/debug/debuglog.hpp>
#include <components/misc/stringops.hpp>
#include <components/misc/constants.hpp>
@ -21,6 +23,7 @@
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/lua_ui/scriptsettings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@ -33,7 +36,6 @@
namespace
{
std::string textureMipmappingToStr(const std::string& val)
{
if (val == "linear") return "Trilinear";
@ -205,9 +207,9 @@ namespace MWGui
}
}
SettingsWindow::SettingsWindow() :
WindowBase("openmw_settings_window.layout"),
mKeyboardMode(true)
SettingsWindow::SettingsWindow() : WindowBase("openmw_settings_window.layout")
, mKeyboardMode(true)
, mCurrentPage(-1)
{
bool terrain = Settings::Manager::getBool("distant terrain", "Terrain");
const std::string widgetName = terrain ? "RenderingDistanceSlider" : "LargeRenderingDistanceSlider";
@ -236,6 +238,12 @@ namespace MWGui
getWidget(mLightingMethodButton, "LightingMethodButton");
getWidget(mLightsResetButton, "LightsResetButton");
getWidget(mMaxLights, "MaxLights");
getWidget(mScriptFilter, "ScriptFilter");
getWidget(mScriptList, "ScriptList");
getWidget(mScriptBox, "ScriptBox");
getWidget(mScriptView, "ScriptView");
getWidget(mScriptAdapter, "ScriptAdapter");
getWidget(mScriptDisabled, "ScriptDisabled");
#ifndef WIN32
// hide gamma controls since it currently does not work under Linux
@ -321,6 +329,9 @@ namespace MWGui
mKeyboardSwitch->setStateSelected(true);
mControllerSwitch->setStateSelected(false);
mScriptFilter->eventEditTextChange += MyGUI::newDelegate(this, &SettingsWindow::onScriptFilterChange);
mScriptList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SettingsWindow::onScriptListSelection);
}
void SettingsWindow::onTabChanged(MyGUI::TabControl* /*_sender*/, size_t /*index*/)
@ -699,6 +710,118 @@ namespace MWGui
mControlsBox->setVisibleVScroll(true);
}
void SettingsWindow::resizeScriptSettings()
{
constexpr int minListWidth = 150;
constexpr float relativeListWidth = 0.2f;
constexpr int padding = 2;
constexpr int outerPadding = padding * 2;
MyGUI::IntSize parentSize = mScriptFilter->getParent()->getClientCoord().size();
int listWidth = std::max(minListWidth, static_cast<int>(parentSize.width * relativeListWidth));
int filterHeight = mScriptFilter->getSize().height;
int listHeight = parentSize.height - mScriptList->getPosition().top - outerPadding;
mScriptFilter->setSize({ listWidth, filterHeight });
mScriptList->setSize({ listWidth, listHeight });
mScriptBox->setPosition({ listWidth + padding, 0 });
mScriptBox->setSize({ parentSize.width - listWidth - padding, parentSize.height - outerPadding });
mScriptDisabled->setPosition({0, 0});
mScriptDisabled->setSize(parentSize);
}
namespace
{
std::string escapeRegex(const std::string& str)
{
static const std::regex specialChars(R"r([\^\.\[\$\(\)\|\*\+\?\{])r", std::regex_constants::extended);
return std::regex_replace(str, specialChars, R"(\$&)");
}
std::regex wordSearch(const std::string& query)
{
static const std::regex wordsRegex(R"([^[:space:]]+)", std::regex_constants::extended);
auto wordsBegin = std::sregex_iterator(query.begin(), query.end(), wordsRegex);
auto wordsEnd = std::sregex_iterator();
std::string searchRegex("(");
for (auto it = wordsBegin; it != wordsEnd; ++it)
{
if (it != wordsBegin)
searchRegex += '|';
searchRegex += escapeRegex(query.substr(it->position(), it->length()));
}
searchRegex += ')';
// query had only whitespace characters
if (searchRegex == "()")
searchRegex = "^(.*)$";
return std::regex(searchRegex, std::regex_constants::extended | std::regex_constants::icase);
}
double weightedSearch(const std::regex& regex, const std::string& text)
{
std::smatch matches;
std::regex_search(text, matches, regex);
// need a signed value, so cast to double (not an integer type to guarantee no overflow)
return static_cast<double>(matches.size());
}
}
void SettingsWindow::renderScriptSettings()
{
mScriptAdapter->detach();
mCurrentPage = -1;
mScriptList->removeAllItems();
mScriptView->setCanvasSize({0, 0});
struct WeightedPage {
size_t mIndex;
std::string mName;
double mNameWeight;
double mHintWeight;
constexpr auto tie() const { return std::tie(mNameWeight, mHintWeight, mName); }
constexpr bool operator<(const WeightedPage& rhs) const { return tie() < rhs.tie(); }
};
std::regex searchRegex = wordSearch(mScriptFilter->getCaption());
std::vector<WeightedPage> weightedPages;
weightedPages.reserve(LuaUi::scriptSettingsPageCount());
for (size_t i = 0; i < LuaUi::scriptSettingsPageCount(); ++i)
{
LuaUi::ScriptSettingsPage page = LuaUi::scriptSettingsPageAt(i);
double nameWeight = weightedSearch(searchRegex, page.mName);
double hintWeight = weightedSearch(searchRegex, page.mSearchHints);
if ((nameWeight + hintWeight) > 0)
weightedPages.push_back({ i, page.mName, -nameWeight, -hintWeight });
}
std::sort(weightedPages.begin(), weightedPages.end());
for (const WeightedPage& weightedPage : weightedPages)
mScriptList->addItem(weightedPage.mName, weightedPage.mIndex);
// Hide script settings tab when the game world isn't loaded and scripts couldn't add their settings
bool disabled = LuaUi::scriptSettingsPageCount() == 0;
mScriptDisabled->setVisible(disabled);
mScriptFilter->setVisible(!disabled);
mScriptList->setVisible(!disabled);
mScriptBox->setVisible(!disabled);
}
void SettingsWindow::onScriptFilterChange(MyGUI::EditBox*)
{
renderScriptSettings();
}
void SettingsWindow::onScriptListSelection(MyGUI::ListBox*, size_t index)
{
mScriptAdapter->detach();
mCurrentPage = -1;
if (index < mScriptList->getItemCount())
{
mCurrentPage = *mScriptList->getItemDataAt<size_t>(index);
LuaUi::attachPageAt(mCurrentPage, mScriptAdapter);
}
mScriptView->setCanvasSize(mScriptAdapter->getSize());
}
void SettingsWindow::onRebindAction(MyGUI::Widget* _sender)
{
int actionId = *_sender->getUserData<int>();
@ -744,12 +867,15 @@ namespace MWGui
updateControlsBox();
updateLightSettings();
resetScrollbars();
renderScriptSettings();
resizeScriptSettings();
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton);
}
void SettingsWindow::onWindowResize(MyGUI::Window *_sender)
{
layoutControlsBox();
resizeScriptSettings();
}
void SettingsWindow::computeMinimumWindowSize()

@ -1,6 +1,8 @@
#ifndef MWGUI_SETTINGS_H
#define MWGUI_SETTINGS_H
#include <components/lua_ui/adapter.hpp>
#include "windowbase.hpp"
namespace MWGui
@ -44,6 +46,14 @@ namespace MWGui
MyGUI::Button* mControllerSwitch;
bool mKeyboardMode; //if true, setting up the keyboard. Otherwise, it's controller
MyGUI::EditBox* mScriptFilter;
MyGUI::ListBox* mScriptList;
MyGUI::Widget* mScriptBox;
MyGUI::ScrollView* mScriptView;
LuaUi::LuaAdapter* mScriptAdapter;
MyGUI::EditBox* mScriptDisabled;
int mCurrentPage;
void onTabChanged(MyGUI::TabControl* _sender, size_t index);
void onOkButtonClicked(MyGUI::Widget* _sender);
void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos);
@ -71,12 +81,17 @@ namespace MWGui
void onWindowResize(MyGUI::Window* _sender);
void onScriptFilterChange(MyGUI::EditBox*);
void onScriptListSelection(MyGUI::ListBox*, size_t index);
void apply();
void configureWidgets(MyGUI::Widget* widget, bool init);
void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value);
void layoutControlsBox();
void resizeScriptSettings();
void renderScriptSettings();
void computeMinimumWindowSize();

@ -179,7 +179,7 @@ namespace MWGui
void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value)
{
int current = static_cast<int>(value.getCurrent());
int modified = static_cast<int>(value.getModified());
int modified = static_cast<int>(value.getModified(false));
// Fatigue can be negative
if (id != "FBar")

@ -181,8 +181,6 @@ namespace MWGui
public:
MWSpell();
typedef MWMechanics::Stat<int> SpellValue;
void setSpellId(const std::string &id);
/**
@ -215,8 +213,6 @@ namespace MWGui
public:
MWEffectList();
typedef MWMechanics::Stat<int> EnchantmentValue;
enum EffectFlags
{
EF_NoTarget = 0x01, // potions have no target (target is always the player)

@ -6,10 +6,13 @@
#include <components/lua/luastate.hpp>
#include <components/settings/settings.hpp>
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/player.hpp"
#include <apps/openmw/mwbase/luamanager.hpp>
#include <apps/openmw/mwworld/action.hpp>
#include <apps/openmw/mwworld/cellstore.hpp>
#include <apps/openmw/mwworld/class.hpp>
#include <apps/openmw/mwworld/inventorystore.hpp>
#include <apps/openmw/mwworld/player.hpp>
namespace MWLua
{
@ -145,4 +148,24 @@ namespace MWLua
tryEquipToSlot(anySlot, item);
}
void ActivateAction::apply(WorldView& worldView) const
{
MWWorld::Ptr object = worldView.getObjectRegistry()->getPtr(mObject, true);
if (object.isEmpty())
throw std::runtime_error(std::string("Object not found: " + idToString(mObject)));
MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, true);
if (actor.isEmpty())
throw std::runtime_error(std::string("Actor not found: " + idToString(mActor)));
MWBase::Environment::get().getLuaManager()->objectActivated(object, actor);
std::shared_ptr<MWWorld::Action> action = object.getClass().activate(object, actor);
action->execute(actor);
}
std::string ActivateAction::toString() const
{
return std::string("ActivateAction object=") + idToString(mObject) +
std::string(" actor=") + idToString(mActor);
}
}

@ -65,6 +65,20 @@ namespace MWLua
Equipment mEquipment;
};
class ActivateAction final : public Action
{
public:
ActivateAction(LuaUtil::LuaState* state, ObjectId object, ObjectId actor)
: Action(state), mObject(object), mActor(actor) {}
void apply(WorldView&) const override;
std::string toString() const override;
private:
ObjectId mObject;
ObjectId mActor;
};
}
#endif // MWLUA_ACTIONS_H

@ -1,9 +1,17 @@
#include "localscripts.hpp"
#include <components/esm3/loadcell.hpp>
#include "../mwworld/ptr.hpp"
#include "../mwworld/class.hpp"
#include "../mwmechanics/aisequence.hpp"
#include "../mwmechanics/aicombat.hpp"
#include "../mwmechanics/aiescort.hpp"
#include "../mwmechanics/aifollow.hpp"
#include "../mwmechanics/aipursue.hpp"
#include "../mwmechanics/aitravel.hpp"
#include "../mwmechanics/aiwander.hpp"
#include "../mwmechanics/aipackage.hpp"
#include "luamanagerimp.hpp"
@ -27,9 +35,11 @@ namespace MWLua
[](ActorControls& c, const TYPE& v) { c.FIELD = v; c.mChanged = true; })
controls["movement"] = CONTROL(float, mMovement);
controls["sideMovement"] = CONTROL(float, mSideMovement);
controls["turn"] = CONTROL(float, mTurn);
controls["pitchChange"] = CONTROL(float, mPitchChange);
controls["yawChange"] = CONTROL(float, mYawChange);
controls["run"] = CONTROL(bool, mRun);
controls["jump"] = CONTROL(bool, mJump);
controls["use"] = CONTROL(int, mUse);
#undef CONTROL
sol::usertype<SelfObject> selfAPI =
@ -58,35 +68,110 @@ namespace MWLua
}
context.mLuaManager->addAction(std::make_unique<SetEquipmentAction>(context.mLua, obj.id(), std::move(eqp)));
};
selfAPI["getCombatTarget"] = [worldView=context.mWorldView](SelfObject& self) -> sol::optional<LObject>
using AiPackage = MWMechanics::AiPackage;
sol::usertype<AiPackage> aiPackage = context.mLua->sol().new_usertype<AiPackage>("AiPackage");
aiPackage["type"] = sol::readonly_property([](const AiPackage& p) -> std::string_view
{
switch (p.getTypeId())
{
case MWMechanics::AiPackageTypeId::Wander: return "Wander";
case MWMechanics::AiPackageTypeId::Travel: return "Travel";
case MWMechanics::AiPackageTypeId::Escort: return "Escort";
case MWMechanics::AiPackageTypeId::Follow: return "Follow";
case MWMechanics::AiPackageTypeId::Activate: return "Activate";
case MWMechanics::AiPackageTypeId::Combat: return "Combat";
case MWMechanics::AiPackageTypeId::Pursue: return "Pursue";
case MWMechanics::AiPackageTypeId::AvoidDoor: return "AvoidDoor";
case MWMechanics::AiPackageTypeId::Face: return "Face";
case MWMechanics::AiPackageTypeId::Breathe: return "Breathe";
case MWMechanics::AiPackageTypeId::Cast: return "Cast";
default: return "Unknown";
}
});
aiPackage["target"] = sol::readonly_property([worldView=context.mWorldView](const AiPackage& p) -> sol::optional<LObject>
{
MWWorld::Ptr target = p.getTarget();
if (target.isEmpty())
return sol::nullopt;
else
return LObject(getId(target), worldView->getObjectRegistry());
});
aiPackage["sideWithTarget"] = sol::readonly_property([](const AiPackage& p) { return p.sideWithTarget(); });
aiPackage["destination"] = sol::readonly_property([](const AiPackage& p) { return p.getDestination(); });
selfAPI["_getActiveAiPackage"] = [](SelfObject& self) -> sol::optional<std::shared_ptr<AiPackage>>
{
const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
MWWorld::Ptr target;
if (ai.getCombatTarget(target))
return LObject(getId(target), worldView->getObjectRegistry());
if (ai.isEmpty())
return sol::nullopt;
else
return {};
return *ai.begin();
};
selfAPI["stopCombat"] = [](SelfObject& self)
selfAPI["_iterateAndFilterAiSequence"] = [](SelfObject& self, sol::function callback)
{
const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
ai.stopCombat();
ai.erasePackagesIf([&](auto& entry)
{
bool keep = LuaUtil::call(callback, entry).template get<bool>();
return !keep;
});
};
selfAPI["startCombat"] = [](SelfObject& self, const LObject& target)
selfAPI["_startAiCombat"] = [](SelfObject& self, const LObject& target)
{
const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
ai.stack(MWMechanics::AiCombat(target.ptr()), ptr);
};
selfAPI["_startAiPursue"] = [](SelfObject& self, const LObject& target)
{
const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
ai.stack(MWMechanics::AiPursue(target.ptr()), ptr);
};
selfAPI["_startAiFollow"] = [](SelfObject& self, const LObject& target)
{
const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
ai.stack(MWMechanics::AiFollow(target.ptr()), ptr);
};
selfAPI["_startAiEscort"] = [](SelfObject& self, const LObject& target, LCell cell,
float duration, const osg::Vec3f& dest)
{
const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
// TODO: change AiEscort implementation to accept ptr instead of a non-unique refId.
const std::string& refId = target.ptr().getCellRef().getRefId();
int gameHoursDuration = static_cast<int>(std::ceil(duration / 3600.0));
const ESM::Cell* esmCell = cell.mStore->getCell();
if (esmCell->isExterior())
ai.stack(MWMechanics::AiEscort(refId, gameHoursDuration, dest.x(), dest.y(), dest.z(), false), ptr);
else
ai.stack(MWMechanics::AiEscort(refId, esmCell->mName, gameHoursDuration, dest.x(), dest.y(), dest.z(), false), ptr);
};
selfAPI["_startAiWander"] = [](SelfObject& self, int distance, float duration)
{
const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
int gameHoursDuration = static_cast<int>(std::ceil(duration / 3600.0));
ai.stack(MWMechanics::AiWander(distance, gameHoursDuration, 0, {}, false), ptr);
};
selfAPI["_startAiTravel"] = [](SelfObject& self, const osg::Vec3f& target)
{
const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
ai.stack(MWMechanics::AiTravel(target.x(), target.y(), target.z(), false), ptr);
};
}
LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj, ESM::LuaScriptCfg::Flags autoStartMode)
: LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id()), autoStartMode), mData(obj)
{
this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData));
registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers});
registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers});
}
void LocalScripts::receiveEngineEvent(const EngineEvent& event)
@ -104,6 +189,10 @@ namespace MWLua
mData.mIsActive = false;
callEngineHandlers(mOnInactiveHandlers);
}
else if constexpr (std::is_same_v<EventT, OnActivated>)
{
callEngineHandlers(mOnActivatedHandlers, arg.mActivatingActor);
}
else
{
static_assert(std::is_same_v<EventT, OnConsume>);

@ -33,11 +33,15 @@ namespace MWLua
struct OnActive {};
struct OnInactive {};
struct OnActivated
{
LObject mActivatingActor;
};
struct OnConsume
{
std::string mRecordId;
};
using EngineEvent = std::variant<OnActive, OnInactive, OnConsume>;
using EngineEvent = std::variant<OnActive, OnInactive, OnConsume, OnActivated>;
void receiveEngineEvent(const EngineEvent&);
@ -48,6 +52,7 @@ namespace MWLua
EngineHandlerList mOnActiveHandlers{"onActive"};
EngineHandlerList mOnInactiveHandlers{"onInactive"};
EngineHandlerList mOnConsumeHandlers{"onConsume"};
EngineHandlerList mOnActivatedHandlers{"onActivated"};
};
}

@ -16,10 +16,10 @@
namespace MWLua
{
static sol::table definitionList(LuaUtil::LuaState& lua, std::initializer_list<std::string> values)
static sol::table definitionList(LuaUtil::LuaState& lua, std::initializer_list<std::string_view> values)
{
sol::table res(lua.sol(), sol::create);
for (const std::string& v : values)
for (const std::string_view& v : values)
res[v] = v;
return LuaUtil::makeReadOnly(res);
}
@ -49,7 +49,7 @@ namespace MWLua
{
auto* lua = context.mLua;
sol::table api(lua->sol(), sol::create);
api["API_REVISION"] = 14;
api["API_REVISION"] = 17;
api["quit"] = [lua]()
{
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
@ -62,8 +62,11 @@ namespace MWLua
addTimeBindings(api, context, false);
api["OBJECT_TYPE"] = definitionList(*lua,
{
"Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient",
"Light", "Miscellaneous", "NPC", "Player", "Potion", "Static", "Weapon"
ObjectTypeName::Activator, ObjectTypeName::Armor, ObjectTypeName::Book, ObjectTypeName::Clothing,
ObjectTypeName::Creature, ObjectTypeName::Door, ObjectTypeName::Ingredient, ObjectTypeName::Light,
ObjectTypeName::MiscItem, ObjectTypeName::NPC, ObjectTypeName::Player, ObjectTypeName::Potion,
ObjectTypeName::Static, ObjectTypeName::Weapon, ObjectTypeName::Activator, ObjectTypeName::Lockpick,
ObjectTypeName::Probe, ObjectTypeName::Repair
});
api["EQUIPMENT_SLOT"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, int>({
{"Helmet", MWWorld::InventoryStore::Slot_Helmet},

@ -114,6 +114,7 @@ namespace MWLua
void LuaManager::update()
{
static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua");
if (mPlayer.isEmpty())
return; // The game is not started yet.
@ -172,7 +173,8 @@ namespace MWLua
LObject obj(e.mDest, objectRegistry);
if (!obj.isValid())
{
Log(Debug::Verbose) << "Can not call engine handlers: object" << idToString(e.mDest) << " is not found";
if (luaDebug)
Log(Debug::Verbose) << "Can not call engine handlers: object" << idToString(e.mDest) << " is not found";
continue;
}
LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts();
@ -204,7 +206,7 @@ namespace MWLua
GObject obj(id, objectRegistry);
if (obj.isValid())
mGlobalScripts.actorActive(obj);
else
else if (luaDebug)
Log(Debug::Verbose) << "Can not call onActorActive engine handler: object" << idToString(id) << " is already removed";
}
mActorAddedEvents.clear();
@ -352,6 +354,11 @@ namespace MWLua
mLocalEngineEvents.push_back({getId(toPtr), LocalScripts::OnConsume{std::string(recordId)}});
}
void LuaManager::objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor)
{
mLocalEngineEvents.push_back({getId(object), LocalScripts::OnActivated{LObject(getId(actor), mWorldView.getObjectRegistry())}});
}
MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const
{
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();

@ -49,6 +49,7 @@ namespace MWLua
void deregisterObject(const MWWorld::Ptr& ptr) override;
void inputEvent(const InputEvent& event) override { mInputEvents.push_back(event); }
void appliedToObject(const MWWorld::Ptr& toPtr, std::string_view recordId, const MWWorld::Ptr& fromPtr) override;
void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override;
MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override;

@ -17,20 +17,24 @@ namespace MWLua
};
const static std::unordered_map<ESM::RecNameInts, LuaObjectTypeInfo> luaObjectTypeInfo = {
{ESM::REC_ACTI, {"Activator", ESM::LuaScriptCfg::sActivator}},
{ESM::REC_ARMO, {"Armor", ESM::LuaScriptCfg::sArmor}},
{ESM::REC_BOOK, {"Book", ESM::LuaScriptCfg::sBook}},
{ESM::REC_CLOT, {"Clothing", ESM::LuaScriptCfg::sClothing}},
{ESM::REC_CONT, {"Container", ESM::LuaScriptCfg::sContainer}},
{ESM::REC_CREA, {"Creature", ESM::LuaScriptCfg::sCreature}},
{ESM::REC_DOOR, {"Door", ESM::LuaScriptCfg::sDoor}},
{ESM::REC_INGR, {"Ingredient", ESM::LuaScriptCfg::sIngredient}},
{ESM::REC_LIGH, {"Light", ESM::LuaScriptCfg::sLight}},
{ESM::REC_MISC, {"Miscellaneous", ESM::LuaScriptCfg::sMiscItem}},
{ESM::REC_NPC_, {"NPC", ESM::LuaScriptCfg::sNPC}},
{ESM::REC_ALCH, {"Potion", ESM::LuaScriptCfg::sPotion}},
{ESM::REC_STAT, {"Static"}},
{ESM::REC_WEAP, {"Weapon", ESM::LuaScriptCfg::sWeapon}},
{ESM::REC_ACTI, {ObjectTypeName::Activator, ESM::LuaScriptCfg::sActivator}},
{ESM::REC_ARMO, {ObjectTypeName::Armor, ESM::LuaScriptCfg::sArmor}},
{ESM::REC_BOOK, {ObjectTypeName::Book, ESM::LuaScriptCfg::sBook}},
{ESM::REC_CLOT, {ObjectTypeName::Clothing, ESM::LuaScriptCfg::sClothing}},
{ESM::REC_CONT, {ObjectTypeName::Container, ESM::LuaScriptCfg::sContainer}},
{ESM::REC_CREA, {ObjectTypeName::Creature, ESM::LuaScriptCfg::sCreature}},
{ESM::REC_DOOR, {ObjectTypeName::Door, ESM::LuaScriptCfg::sDoor}},
{ESM::REC_INGR, {ObjectTypeName::Ingredient, ESM::LuaScriptCfg::sIngredient}},
{ESM::REC_LIGH, {ObjectTypeName::Light, ESM::LuaScriptCfg::sLight}},
{ESM::REC_MISC, {ObjectTypeName::MiscItem, ESM::LuaScriptCfg::sMiscItem}},
{ESM::REC_NPC_, {ObjectTypeName::NPC, ESM::LuaScriptCfg::sNPC}},
{ESM::REC_ALCH, {ObjectTypeName::Potion, ESM::LuaScriptCfg::sPotion}},
{ESM::REC_STAT, {ObjectTypeName::Static}},
{ESM::REC_WEAP, {ObjectTypeName::Weapon, ESM::LuaScriptCfg::sWeapon}},
{ESM::REC_APPA, {ObjectTypeName::Apparatus}},
{ESM::REC_LOCK, {ObjectTypeName::Lockpick}},
{ESM::REC_PROB, {ObjectTypeName::Probe}},
{ESM::REC_REPA, {ObjectTypeName::Repair}},
};
std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback)

@ -14,6 +14,31 @@
namespace MWLua
{
namespace ObjectTypeName
{
// Names of object types in Lua.
// These names are part of OpenMW Lua API.
constexpr std::string_view Activator = "Activator";
constexpr std::string_view Armor = "Armor";
constexpr std::string_view Book = "Book";
constexpr std::string_view Clothing = "Clothing";
constexpr std::string_view Container = "Container";
constexpr std::string_view Creature = "Creature";
constexpr std::string_view Door = "Door";
constexpr std::string_view Ingredient = "Ingredient";
constexpr std::string_view Light = "Light";
constexpr std::string_view MiscItem = "Miscellaneous";
constexpr std::string_view NPC = "NPC";
constexpr std::string_view Player = "Player";
constexpr std::string_view Potion = "Potion";
constexpr std::string_view Static = "Static";
constexpr std::string_view Weapon = "Weapon";
constexpr std::string_view Apparatus = "Apparatus";
constexpr std::string_view Lockpick = "Lockpick";
constexpr std::string_view Probe = "Probe";
constexpr std::string_view Repair = "Repair";
}
// ObjectId is a unique identifier of a game object.
// It can change only if the order of content files was change.
using ObjectId = ESM::RefNum;

@ -72,7 +72,7 @@ namespace MWLua
else
throw std::runtime_error("Index out of range");
};
listT["ipairs"] = [registry](const ListT& list)
listT[sol::meta_function::ipairs] = [registry](const ListT& list)
{
auto iter = [registry](const ListT& l, int64_t i) -> sol::optional<std::tuple<int64_t, ObjectT>>
{
@ -137,6 +137,14 @@ namespace MWLua
const MWWorld::Class& cls = o.ptr().getClass();
return cls.getWalkSpeed(o.ptr());
};
objectT["activateBy"] = [context](const ObjectT& o, const ObjectT& actor)
{
uint32_t esmRecordType = actor.ptr().getType();
if (esmRecordType != ESM::REC_CREA && esmRecordType != ESM::REC_NPC_)
throw std::runtime_error("The argument of `activateBy` must be an actor who activates the object. Got: " +
ptrToString(actor.ptr()));
context.mLuaManager->addAction(std::make_unique<ActivateAction>(context.mLua, o.id(), actor.id()));
};
if constexpr (std::is_same_v<ObjectT, GObject>)
{ // Only for global scripts
@ -301,8 +309,38 @@ namespace MWLua
inventoryT[sol::meta_function::to_string] =
[](const InventoryT& inv) { return "Inventory[" + inv.mObj.toString() + "]"; };
auto getWithMask = [context](const InventoryT& inventory, int mask)
inventoryT["getAll"] = [worldView=context.mWorldView](const InventoryT& inventory, sol::optional<std::string_view> type)
{
int mask;
if (!type.has_value())
mask = MWWorld::ContainerStore::Type_All;
else if (*type == ObjectTypeName::Potion)
mask = MWWorld::ContainerStore::Type_Potion;
else if (*type == ObjectTypeName::Armor)
mask = MWWorld::ContainerStore::Type_Armor;
else if (*type == ObjectTypeName::Book)
mask = MWWorld::ContainerStore::Type_Book;
else if (*type == ObjectTypeName::Clothing)
mask = MWWorld::ContainerStore::Type_Clothing;
else if (*type == ObjectTypeName::Ingredient)
mask = MWWorld::ContainerStore::Type_Ingredient;
else if (*type == ObjectTypeName::Light)
mask = MWWorld::ContainerStore::Type_Light;
else if (*type == ObjectTypeName::MiscItem)
mask = MWWorld::ContainerStore::Type_Miscellaneous;
else if (*type == ObjectTypeName::Weapon)
mask = MWWorld::ContainerStore::Type_Weapon;
else if (*type == ObjectTypeName::Apparatus)
mask = MWWorld::ContainerStore::Type_Apparatus;
else if (*type == ObjectTypeName::Lockpick)
mask = MWWorld::ContainerStore::Type_Lockpick;
else if (*type == ObjectTypeName::Probe)
mask = MWWorld::ContainerStore::Type_Probe;
else if (*type == ObjectTypeName::Repair)
mask = MWWorld::ContainerStore::Type_Repair;
else
throw std::runtime_error(std::string("inventory:getAll doesn't support type " + std::string(*type)));
const MWWorld::Ptr& ptr = inventory.mObj.ptr();
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
ObjectIdList list = std::make_shared<std::vector<ObjectId>>();
@ -310,39 +348,12 @@ namespace MWLua
while (it.getType() != -1)
{
const MWWorld::Ptr& item = *(it++);
context.mWorldView->getObjectRegistry()->registerPtr(item);
worldView->getObjectRegistry()->registerPtr(item);
list->push_back(getId(item));
}
return ObjectList<ObjectT>{list};
};
inventoryT["getAll"] =
[getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_All); };
inventoryT["getPotions"] =
[getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Potion); };
inventoryT["getApparatuses"] =
[getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Apparatus); };
inventoryT["getArmor"] =
[getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Armor); };
inventoryT["getBooks"] =
[getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Book); };
inventoryT["getClothing"] =
[getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Clothing); };
inventoryT["getIngredients"] =
[getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Ingredient); };
inventoryT["getLights"] =
[getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Light); };
inventoryT["getLockpicks"] =
[getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Lockpick); };
inventoryT["getMiscellaneous"] =
[getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Miscellaneous); };
inventoryT["getProbes"] =
[getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Probe); };
inventoryT["getRepairKits"] =
[getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Repair); };
inventoryT["getWeapons"] =
[getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Weapon); };
inventoryT["countOf"] = [](const InventoryT& inventory, const std::string& recordId)
{
const MWWorld::Ptr& ptr = inventory.mObj.ptr();

@ -2,6 +2,8 @@
#include <components/lua_ui/element.hpp>
#include <components/lua_ui/layers.hpp>
#include <components/lua_ui/content.hpp>
#include <components/lua_ui/registerscriptsettings.hpp>
#include <components/lua_ui/alignment.hpp>
#include "context.hpp"
#include "actions.hpp"
@ -43,7 +45,7 @@ namespace MWLua
break;
}
}
catch (std::exception& e)
catch (std::exception&)
{
// prevent any actions on a potentially corrupted widget
mElement->mRoot = nullptr;
@ -238,6 +240,14 @@ namespace MWLua
typeTable.set(it.second, it.first);
api["TYPE"] = LuaUtil::makeReadOnly(typeTable);
api["ALIGNMENT"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, LuaUi::Alignment>({
{ "Start", LuaUi::Alignment::Start },
{ "Center", LuaUi::Alignment::Center },
{ "End", LuaUi::Alignment::End }
}));
api["registerSettingsPage"] = &LuaUi::registerSettingsPage;
return LuaUtil::makeReadOnly(api);
}
}

@ -73,23 +73,20 @@ bool isCommanded(const MWWorld::Ptr& actor)
// Check for command effects having ended and remove package if necessary
void adjustCommandedActor (const MWWorld::Ptr& actor)
{
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
if (!isCommanded(actor))
return;
bool hasCommandPackage = false;
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
auto it = stats.getAiSequence().begin();
for (; it != stats.getAiSequence().end(); ++it)
stats.getAiSequence().erasePackageIf([](auto& entry)
{
if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Follow &&
static_cast<const MWMechanics::AiFollow*>(it->get())->isCommanded())
if (entry->getTypeId() == MWMechanics::AiPackageTypeId::Follow &&
static_cast<const MWMechanics::AiFollow*>(entry.get())->isCommanded())
{
hasCommandPackage = true;
break;
return true;
}
}
if (!isCommanded(actor) && hasCommandPackage)
stats.getAiSequence().erase(it);
return false;
});
}
void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka)
@ -1401,9 +1398,6 @@ namespace MWMechanics
// AI processing is only done within given distance to the player.
bool inProcessingRange = distSqr <= mActorsProcessingRange*mActorsProcessingRange;
if (isPlayer)
ctrl->setAttackingOrSpell(world->getPlayer().getAttackingOrSpell());
// If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player.
if (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead()
|| !iter->first.getClass().getCreatureStats(iter->first).getAiSequence().isInCombat()
@ -1524,25 +1518,31 @@ namespace MWMechanics
CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
float speedFactor = isPlayer ? 1.f : mov.mSpeedFactor;
osg::Vec2f movement = osg::Vec2f(mov.mPosition[0], mov.mPosition[1]) * speedFactor;
float rotationX = mov.mRotation[0];
float rotationZ = mov.mRotation[2];
bool jump = mov.mPosition[2] == 1;
bool runFlag = stats.getMovementFlag(MWMechanics::CreatureStats::Flag_Run);
bool attackingOrSpell = stats.getAttackingOrSpell();
if (luaControls->mChanged)
{
mov.mPosition[0] = luaControls->mSideMovement;
mov.mPosition[1] = luaControls->mMovement;
mov.mPosition[2] = luaControls->mJump ? 1 : 0;
mov.mRotation[0] = luaControls->mPitchChange;
mov.mRotation[1] = 0;
mov.mRotation[2] = luaControls->mTurn;
mov.mRotation[2] = luaControls->mYawChange;
mov.mSpeedFactor = osg::Vec2(luaControls->mMovement, luaControls->mSideMovement).length();
stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, luaControls->mRun);
stats.setAttackingOrSpell(luaControls->mUse == 1);
luaControls->mChanged = false;
}
luaControls->mSideMovement = movement.x();
luaControls->mMovement = movement.y();
luaControls->mTurn = rotationZ;
luaControls->mPitchChange = rotationX;
luaControls->mYawChange = rotationZ;
luaControls->mJump = jump;
luaControls->mRun = runFlag;
luaControls->mUse = attackingOrSpell ? luaControls->mUse | 1 : luaControls->mUse & ~1;
}
}
}
@ -2010,7 +2010,7 @@ namespace MWMechanics
std::list<MWWorld::Ptr> Actors::getActorsFollowing(const MWWorld::Ptr& actor)
{
std::list<MWWorld::Ptr> list;
forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr<AiPackage>& package)
forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::shared_ptr<AiPackage>& package)
{
if (package->followTargetThroughDoors() && package->getTarget() == actor)
list.push_back(iter.first);
@ -2061,7 +2061,7 @@ namespace MWMechanics
std::list<int> Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor)
{
std::list<int> list;
forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr<AiPackage>& package)
forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::shared_ptr<AiPackage>& package)
{
if (package->followTargetThroughDoors() && package->getTarget() == actor)
{
@ -2078,7 +2078,7 @@ namespace MWMechanics
std::map<int, MWWorld::Ptr> Actors::getActorsFollowingByIndex(const MWWorld::Ptr &actor)
{
std::map<int, MWWorld::Ptr> map;
forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr<AiPackage>& package)
forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::shared_ptr<AiPackage>& package)
{
if (package->followTargetThroughDoors() && package->getTarget() == actor)
{

@ -141,7 +141,7 @@ namespace MWMechanics
if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage);
if (storage.mRotateMove)
return false;
storage.updateAttack(characterController);
storage.updateAttack(actor, characterController);
}
else
{
@ -168,7 +168,7 @@ namespace MWMechanics
if (!canFight(actor, target))
{
storage.stopAttack();
characterController.setAttackingOrSpell(false);
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false);
storage.mActionCooldown = 0.f;
// Continue combat if target is player or player follower/escorter and an attack has been attempted
const std::list<MWWorld::Ptr>& playerFollowersAndEscorters = MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(MWMechanics::getPlayer());
@ -299,7 +299,7 @@ namespace MWMechanics
{
storage.mUseCustomDestination = false;
storage.stopAttack();
characterController.setAttackingOrSpell(false);
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false);
currentAction.reset(new ActionFlee());
actionCooldown = currentAction->getActionCooldown();
storage.startFleeing();
@ -575,7 +575,7 @@ namespace MWMechanics
if (mAttackCooldown <= 0)
{
mAttack = true; // attack starts just now
characterController.setAttackingOrSpell(true);
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(true);
if (!distantCombat)
characterController.setAIAttackType(chooseBestAttack(weapon));
@ -603,13 +603,13 @@ namespace MWMechanics
}
}
void AiCombatStorage::updateAttack(CharacterController& characterController)
void AiCombatStorage::updateAttack(const MWWorld::Ptr& actor, CharacterController& characterController)
{
if (mAttack && (characterController.getAttackStrength() >= mStrength || characterController.readyToPrepareAttack()))
{
mAttack = false;
}
characterController.setAttackingOrSpell(mAttack);
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack);
}
void AiCombatStorage::stopAttack()

@ -88,7 +88,7 @@ namespace MWMechanics
void stopCombatMove();
void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController,
const ESM::Weapon* weapon, bool distantCombat);
void updateAttack(CharacterController& characterController);
void updateAttack(const MWWorld::Ptr& actor, CharacterController& characterController);
void stopAttack();
void startFleeing();

@ -476,8 +476,7 @@ namespace MWMechanics
static const float fAIFleeHealthMult = gmst.find("fAIFleeHealthMult")->mValue.getFloat();
static const float fAIFleeFleeMult = gmst.find("fAIFleeFleeMult")->mValue.getFloat();
float healthPercentage = (stats.getHealth().getModified() == 0.0f)
? 1.0f : stats.getHealth().getCurrent() / stats.getHealth().getModified();
float healthPercentage = stats.getHealth().getRatio(false);
float rating = (1.0f - healthPercentage) * fAIFleeHealthMult + flee * fAIFleeFleeMult;
static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->mValue.getInteger();

@ -1,6 +1,7 @@
#include "aisequence.hpp"
#include <limits>
#include <algorithm>
#include <components/debug/debuglog.hpp>
#include <components/esm3/aisequence.hpp>
@ -29,6 +30,9 @@ void AiSequence::copy (const AiSequence& sequence)
// We need to keep an AiWander storage, if present - it has a state machine.
// Not sure about another temporary storages
sequence.mAiState.copy<AiWanderStorage>(mAiState);
mNumCombatPackages = sequence.mNumCombatPackages;
mNumPursuitPackages = sequence.mNumPursuitPackages;
}
AiSequence::AiSequence() : mDone (false), mLastAiPackage(AiPackageTypeId::None) {}
@ -58,6 +62,28 @@ AiSequence::~AiSequence()
clear();
}
void AiSequence::onPackageAdded(const AiPackage& package)
{
if (package.getTypeId() == AiPackageTypeId::Combat)
mNumCombatPackages++;
else if (package.getTypeId() == AiPackageTypeId::Pursue)
mNumPursuitPackages++;
assert(mNumCombatPackages >= 0);
assert(mNumPursuitPackages >= 0);
}
void AiSequence::onPackageRemoved(const AiPackage& package)
{
if (package.getTypeId() == AiPackageTypeId::Combat)
mNumCombatPackages--;
else if (package.getTypeId() == AiPackageTypeId::Pursue)
mNumPursuitPackages--;
assert(mNumCombatPackages >= 0);
assert(mNumPursuitPackages >= 0);
}
AiPackageTypeId AiSequence::getTypeId() const
{
if (mPackages.empty())
@ -87,42 +113,30 @@ bool AiSequence::getCombatTargets(std::vector<MWWorld::Ptr> &targetActors) const
return !targetActors.empty();
}
std::list<std::unique_ptr<AiPackage>>::const_iterator AiSequence::begin() const
AiPackages::iterator AiSequence::erase(AiPackages::iterator package)
{
return mPackages.begin();
}
// Not sure if manually terminated packages should trigger mDone, probably not?
auto& ptr = *package;
onPackageRemoved(*ptr);
std::list<std::unique_ptr<AiPackage>>::const_iterator AiSequence::end() const
{
return mPackages.end();
return mPackages.erase(package);
}
void AiSequence::erase(std::list<std::unique_ptr<AiPackage>>::const_iterator package)
bool AiSequence::isInCombat() const
{
// Not sure if manually terminated packages should trigger mDone, probably not?
for(auto it = mPackages.begin(); it != mPackages.end(); ++it)
{
if (package == it)
{
mPackages.erase(it);
return;
}
}
throw std::runtime_error("can't find package to erase");
return mNumCombatPackages > 0;
}
bool AiSequence::isInCombat() const
bool AiSequence::isInPursuit() const
{
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
{
if ((*it)->getTypeId() == AiPackageTypeId::Combat)
return true;
}
return false;
return mNumPursuitPackages > 0;
}
bool AiSequence::isEngagedWithActor() const
{
if (!isInCombat())
return false;
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
{
if ((*it)->getTypeId() == AiPackageTypeId::Combat)
@ -137,16 +151,18 @@ bool AiSequence::isEngagedWithActor() const
bool AiSequence::hasPackage(AiPackageTypeId typeId) const
{
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
auto it = std::find_if(mPackages.begin(), mPackages.end(), [typeId](const auto& package)
{
if ((*it)->getTypeId() == typeId)
return true;
}
return false;
return package->getTypeId() == typeId;
});
return it != mPackages.end();
}
bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const
{
if (!isInCombat())
return false;
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
{
if ((*it)->getTypeId() == AiPackageTypeId::Combat)
@ -158,27 +174,31 @@ bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const
return false;
}
// TODO: use std::list::remove_if for all these methods when we switch to C++20
void AiSequence::stopCombat()
void AiSequence::removePackagesById(AiPackageTypeId id)
{
for(auto it = mPackages.begin(); it != mPackages.end(); )
for (auto it = mPackages.begin(); it != mPackages.end(); )
{
if ((*it)->getTypeId() == AiPackageTypeId::Combat)
if ((*it)->getTypeId() == id)
{
it = mPackages.erase(it);
it = erase(it);
}
else
++it;
}
}
void AiSequence::stopCombat()
{
removePackagesById(AiPackageTypeId::Combat);
}
void AiSequence::stopCombat(const std::vector<MWWorld::Ptr>& targets)
{
for(auto it = mPackages.begin(); it != mPackages.end(); )
{
if ((*it)->getTypeId() == AiPackageTypeId::Combat && std::find(targets.begin(), targets.end(), (*it)->getTarget()) != targets.end())
{
it = mPackages.erase(it);
it = erase(it);
}
else
++it;
@ -187,15 +207,7 @@ void AiSequence::stopCombat(const std::vector<MWWorld::Ptr>& targets)
void AiSequence::stopPursuit()
{
for(auto it = mPackages.begin(); it != mPackages.end(); )
{
if ((*it)->getTypeId() == AiPackageTypeId::Pursue)
{
it = mPackages.erase(it);
}
else
++it;
}
removePackagesById(AiPackageTypeId::Pursue);
}
bool AiSequence::isPackageDone() const
@ -214,112 +226,117 @@ namespace
void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange)
{
if(actor != getPlayer())
if (actor == getPlayer())
{
if (mPackages.empty())
{
mLastAiPackage = AiPackageTypeId::None;
return;
}
// Players don't use this.
return;
}
auto packageIt = mPackages.begin();
MWMechanics::AiPackage* package = packageIt->get();
if (!package->alwaysActive() && outOfRange)
return;
if (mPackages.empty())
{
mLastAiPackage = AiPackageTypeId::None;
return;
}
auto packageTypeId = package->getTypeId();
// workaround ai packages not being handled as in the vanilla engine
if (isActualAiPackage(packageTypeId))
mLastAiPackage = packageTypeId;
// if active package is combat one, choose nearest target
if (packageTypeId == AiPackageTypeId::Combat)
{
auto itActualCombat = mPackages.end();
auto packageIt = mPackages.begin();
MWMechanics::AiPackage* package = packageIt->get();
if (!package->alwaysActive() && outOfRange)
return;
auto packageTypeId = package->getTypeId();
// workaround ai packages not being handled as in the vanilla engine
if (isActualAiPackage(packageTypeId))
mLastAiPackage = packageTypeId;
// if active package is combat one, choose nearest target
if (packageTypeId == AiPackageTypeId::Combat)
{
auto itActualCombat = mPackages.end();
float nearestDist = std::numeric_limits<float>::max();
osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3();
float nearestDist = std::numeric_limits<float>::max();
osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3();
float bestRating = 0.f;
float bestRating = 0.f;
for (auto it = mPackages.begin(); it != mPackages.end();)
{
if ((*it)->getTypeId() != AiPackageTypeId::Combat) break;
for (auto it = mPackages.begin(); it != mPackages.end();)
{
if ((*it)->getTypeId() != AiPackageTypeId::Combat) break;
MWWorld::Ptr target = (*it)->getTarget();
MWWorld::Ptr target = (*it)->getTarget();
// target disappeared (e.g. summoned creatures)
if (target.isEmpty())
{
it = mPackages.erase(it);
}
else
{
float rating = MWMechanics::getBestActionRating(actor, target);
// target disappeared (e.g. summoned creatures)
if (target.isEmpty())
{
it = erase(it);
}
else
{
float rating = MWMechanics::getBestActionRating(actor, target);
const ESM::Position &targetPos = target.getRefData().getPosition();
const ESM::Position &targetPos = target.getRefData().getPosition();
float distTo = (targetPos.asVec3() - vActorPos).length2();
float distTo = (targetPos.asVec3() - vActorPos).length2();
// Small threshold for changing target
if (it == mPackages.begin())
distTo = std::max(0.f, distTo - 2500.f);
// Small threshold for changing target
if (it == mPackages.begin())
distTo = std::max(0.f, distTo - 2500.f);
// if a target has higher priority than current target or has same priority but closer
if (rating > bestRating || ((distTo < nearestDist) && rating == bestRating))
{
nearestDist = distTo;
itActualCombat = it;
bestRating = rating;
}
++it;
// if a target has higher priority than current target or has same priority but closer
if (rating > bestRating || ((distTo < nearestDist) && rating == bestRating))
{
nearestDist = distTo;
itActualCombat = it;
bestRating = rating;
}
++it;
}
}
assert(!mPackages.empty());
assert(!mPackages.empty());
if (nearestDist < std::numeric_limits<float>::max() && mPackages.begin() != itActualCombat)
{
assert(itActualCombat != mPackages.end());
// move combat package with nearest target to the front
mPackages.splice(mPackages.begin(), mPackages, itActualCombat);
}
packageIt = mPackages.begin();
package = packageIt->get();
packageTypeId = package->getTypeId();
if (nearestDist < std::numeric_limits<float>::max() && mPackages.begin() != itActualCombat)
{
assert(itActualCombat != mPackages.end());
// move combat package with nearest target to the front
std::rotate(mPackages.begin(), itActualCombat, std::next(itActualCombat));
}
try
packageIt = mPackages.begin();
package = packageIt->get();
packageTypeId = package->getTypeId();
}
try
{
if (package->execute(actor, characterController, mAiState, duration))
{
if (package->execute(actor, characterController, mAiState, duration))
{
// Put repeating noncombat AI packages on the end of the stack so they can be used again
if (isActualAiPackage(packageTypeId) && package->getRepeat())
{
package->reset();
mPackages.push_back(package->clone());
}
// To account for the rare case where AiPackage::execute() queued another AI package
// (e.g. AiPursue executing a dialogue script that uses startCombat)
mPackages.erase(packageIt);
if (isActualAiPackage(packageTypeId))
mDone = true;
}
else
// Put repeating noncombat AI packages on the end of the stack so they can be used again
if (isActualAiPackage(packageTypeId) && package->getRepeat())
{
mDone = false;
package->reset();
mPackages.push_back(package->clone());
}
// To account for the rare case where AiPackage::execute() queued another AI package
// (e.g. AiPursue executing a dialogue script that uses startCombat)
erase(packageIt);
if (isActualAiPackage(packageTypeId))
mDone = true;
}
catch (std::exception& e)
else
{
Log(Debug::Error) << "Error during AiSequence::execute: " << e.what();
mDone = false;
}
}
catch (std::exception& e)
{
Log(Debug::Error) << "Error during AiSequence::execute: " << e.what();
}
}
void AiSequence::clear()
{
mPackages.clear();
mNumCombatPackages = 0;
mNumPursuitPackages = 0;
}
void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther)
@ -363,7 +380,7 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
{
if((*it)->canCancel())
{
it = mPackages.erase(it);
it = erase(it);
}
else
++it;
@ -383,11 +400,13 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
if((*it)->getPriority() <= package.getPriority())
{
onPackageAdded(package);
mPackages.insert(it, package.clone());
return;
}
}
onPackageAdded(package);
mPackages.push_back(package.clone());
// Make sure that temporary storage is empty
@ -445,6 +464,8 @@ void AiSequence::fill(const ESM::AIPackageList &list)
ESM::AITarget data = esmPackage.mTarget;
package = std::make_unique<MWMechanics::AiFollow>(data.mId.toStringView(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0);
}
onPackageAdded(*package);
mPackages.push_back(std::move(package));
}
}
@ -514,6 +535,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
if (!package.get())
continue;
onPackageAdded(*package);
mPackages.push_back(std::move(package));
}

@ -1,8 +1,9 @@
#ifndef GAME_MWMECHANICS_AISEQUENCE_H
#define GAME_MWMECHANICS_AISEQUENCE_H
#include <list>
#include <memory>
#include <vector>
#include <algorithm>
#include "aistate.hpp"
#include "aipackagetypeid.hpp"
@ -22,8 +23,6 @@ namespace ESM
}
}
namespace MWMechanics
{
class AiPackage;
@ -33,15 +32,20 @@ namespace MWMechanics
struct AiTemporaryBase;
typedef DerivedClassStorage<AiTemporaryBase> AiState;
using AiPackages = std::vector<std::shared_ptr<AiPackage>>;
/// \brief Sequence of AI-packages for a single actor
/** The top-most AI package is run each frame. When completed, it is removed from the stack. **/
class AiSequence
{
///AiPackages to run though
std::list<std::unique_ptr<AiPackage>> mPackages;
AiPackages mPackages;
///Finished with top AIPackage, set for one frame
bool mDone;
bool mDone{};
int mNumCombatPackages{};
int mNumPursuitPackages{};
///Copy AiSequence
void copy (const AiSequence& sequence);
@ -50,6 +54,11 @@ namespace MWMechanics
AiPackageTypeId mLastAiPackage;
AiState mAiState;
void onPackageAdded(const AiPackage& package);
void onPackageRemoved(const AiPackage& package);
AiPackages::iterator erase(AiPackages::iterator package);
public:
///Default constructor
AiSequence();
@ -63,10 +72,31 @@ namespace MWMechanics
virtual ~AiSequence();
/// Iterator may be invalidated by any function calls other than begin() or end().
std::list<std::unique_ptr<AiPackage>>::const_iterator begin() const;
std::list<std::unique_ptr<AiPackage>>::const_iterator end() const;
void erase(std::list<std::unique_ptr<AiPackage>>::const_iterator package);
AiPackages::const_iterator begin() const { return mPackages.begin(); }
AiPackages::const_iterator end() const { return mPackages.end(); }
/// Removes all packages controlled by the predicate.
template<typename F>
void erasePackagesIf(const F&& pred)
{
mPackages.erase(std::remove_if(mPackages.begin(), mPackages.end(), [&](auto& entry)
{
const bool doRemove = pred(entry);
if (doRemove)
onPackageRemoved(*entry);
return doRemove;
}), mPackages.end());
}
/// Removes a single package controlled by the predicate.
template<typename F>
void erasePackageIf(const F&& pred)
{
auto it = std::find_if(mPackages.begin(), mPackages.end(), pred);
if (it == mPackages.end())
return;
erase(it);
}
/// Returns currently executing AiPackage type
/** \see enum class AiPackageTypeId **/
@ -87,6 +117,12 @@ namespace MWMechanics
/// Is there any combat package?
bool isInCombat () const;
/// Is there any pursuit package.
bool isInPursuit() const;
/// Removes all packages using the specified id.
void removePackagesById(AiPackageTypeId id);
/// Are we in combat with any other actor, who's also engaging us?
bool isEngagedWithActor () const;

@ -55,9 +55,9 @@ namespace
std::string getBestAttack (const ESM::Weapon* weapon)
{
int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2;
int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2;
int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2;
int slash = weapon->mData.mSlash[0] + weapon->mData.mSlash[1];
int chop = weapon->mData.mChop[0] + weapon->mData.mChop[1];
int thrust = weapon->mData.mThrust[0] + weapon->mData.mThrust[1];
if (slash == chop && slash == thrust)
return "slash";
else if (thrust >= chop && thrust >= slash)
@ -435,6 +435,8 @@ std::string CharacterController::getWeaponAnimation(int weaponType) const
else if (isRealWeapon)
weaponGroup = oneHandFallback;
}
else if (weaponType == ESM::Weapon::HandToHand && !mPtr.getClass().isBipedal(mPtr))
return "attack1";
return weaponGroup;
}
@ -707,9 +709,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
if (mPtr.getClass().isActor())
refreshHitRecoilAnims(idle);
std::string weap;
if (mPtr.getClass().hasInventoryStore(mPtr))
weap = getWeaponType(mWeaponType)->mShortGroup;
std::string weap = getWeaponType(mWeaponType)->mShortGroup;
refreshJumpAnims(weap, jump, idle, force);
refreshMovementAnims(weap, movement, idle, force);
@ -854,7 +854,6 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
, mSecondsOfSwimming(0)
, mSecondsOfRunning(0)
, mTurnAnimationThreshold(0)
, mAttackingOrSpell(false)
, mCastingManualSpell(false)
, mTimeUntilWake(0.f)
, mIsMovingBackward(false)
@ -1120,97 +1119,6 @@ void CharacterController::updateIdleStormState(bool inwater)
}
}
bool CharacterController::updateCreatureState()
{
const MWWorld::Class &cls = mPtr.getClass();
CreatureStats &stats = cls.getCreatureStats(mPtr);
int weapType = ESM::Weapon::None;
if(stats.getDrawState() == DrawState_Weapon)
weapType = ESM::Weapon::HandToHand;
else if (stats.getDrawState() == DrawState_Spell)
weapType = ESM::Weapon::Spell;
if (weapType != mWeaponType)
{
mWeaponType = weapType;
if (mAnimation->isPlaying(mCurrentWeapon))
mAnimation->disable(mCurrentWeapon);
}
if(mAttackingOrSpell)
{
if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None)
{
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
std::string startKey = "start";
std::string stopKey = "stop";
if (weapType == ESM::Weapon::Spell)
{
const std::string spellid = stats.getSpells().getSelectedSpell();
bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr);
if (!spellid.empty() && canCast)
{
MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell);
cast.playSpellCastingEffects(spellid, false);
if (!mAnimation->hasAnimation("spellcast"))
{
MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately
mCastingManualSpell = false;
}
else
{
const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellid);
const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0);
switch(effectentry.mRange)
{
case 0: mAttackType = "self"; break;
case 1: mAttackType = "touch"; break;
case 2: mAttackType = "target"; break;
}
startKey = mAttackType + " " + startKey;
stopKey = mAttackType + " " + stopKey;
mCurrentWeapon = "spellcast";
}
}
else
mCurrentWeapon = "";
}
if (weapType != ESM::Weapon::Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation
{
mCurrentWeapon = chooseRandomAttackAnimation();
}
if (!mCurrentWeapon.empty())
{
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::BlendMask_All, true,
1, startKey, stopKey,
0.0f, 0);
mUpperBodyState = UpperCharState_StartToMinAttack;
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability());
if (weapType == ESM::Weapon::HandToHand)
playSwishSound(0.0f);
}
}
mAttackingOrSpell = false;
}
bool animPlaying = mAnimation->getInfo(mCurrentWeapon);
if (!animPlaying)
mUpperBodyState = UpperCharState_Nothing;
return false;
}
bool CharacterController::updateCarriedLeftVisible(const int weaptype) const
{
// Shields/torches shouldn't be visible during any operation involving two hands
@ -1219,7 +1127,7 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const
return mAnimation->updateCarriedLeftVisible(weaptype);
}
bool CharacterController::updateWeaponState(CharacterState& idle)
bool CharacterController::updateState(CharacterState& idle)
{
const MWWorld::Class &cls = mPtr.getClass();
CreatureStats &stats = cls.getCreatureStats(mPtr);
@ -1277,11 +1185,10 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
{
forcestateupdate = true;
mUpperBodyState = UpperCharState_WeapEquiped;
mAttackingOrSpell = false;
setAttackingOrSpell(false);
mAnimation->disable(mCurrentWeapon);
mAnimation->showWeapons(true);
if (mPtr == getPlayer())
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
stats.setAttackingOrSpell(false);
}
if(!isKnockedOut() && !isKnockedDown() && !isRecovery())
@ -1388,7 +1295,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
}
mWeaponType = weaptype;
mCurrentWeapon = getWeaponAnimation(mWeaponType);
mCurrentWeapon = weapgroup;
if(!upSoundId.empty() && !isStillWeapon)
{
@ -1456,17 +1363,15 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
float complete;
bool animPlaying;
ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass;
if(mAttackingOrSpell)
if(getAttackingOrSpell())
{
MWWorld::Ptr player = getPlayer();
bool resetIdle = ammunition;
if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block))
{
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
mAttackStrength = 0;
// Randomize attacks for non-bipedal creatures with Weapon flag
// Randomize attacks for non-bipedal creatures
if (mPtr.getClass().getType() == ESM::Creature::sRecordId &&
!mPtr.getClass().isBipedal(mPtr) &&
(!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon)))
@ -1478,11 +1383,9 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
{
// Unset casting flag, otherwise pressing the mouse button down would
// continue casting every frame if there is no animation
mAttackingOrSpell = false;
if (mPtr == player)
setAttackingOrSpell(false);
if (mPtr == getPlayer())
{
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
// For the player, set the spell we want to cast
// This has to be done at the start of the casting animation,
// *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation)
@ -1654,7 +1557,14 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
weapSpeed, startKey, stopKey,
0.0f, 0);
if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f)
{
mUpperBodyState = UpperCharState_StartToMinAttack;
if (isRandomAttackAnimation(mCurrentWeapon))
{
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability());
playSwishSound(0.0f);
}
}
}
}
@ -1791,7 +1701,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
// Note: if the "min attack"->"max attack" is a stub, "play" it anyway. Attack strength will be random.
float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack");
float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack");
if (mAttackingOrSpell || minAttackTime == maxAttackTime)
if (getAttackingOrSpell() || minAttackTime == maxAttackTime)
{
start = mAttackType+" min attack";
stop = mAttackType+" max attack";
@ -2350,11 +2260,7 @@ void CharacterController::update(float duration)
if (!mSkipAnim)
{
// bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used.
if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr))
forcestateupdate = updateWeaponState(idlestate) || forcestateupdate;
else
forcestateupdate = updateCreatureState() || forcestateupdate;
forcestateupdate = updateState(idlestate) || forcestateupdate;
refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate);
updateIdleStormState(inwater);
@ -2647,7 +2553,7 @@ void CharacterController::forceStateUpdate()
// Make sure we canceled the current attack or spellcasting,
// because we disabled attack animations anyway.
mCastingManualSpell = false;
mAttackingOrSpell = false;
setAttackingOrSpell(false);
if (mUpperBodyState != UpperCharState_Nothing)
mUpperBodyState = UpperCharState_WeapEquiped;
@ -2845,12 +2751,12 @@ bool CharacterController::isRunning() const
void CharacterController::setAttackingOrSpell(bool attackingOrSpell)
{
mAttackingOrSpell = attackingOrSpell;
mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(attackingOrSpell);
}
void CharacterController::castSpell(const std::string& spellId, bool manualSpell)
{
mAttackingOrSpell = true;
setAttackingOrSpell(true);
mCastingManualSpell = manualSpell;
ActionSpell action = ActionSpell(spellId);
action.prepare(mPtr);
@ -2883,10 +2789,7 @@ bool CharacterController::readyToStartAttack() const
if (mHitState != CharState_None && mHitState != CharState_Block)
return false;
if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr))
return mUpperBodyState == UpperCharState_WeapEquiped;
else
return mUpperBodyState == UpperCharState_Nothing;
return mUpperBodyState == UpperCharState_WeapEquiped;
}
float CharacterController::getAttackStrength() const
@ -2894,6 +2797,11 @@ float CharacterController::getAttackStrength() const
return mAttackStrength;
}
bool CharacterController::getAttackingOrSpell()
{
return mPtr.getClass().getCreatureStats(mPtr).getAttackingOrSpell();
}
void CharacterController::setActive(int active)
{
mAnimation->setActive(active);

@ -188,7 +188,6 @@ class CharacterController : public MWRender::Animation::TextKeyListener
std::string mAttackType; // slash, chop or thrust
bool mAttackingOrSpell;
bool mCastingManualSpell;
float mTimeUntilWake;
@ -206,8 +205,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
void clearAnimQueue(bool clearPersistAnims = false);
bool updateWeaponState(CharacterState& idle);
bool updateCreatureState();
bool updateState(CharacterState& idle);
void updateIdleStormState(bool inwater);
std::string chooseRandomAttackAnimation() const;
@ -235,6 +233,10 @@ class CharacterController : public MWRender::Animation::TextKeyListener
std::string getWeaponAnimation(int weaponType) const;
bool getAttackingOrSpell();
void setAttackingOrSpell(bool attackingOrSpell);
public:
CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim);
virtual ~CharacterController();
@ -285,7 +287,6 @@ public:
bool isAttackingOrSpell() const;
void setVisibility(float visibility);
void setAttackingOrSpell(bool attackingOrSpell);
void castSpell(const std::string& spellId, bool manualSpell=false);
void setAIAttackType(const std::string& attackType);
static void setAttackTypeRandomly(std::string& attackType);

@ -24,9 +24,8 @@ namespace MWMechanics
mHitRecovery(false), mBlock(false), mMovementFlags(0),
mFallHeight(0), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1),
mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0)
, mAttackingOrSpell(false)
{
for (int i=0; i<4; ++i)
mAiSettings[i] = 0;
}
const AiSequence& CreatureStats::getAiSequence() const
@ -157,9 +156,8 @@ namespace MWMechanics
float agility = getAttribute(ESM::Attribute::Agility).getModified();
float endurance = getAttribute(ESM::Attribute::Endurance).getModified();
DynamicStat<float> fatigue = getFatigue();
float diff = (strength+willpower+agility+endurance) - fatigue.getBase();
float currentToBaseRatio = fatigue.getBase() > 0 ? (fatigue.getCurrent() / fatigue.getBase()) : 0;
fatigue.setModified(fatigue.getModified() + diff, 0);
fatigue.setBase(std::max(0.f, strength + willpower + agility + endurance));
fatigue.setCurrent(fatigue.getBase() * currentToBaseRatio, false, true);
setFatigue(fatigue);
}
@ -195,8 +193,6 @@ namespace MWMechanics
mDead = true;
mDynamic[index].setModifier(0);
mDynamic[index].setCurrentModifier(0);
mDynamic[index].setCurrent(0);
}
}
@ -280,10 +276,7 @@ namespace MWMechanics
{
if (mDead)
{
if (mDynamic[0].getModified() < 1)
mDynamic[0].setModified(1, 0);
mDynamic[0].setCurrent(mDynamic[0].getModified());
mDynamic[0].setCurrent(mDynamic[0].getBase());
mDead = false;
mDeathAnimationFinished = false;
}
@ -414,9 +407,8 @@ namespace MWMechanics
double magickaFactor = base + mMagicEffects.get(EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1;
DynamicStat<float> magicka = getMagicka();
float diff = (static_cast<int>(magickaFactor*intelligence)) - magicka.getBase();
float currentToBaseRatio = magicka.getBase() > 0 ? magicka.getCurrent() / magicka.getBase() : 0;
magicka.setModified(magicka.getModified() + diff, 0);
magicka.setBase(magickaFactor * intelligence);
magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true);
setMagicka(magicka);
}

@ -92,6 +92,7 @@ namespace MWMechanics
protected:
int mLevel;
bool mAttackingOrSpell;
public:
CreatureStats();
@ -124,7 +125,7 @@ namespace MWMechanics
const MagicEffects & getMagicEffects() const;
bool getAttackingOrSpell() const;
bool getAttackingOrSpell() const { return mAttackingOrSpell; }
int getLevel() const;
@ -149,7 +150,7 @@ namespace MWMechanics
/// Set Modifier for each magic effect according to \a effects. Does not touch Base values.
void modifyMagicEffects(const MagicEffects &effects);
void setAttackingOrSpell(bool attackingOrSpell);
void setAttackingOrSpell(bool attackingOrSpell) { mAttackingOrSpell = attackingOrSpell; }
void setLevel(int level);

@ -1318,7 +1318,7 @@ namespace MWMechanics
// once the bounty has been paid.
actor.getClass().getNpcStats(actor).setCrimeId(id);
if (!actor.getClass().getCreatureStats(actor).getAiSequence().hasPackage(AiPackageTypeId::Pursue))
if (!actor.getClass().getCreatureStats(actor).getAiSequence().isInPursuit())
{
actor.getClass().getCreatureStats(actor).getAiSequence().stack(AiPursue(player), actor);
}
@ -1396,7 +1396,7 @@ namespace MWMechanics
{
// Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back.
// Note: accidental or collateral damage attacks are ignored.
if (!victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue))
if (!victim.getClass().getCreatureStats(victim).getAiSequence().isInPursuit())
startCombat(victim, player);
// Set the crime ID, which we will use to calm down participants
@ -1442,7 +1442,7 @@ namespace MWMechanics
{
// Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back.
// Note: accidental or collateral damage attacks are ignored.
if (!target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackageTypeId::Pursue))
if (!target.getClass().getCreatureStats(target).getAiSequence().isInPursuit())
{
// If an actor has OnPCHitMe declared in his script, his Fight = 0 and the attacker is player,
// he will attack the player only if we will force him (e.g. via StartCombat console command)
@ -1450,7 +1450,7 @@ namespace MWMechanics
std::string script = target.getClass().getScript(target);
if (!script.empty() && target.getRefData().getLocals().hasVar(script, "onpchitme") && attacker == player)
{
int fight = std::max(0, target.getClass().getCreatureStats(target).getAiSetting(CreatureStats::AI_Fight).getModified());
int fight = target.getClass().getCreatureStats(target).getAiSetting(CreatureStats::AI_Fight).getModified();
peaceful = (fight == 0);
}
@ -1467,7 +1467,7 @@ namespace MWMechanics
const MWMechanics::AiSequence& seq = target.getClass().getCreatureStats(target).getAiSequence();
return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker)
&& !isAggressive(target, attacker) && !seq.isEngagedWithActor()
&& !target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackageTypeId::Pursue);
&& !target.getClass().getCreatureStats(target).getAiSequence().isInPursuit();
}
void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker)

@ -64,8 +64,7 @@ namespace
auto& creatureStats = target.getClass().getCreatureStats(target);
auto stat = creatureStats.getDynamic(index);
float current = stat.getCurrent();
stat.setModified(stat.getModified() + magnitude, 0);
stat.setCurrentModified(stat.getCurrentModified() + magnitude);
stat.setBase(std::max(0.f, stat.getBase() + magnitude));
stat.setCurrent(current + magnitude);
creatureStats.setDynamic(index, stat);
}
@ -980,12 +979,10 @@ void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellPara
if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f)
{
auto& seq = target.getClass().getCreatureStats(target).getAiSequence();
auto it = std::find_if(seq.begin(), seq.end(), [&](const auto& package)
seq.erasePackageIf([&](const auto& package)
{
return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow && static_cast<const MWMechanics::AiFollow*>(package.get())->isCommanded();
});
if(it != seq.end())
seq.erase(it);
}
break;
case ESM::MagicEffect::ExtraSpell:

@ -5,174 +5,41 @@
namespace MWMechanics
{
template<typename T>
Stat<T>::Stat() : mBase (0), mModified (0), mCurrentModified (0) {}
Stat<T>::Stat() : mBase (0), mModifier (0) {}
template<typename T>
Stat<T>::Stat(T base) : mBase (base), mModified (base), mCurrentModified (base) {}
template<typename T>
Stat<T>::Stat(T base, T modified) : mBase (base), mModified (modified), mCurrentModified (modified) {}
template<typename T>
const T& Stat<T>::getBase() const
{
return mBase;
}
Stat<T>::Stat(T base, T modified) : mBase (base), mModifier (modified) {}
template<typename T>
T Stat<T>::getModified(bool capped) const
{
if(!capped)
return mModified;
return std::max(static_cast<T>(0), mModified);
}
template<typename T>
T Stat<T>::getCurrentModified() const
{
return mCurrentModified;
}
template<typename T>
T Stat<T>::getModifier() const
{
return mModified-mBase;
}
template<typename T>
T Stat<T>::getCurrentModifier() const
{
return mCurrentModified - mModified;
}
template<typename T>
void Stat<T>::set (const T& value)
{
T diff = value - mBase;
mBase = mModified = value;
mCurrentModified += diff;
}
template<typename T>
void Stat<T>::setBase (const T& value)
{
T diff = value - mBase;
mBase = value;
mModified += diff;
mCurrentModified += diff;
}
template<typename T>
void Stat<T>::setModified (T value, const T& min, const T& max)
{
T diff = value - mModified;
if (mBase+diff<min)
{
value = min + (mModified - mBase);
diff = value - mModified;
}
else if (mBase+diff>max)
{
value = max + (mModified - mBase);
diff = value - mModified;
}
mModified = value;
mBase += diff;
mCurrentModified += diff;
}
template<typename T>
void Stat<T>::setCurrentModified(T value)
{
mCurrentModified = value;
}
template<typename T>
void Stat<T>::setModifier (const T& modifier)
{
mModified = mBase + modifier;
}
template<typename T>
void Stat<T>::setCurrentModifier(const T& modifier)
{
mCurrentModified = mModified + modifier;
if(capped)
return std::max({}, mModifier + mBase);
return mModifier + mBase;
}
template<typename T>
void Stat<T>::writeState (ESM::StatState<T>& state) const
{
state.mBase = mBase;
state.mMod = mCurrentModified;
state.mMod = mModifier;
}
template<typename T>
void Stat<T>::readState (const ESM::StatState<T>& state)
{
mBase = state.mBase;
mModified = state.mBase;
mCurrentModified = state.mMod;
mModifier = state.mMod;
}
template<typename T>
DynamicStat<T>::DynamicStat() : mStatic (0), mCurrent (0) {}
DynamicStat<T>::DynamicStat() : mStatic(0, 0), mCurrent(0) {}
template<typename T>
DynamicStat<T>::DynamicStat(T base) : mStatic (base), mCurrent (base) {}
DynamicStat<T>::DynamicStat(T base) : mStatic(base, 0), mCurrent(base) {}
template<typename T>
DynamicStat<T>::DynamicStat(T base, T modified, T current) : mStatic(base, modified), mCurrent (current) {}
template<typename T>
DynamicStat<T>::DynamicStat(const Stat<T> &stat, T current) : mStatic(stat), mCurrent (current) {}
template<typename T>
const T& DynamicStat<T>::getBase() const
{
return mStatic.getBase();
}
template<typename T>
T DynamicStat<T>::getModified() const
{
return mStatic.getModified();
}
template<typename T>
T DynamicStat<T>::getCurrentModified() const
{
return mStatic.getCurrentModified();
}
template<typename T>
const T& DynamicStat<T>::getCurrent() const
{
return mCurrent;
}
template<typename T>
void DynamicStat<T>::set (const T& value)
{
mStatic.set (value);
mCurrent = value;
}
template<typename T>
void DynamicStat<T>::setBase (const T& value)
{
mStatic.setBase (value);
if (mCurrent>getModified())
mCurrent = getModified();
}
template<typename T>
void DynamicStat<T>::setModified (T value, const T& min, const T& max)
{
mStatic.setModified (value, min, max);
if (mCurrent>getModified())
mCurrent = getModified();
}
template<typename T>
void DynamicStat<T>::setCurrentModified(T value)
{
mStatic.setCurrentModified(value);
}
template<typename T>
void DynamicStat<T>::setCurrent (const T& value, bool allowDecreaseBelowZero, bool allowIncreaseAboveModified)
{
@ -197,22 +64,18 @@ namespace MWMechanics
mCurrent = 0;
}
}
template<typename T>
void DynamicStat<T>::setModifier (const T& modifier, bool allowCurrentDecreaseBelowZero)
{
T diff = modifier - mStatic.getModifier();
mStatic.setModifier (modifier);
setCurrent (getCurrent()+diff, allowCurrentDecreaseBelowZero);
}
template<typename T>
void DynamicStat<T>::setCurrentModifier(const T& modifier, bool allowCurrentDecreaseBelowZero)
T DynamicStat<T>::getRatio(bool nanIsZero) const
{
T diff = modifier - mStatic.getCurrentModifier();
mStatic.setCurrentModifier(modifier);
// The (modifier > 0) check here allows increase over modified only if the modifier is positive (a fortify effect is active).
setCurrent (getCurrent() + diff, allowCurrentDecreaseBelowZero, (modifier > 0));
T modified = getModified();
if(modified == T{})
{
if(nanIsZero)
return modified;
return {1};
}
return getCurrent() / modified;
}
template<typename T>

@ -16,38 +16,22 @@ namespace MWMechanics
class Stat
{
T mBase;
T mModified;
T mCurrentModified;
T mModifier;
public:
typedef T Type;
Stat();
Stat(T base);
Stat(T base, T modified);
const T& getBase() const;
const T& getBase() const { return mBase; };
T getModified(bool capped = true) const;
T getCurrentModified() const;
T getModifier() const;
T getCurrentModifier() const;
T getModifier() const { return mModifier; };
/// Set base and modified to \a value.
void set (const T& value);
void setBase(const T& value) { mBase = value; };
/// Set base and adjust modified accordingly.
void setBase (const T& value);
/// Set modified value and adjust base accordingly.
void setModified (T value, const T& min, const T& max = std::numeric_limits<T>::max());
/// Set "current modified," used for drain and fortify. Unlike the regular modifier
/// this just adds and subtracts from the current value without changing the maximum.
void setCurrentModified(T value);
void setModifier (const T& modifier);
void setCurrentModifier (const T& modifier);
void setModifier(const T& modifier) { mModifier = modifier; };
void writeState (ESM::StatState<T>& state) const;
void readState (const ESM::StatState<T>& state);
@ -57,7 +41,7 @@ namespace MWMechanics
inline bool operator== (const Stat<T>& left, const Stat<T>& right)
{
return left.getBase()==right.getBase() &&
left.getModified()==right.getModified();
left.getModifier()==right.getModifier();
}
template<typename T>
@ -80,27 +64,18 @@ namespace MWMechanics
DynamicStat(T base, T modified, T current);
DynamicStat(const Stat<T> &stat, T current);
const T& getBase() const;
T getModified() const;
T getCurrentModified() const;
const T& getCurrent() const;
/// Set base, modified and current to \a value.
void set (const T& value);
const T& getBase() const { return mStatic.getBase(); };
T getModified(bool capped = true) const { return mStatic.getModified(capped); };
const T& getCurrent() const { return mCurrent; };
T getRatio(bool nanIsZero = true) const;
/// Set base and adjust modified accordingly.
void setBase (const T& value);
/// Set modified value and adjust base accordingly.
void setModified (T value, const T& min, const T& max = std::numeric_limits<T>::max());
/// Set "current modified," used for drain and fortify. Unlike the regular modifier
/// this just adds and subtracts from the current value without changing the maximum.
void setCurrentModified(T value);
/// Set base and adjust current accordingly.
void setBase(const T& value) { mStatic.setBase(value); };
void setCurrent (const T& value, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false);
void setModifier (const T& modifier, bool allowCurrentToDecreaseBelowZero=false);
void setCurrentModifier (const T& modifier, bool allowCurrentToDecreaseBelowZero = false);
T getModifier() const { return mStatic.getModifier(); }
void setModifier(T value) { mStatic.setModifier(value); }
void writeState (ESM::StatState<T>& state) const;
void readState (const ESM::StatState<T>& state);
@ -110,7 +85,7 @@ namespace MWMechanics
inline bool operator== (const DynamicStat<T>& left, const DynamicStat<T>& right)
{
return left.getBase()==right.getBase() &&
left.getModified()==right.getModified() &&
left.getModifier()==right.getModifier() &&
left.getCurrent()==right.getCurrent();
}

@ -1,3 +1,5 @@
#include <functional>
#include <BulletCollision/BroadphaseCollision/btDbvtBroadphase.h>
#include <BulletCollision/CollisionShapes/btCollisionShape.h>
@ -111,17 +113,49 @@ namespace
return ptr.getPosition() * interpolationFactor + ptr.getPreviousPosition() * (1.f - interpolationFactor);
}
using LockedActorSimulation = std::pair<
std::shared_ptr<MWPhysics::Actor>,
std::reference_wrapper<MWPhysics::ActorFrameData>
>;
using LockedProjectileSimulation = std::pair<
std::shared_ptr<MWPhysics::Projectile>,
std::reference_wrapper<MWPhysics::ProjectileFrameData>
>;
namespace Visitors
{
template <class Impl, template <class> class Lock>
struct WithLockedPtr
{
const Impl& mImpl;
std::shared_mutex& mCollisionWorldMutex;
const int mNumJobs;
template <class Ptr, class FrameData>
void operator()(MWPhysics::SimulationImpl<Ptr, FrameData>& sim) const
{
auto locked = sim.lock();
if (!locked.has_value())
return;
auto&& [ptr, frameData] = *std::move(locked);
// Locked shared_ptr has to be destructed after releasing mCollisionWorldMutex to avoid
// possible deadlock. Ptr destructor also acquires mCollisionWorldMutex.
const std::pair arg(std::move(ptr), frameData);
const Lock<std::shared_mutex> lock(mCollisionWorldMutex, mNumJobs);
mImpl(arg);
}
};
struct InitPosition
{
const btCollisionWorld* mCollisionWorld;
void operator()(MWPhysics::ActorSimulation& sim) const
{
auto& [actorPtr, frameData] = sim;
const auto actor = actorPtr.lock();
if (actor == nullptr)
auto locked = sim.lock();
if (!locked.has_value())
return;
auto& [actor, frameDataRef] = *locked;
auto& frameData = frameDataRef.get();
actor->applyOffsetChange();
frameData.mPosition = actor->getPosition();
if (frameData.mWaterCollision && frameData.mPosition.z() < frameData.mWaterlevel && actor->canMoveToWaterSurface(frameData.mWaterlevel, mCollisionWorld))
@ -138,7 +172,7 @@ namespace
frameData.mStuckFrames = actor->getStuckFrames();
frameData.mLastStuckPosition = actor->getLastStuckPosition();
}
void operator()(MWPhysics::ProjectileSimulation& sim) const
void operator()(MWPhysics::ProjectileSimulation& /*sim*/) const
{
}
};
@ -146,11 +180,11 @@ namespace
struct PreStep
{
btCollisionWorld* mCollisionWorld;
void operator()(MWPhysics::ActorSimulation& sim) const
void operator()(const LockedActorSimulation& sim) const
{
MWPhysics::MovementSolver::unstuck(sim.second, mCollisionWorld);
}
void operator()(MWPhysics::ProjectileSimulation& sim) const
void operator()(const LockedProjectileSimulation& /*sim*/) const
{
}
};
@ -158,12 +192,10 @@ namespace
struct UpdatePosition
{
btCollisionWorld* mCollisionWorld;
void operator()(MWPhysics::ActorSimulation& sim) const
void operator()(const LockedActorSimulation& sim) const
{
auto& [actorPtr, frameData] = sim;
const auto actor = actorPtr.lock();
if (actor == nullptr)
return;
auto& [actor, frameDataRef] = sim;
auto& frameData = frameDataRef.get();
if (actor->setPosition(frameData.mPosition))
{
frameData.mPosition = actor->getPosition(); // account for potential position change made by script
@ -171,12 +203,10 @@ namespace
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
}
}
void operator()(MWPhysics::ProjectileSimulation& sim) const
void operator()(const LockedProjectileSimulation& sim) const
{
auto& [projPtr, frameData] = sim;
const auto proj = projPtr.lock();
if (proj == nullptr)
return;
auto& [proj, frameDataRef] = sim;
auto& frameData = frameDataRef.get();
proj->setPosition(frameData.mPosition);
proj->updateCollisionObjectPosition();
mCollisionWorld->updateSingleAabb(proj->getCollisionObject());
@ -188,11 +218,11 @@ namespace
const float mPhysicsDt;
const btCollisionWorld* mCollisionWorld;
const MWPhysics::WorldFrameData& mWorldFrameData;
void operator()(MWPhysics::ActorSimulation& sim) const
void operator()(const LockedActorSimulation& sim) const
{
MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld, mWorldFrameData);
}
void operator()(MWPhysics::ProjectileSimulation& sim) const
void operator()(const LockedProjectileSimulation& sim) const
{
MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld);
}
@ -206,10 +236,11 @@ namespace
const MWPhysics::PhysicsTaskScheduler* scheduler;
void operator()(MWPhysics::ActorSimulation& sim) const
{
auto& [actorPtr, frameData] = sim;
const auto actor = actorPtr.lock();
if (actor == nullptr)
auto locked = sim.lock();
if (!locked.has_value())
return;
auto& [actor, frameDataRef] = *locked;
auto& frameData = frameDataRef.get();
auto ptr = actor->getPtr();
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
@ -241,10 +272,10 @@ namespace
}
void operator()(MWPhysics::ProjectileSimulation& sim) const
{
auto& [projPtr, frameData] = sim;
const auto proj = projPtr.lock();
if (proj == nullptr)
auto locked = sim.lock();
if (!locked.has_value())
return;
auto& [proj, frameData] = *locked;
proj->setSimulationPosition(::interpolateMovements(*proj, mTimeAccum, mPhysicsDt));
}
};
@ -612,12 +643,10 @@ namespace MWPhysics
void PhysicsTaskScheduler::updateActorsPositions()
{
const Visitors::UpdatePosition vis{mCollisionWorld};
for (auto& sim : mSimulations)
{
MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads);
const Visitors::UpdatePosition impl{mCollisionWorld};
const Visitors::WithLockedPtr<Visitors::UpdatePosition, MaybeExclusiveLock> vis{impl, mCollisionWorldMutex, mNumThreads};
for (Simulation& sim : mSimulations)
std::visit(vis, sim);
}
}
bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2)
@ -641,12 +670,10 @@ namespace MWPhysics
{
mPreStepBarrier->wait([this] { afterPreStep(); });
int job = 0;
const Visitors::Move vis{mPhysicsDt, mCollisionWorld, *mWorldFrameData};
const Visitors::Move impl{mPhysicsDt, mCollisionWorld, *mWorldFrameData};
const Visitors::WithLockedPtr<Visitors::Move, MaybeLock> vis{impl, mCollisionWorldMutex, mNumThreads};
while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
{
MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads);
std::visit(vis, mSimulations[job]);
}
mPostStepBarrier->wait([this] { afterPostStep(); });
}
@ -697,12 +724,10 @@ namespace MWPhysics
updateAabbs();
if (!mRemainingSteps)
return;
const Visitors::PreStep vis{mCollisionWorld};
const Visitors::PreStep impl{mCollisionWorld};
const Visitors::WithLockedPtr<Visitors::PreStep, MaybeExclusiveLock> vis{impl, mCollisionWorldMutex, mNumThreads};
for (auto& sim : mSimulations)
{
MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads);
std::visit(vis, sim);
}
}
void PhysicsTaskScheduler::afterPostStep()

@ -112,6 +112,7 @@ namespace MWPhysics
assert (mShapeInstance->mCollisionShape->isCompound());
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->mCollisionShape.get());
bool result = false;
for (const auto& [recIndex, shapeIndex] : mShapeInstance->mAnimatedShapes)
{
auto nodePathFound = mRecIndexToNodePath.find(recIndex);
@ -145,8 +146,11 @@ namespace MWPhysics
// Note: we can not apply scaling here for now since we treat scaled shapes
// as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now
if (!(transform == compound->getChildTransform(shapeIndex)))
{
compound->updateChildTransform(shapeIndex, transform);
result = true;
}
}
return true;
return result;
}
}

@ -490,7 +490,7 @@ namespace MWPhysics
mObjects.emplace(ptr.mRef, obj);
if (obj->isAnimated())
mAnimatedObjects.insert(obj.get());
mAnimatedObjects.emplace(obj.get(), false);
}
void PhysicsSystem::remove(const MWWorld::Ptr &ptr)
@ -739,13 +739,18 @@ namespace MWPhysics
void PhysicsSystem::stepSimulation(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{
for (Object* animatedObject : mAnimatedObjects)
for (auto& [animatedObject, changed] : mAnimatedObjects)
{
if (animatedObject->animateCollisionShapes())
{
auto obj = mObjects.find(animatedObject->getPtr().mRef);
assert(obj != mObjects.end());
mTaskScheduler->updateSingleAabb(obj->second);
changed = true;
}
else
{
changed = false;
}
}

@ -8,6 +8,8 @@
#include <unordered_map>
#include <algorithm>
#include <variant>
#include <optional>
#include <functional>
#include <osg/Quat>
#include <osg/BoundingBox>
@ -117,8 +119,26 @@ namespace MWPhysics
osg::Vec3f mStormDirection;
};
using ActorSimulation = std::pair<std::weak_ptr<Actor>, ActorFrameData>;
using ProjectileSimulation = std::pair<std::weak_ptr<Projectile>, ProjectileFrameData>;
template <class Ptr, class FrameData>
class SimulationImpl
{
public:
explicit SimulationImpl(const std::weak_ptr<Ptr>& ptr, FrameData&& data) : mPtr(ptr), mData(data) {}
std::optional<std::pair<std::shared_ptr<Ptr>, std::reference_wrapper<FrameData>>> lock()
{
if (auto locked = mPtr.lock())
return {{std::move(locked), std::ref(mData)}};
return std::nullopt;
}
private:
std::weak_ptr<Ptr> mPtr;
FrameData mData;
};
using ActorSimulation = SimulationImpl<Actor, ActorFrameData>;
using ProjectileSimulation = SimulationImpl<Projectile, ProjectileFrameData>;
using Simulation = std::variant<ActorSimulation, ProjectileSimulation>;
class PhysicsSystem : public RayCastingInterface
@ -280,7 +300,7 @@ namespace MWPhysics
using ObjectMap = std::unordered_map<const MWWorld::LiveCellRefBase*, std::shared_ptr<Object>>;
ObjectMap mObjects;
std::set<Object*> mAnimatedObjects; // stores pointers to elements in mObjects
std::map<Object*, bool> mAnimatedObjects; // stores pointers to elements in mObjects
ActorMap mActors;

@ -187,7 +187,7 @@ osg::ref_ptr<osg::Camera> LocalMap::createOrthographicCamera(float x, float y, f
camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT);
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT);
camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f));
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
camera->setRenderOrder(osg::Camera::PRE_RENDER);
camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static);
@ -460,8 +460,10 @@ void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell)
yOffset++;
mBounds.yMin() = fog->mBounds.mMinY - yOffset * mMapWorldSize;
}
mBounds.xMax() = std::max(mBounds.xMax(), fog->mBounds.mMaxX);
mBounds.yMax() = std::max(mBounds.yMax(), fog->mBounds.mMaxY);
if (fog->mBounds.mMaxX > mBounds.xMax())
mBounds.xMax() = fog->mBounds.mMaxX;
if (fog->mBounds.mMaxY > mBounds.yMax())
mBounds.yMax() = fog->mBounds.mMaxY;
if(xOffset != 0 || yOffset != 0)
Log(Debug::Warning) << "Warning: expanding fog by " << xOffset << ", " << yOffset;

@ -333,12 +333,12 @@ public:
osg::FrameBufferAttachment(postProcessor->getFirstPersonRBProxy()).attach(*state, GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, ext);
glClear(GL_DEPTH_BUFFER_BIT);
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// color accumulation pass
bin->drawImplementation(renderInfo, previous);
auto primaryFBO = postProcessor->getMsaaFbo() ? postProcessor->getMsaaFbo() : postProcessor->getFbo();
primaryFBO->getAttachment(osg::FrameBufferObject::BufferComponent::DEPTH_BUFFER).attach(*state, GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, ext);
primaryFBO->getAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER).attach(*state, GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, ext);
state->pushStateSet(mStateSet);
state->apply();
@ -349,7 +349,7 @@ public:
else
{
// fallback to standard depth clear when we are not rendering our main scene via an intermediate FBO
glClear(GL_DEPTH_BUFFER_BIT);
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
bin->drawImplementation(renderInfo, previous);
}
}
@ -957,7 +957,7 @@ void NpcAnimation::showWeapons(bool showWeapon)
removeIndividualPart(ESM::PRT_Weapon);
// If we remove/hide weapon from player, we should reset attack animation as well
if (mPtr == MWMechanics::getPlayer())
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(false);
}
updateHolsteredWeapon(!mShowWeapons);

@ -1,5 +1,7 @@
#include "postprocessor.hpp"
#include <SDL_opengl_glext.h>
#include <osg/Group>
#include <osg/Camera>
#include <osg/Callback>
@ -155,7 +157,7 @@ namespace MWRender
PostProcessor::PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode)
: mViewer(viewer)
, mRootNode(new osg::Group)
, mDepthFormat(GL_DEPTH_COMPONENT24)
, mDepthFormat(GL_DEPTH24_STENCIL8_EXT)
{
bool softParticles = Settings::Manager::getBool("soft particles", "Shaders");
@ -183,9 +185,9 @@ namespace MWRender
if (SceneUtil::AutoDepth::isReversed())
{
if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float"))
mDepthFormat = GL_DEPTH_COMPONENT32F;
mDepthFormat = GL_DEPTH32F_STENCIL8;
else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float"))
mDepthFormat = GL_DEPTH_COMPONENT32F_NV;
mDepthFormat = GL_DEPTH32F_STENCIL8_NV;
else
{
// TODO: Once we have post-processing implemented we want to skip this return and continue with setup.
@ -213,7 +215,7 @@ namespace MWRender
mViewer->getCamera()->addCullCallback(new CullCallback);
mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
mViewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, mSceneTex);
mViewer->getCamera()->attach(osg::Camera::DEPTH_BUFFER, mDepthTex);
mViewer->getCamera()->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, mDepthTex);
mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this));
mViewer->getCamera()->setUserData(this);
@ -236,7 +238,7 @@ namespace MWRender
mFbo = new osg::FrameBufferObject;
mFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(mSceneTex));
mFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(mDepthTex));
mFbo->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTex));
// When MSAA is enabled we must first render to a render buffer, then
// blit the result to the FBO which is either passed to the main frame
@ -247,7 +249,7 @@ namespace MWRender
osg::ref_ptr<osg::RenderBuffer> colorRB = new osg::RenderBuffer(width, height, mSceneTex->getInternalFormat(), samples);
osg::ref_ptr<osg::RenderBuffer> depthRB = new osg::RenderBuffer(width, height, mDepthTex->getInternalFormat(), samples);
mMsaaFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(colorRB));
mMsaaFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(depthRB));
mMsaaFbo->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(depthRB));
}
if (const auto depthProxy = std::getenv("OPENMW_ENABLE_DEPTH_CLEAR_PROXY"))
@ -264,8 +266,8 @@ namespace MWRender
{
mDepthTex = new osg::Texture2D;
mDepthTex->setTextureSize(width, height);
mDepthTex->setSourceFormat(GL_DEPTH_COMPONENT);
mDepthTex->setSourceType(SceneUtil::isFloatingPointDepthFormat(getDepthFormat()) ? GL_FLOAT : GL_UNSIGNED_INT);
mDepthTex->setSourceFormat(GL_DEPTH_STENCIL_EXT);
mDepthTex->setSourceType(SceneUtil::isFloatingPointDepthFormat(getDepthFormat()) ? GL_FLOAT_32_UNSIGNED_INT_24_8_REV : GL_UNSIGNED_INT_24_8_EXT);
mDepthTex->setInternalFormat(mDepthFormat);
mDepthTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
mDepthTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);

@ -538,6 +538,8 @@ namespace MWRender
SceneUtil::setCameraClearDepth(mViewer->getCamera());
updateProjectionMatrix();
mViewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
RenderingManager::~RenderingManager()
@ -656,11 +658,10 @@ namespace MWRender
if (relativeLuminance < mMinimumAmbientLuminance)
{
// brighten ambient so it reaches the minimum threshold but no more, we want to mess with content data as least we can
float targetBrightnessIncreaseFactor = mMinimumAmbientLuminance / relativeLuminance;
if (ambient.r() == 0.f && ambient.g() == 0.f && ambient.b() == 0.f)
ambient = osg::Vec4(mMinimumAmbientLuminance, mMinimumAmbientLuminance, mMinimumAmbientLuminance, ambient.a());
else
ambient *= targetBrightnessIncreaseFactor;
ambient *= mMinimumAmbientLuminance / relativeLuminance;
}
}

@ -348,7 +348,7 @@ namespace MWRender
rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & ~(Mask_GUI|Mask_FirstPerson));
rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
renderCameraToImage(rttCamera.get(),image,w,h);
}

@ -12,6 +12,7 @@
#include "../mwbase/scriptmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/inputmanager.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwworld/action.hpp"
#include "../mwworld/class.hpp"
@ -417,6 +418,7 @@ namespace MWScript
void InterpreterContext::executeActivation(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor)
{
MWBase::Environment::get().getLuaManager()->objectActivated(ptr, actor);
std::shared_ptr<MWWorld::Action> action = (ptr.getClass().activate(ptr, actor));
action->execute (actor);
if (action->getTarget() != MWWorld::Ptr() && action->getTarget() != ptr)

@ -218,8 +218,8 @@ namespace MWScript
MWMechanics::DynamicStat<float> stat (ptr.getClass().getCreatureStats (ptr)
.getDynamic (mIndex));
stat.setModified (value, 0);
stat.setCurrent(value);
stat.setBase(value);
stat.setCurrent(stat.getModified(false), true, true);
ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat);
}
@ -259,19 +259,18 @@ namespace MWScript
}
}
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent();
MWMechanics::DynamicStat<float> stat = stats.getDynamic(mIndex);
MWMechanics::DynamicStat<float> stat (ptr.getClass().getCreatureStats (ptr)
.getDynamic (mIndex));
float current = stat.getCurrent();
float base = diff + stat.getBase();
if(mIndex != 2)
base = std::max(base, 0.f);
stat.setBase(base);
stat.setCurrent(diff + current, true, true);
stat.setModified (diff + stat.getModified(), 0);
stat.setCurrentModified (diff + stat.getCurrentModified());
stat.setCurrent (diff + current);
ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat);
stats.setDynamic (mIndex, stat);
}
};
@ -325,17 +324,9 @@ namespace MWScript
void execute (Interpreter::Runtime& runtime) override
{
MWWorld::Ptr ptr = R()(runtime);
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
Interpreter::Type_Float value = 0;
Interpreter::Type_Float max = stats.getDynamic(mIndex).getModified();
if (max>0)
value = stats.getDynamic(mIndex).getCurrent() / max;
runtime.push (value);
runtime.push(stats.getDynamic(mIndex).getRatio());
}
};

@ -185,6 +185,11 @@ namespace
else if constexpr (std::is_same_v<T, ESM::NPC>)
MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory, &state.mNpcStats);
}
else if(state.mVersion < 20)
{
if constexpr (std::is_same_v<T, ESM::Creature> || std::is_same_v<T, ESM::NPC>)
MWWorld::convertStats(state.mCreatureStats);
}
if (state.mRef.mRefNum.hasContentFile())
{

@ -198,7 +198,11 @@ namespace MWWorld
for(std::size_t i = 0; i < ESM::Attribute::Length; ++i)
creatureStats.mAttributes[i].mMod = 0.f;
for(std::size_t i = 0; i < 3; ++i)
creatureStats.mDynamic[i].mMod = 0.f;
{
auto& dynamic = creatureStats.mDynamic[i];
dynamic.mCurrent -= dynamic.mMod - dynamic.mBase;
dynamic.mMod = 0.f;
}
for(std::size_t i = 0; i < 4; ++i)
creatureStats.mAiSettings[i].mMod = 0.f;
if(npcStats)
@ -207,4 +211,13 @@ namespace MWWorld
npcStats->mSkills[i].mMod = 0.f;
}
}
// Versions 17-19 wrote different modifiers to the savegame depending on whether the save had upgraded from a pre-17 version or not
void convertStats(ESM::CreatureStats& creatureStats)
{
for(std::size_t i = 0; i < 3; ++i)
creatureStats.mDynamic[i].mMod = 0.f;
for(std::size_t i = 0; i < 4; ++i)
creatureStats.mAiSettings[i].mMod = 0.f;
}
}

@ -12,6 +12,8 @@ namespace MWWorld
{
void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory,
ESM::NpcStats* npcStats = nullptr);
void convertStats(ESM::CreatureStats& creatureStats);
}
#endif

@ -39,7 +39,6 @@ namespace MWWorld
mTeleported(false),
mCurrentCrimeId(-1),
mPaidCrimeId(-1),
mAttackingOrSpell(false),
mJumping(false)
{
ESM::CellRef cellRef;
@ -266,12 +265,7 @@ namespace MWWorld
void Player::setAttackingOrSpell(bool attackingOrSpell)
{
mAttackingOrSpell = attackingOrSpell;
}
bool Player::getAttackingOrSpell() const
{
return mAttackingOrSpell;
getPlayer().getClass().getCreatureStats(getPlayer()).setAttackingOrSpell(attackingOrSpell);
}
void Player::setJumping(bool jumping)
@ -314,7 +308,6 @@ namespace MWWorld
mAutoMove = false;
mForwardBackward = 0;
mTeleported = false;
mAttackingOrSpell = false;
mJumping = false;
mCurrentCrimeId = -1;
mPaidCrimeId = -1;
@ -390,6 +383,8 @@ namespace MWWorld
}
if (reader.getFormat() < 17)
convertMagicEffects(player.mObject.mCreatureStats, player.mObject.mInventory, &player.mObject.mNpcStats);
else if(reader.getFormat() < 20)
convertStats(player.mObject.mCreatureStats);
if (!player.mObject.mEnabled)
{

@ -56,7 +56,6 @@ namespace MWWorld
float mSaveSkills[ESM::Skill::Length];
float mSaveAttributes[ESM::Attribute::Length];
bool mAttackingOrSpell;
bool mJumping;
public:
@ -112,7 +111,6 @@ namespace MWWorld
void setTeleported(bool teleported);
void setAttackingOrSpell(bool attackingOrSpell);
bool getAttackingOrSpell() const;
void setJumping(bool jumping);
bool getJumping() const;

@ -360,7 +360,7 @@ namespace MWWorld
mActiveCells.erase(cell);
}
void Scene::loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn)
void Scene::loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn, const osg::Vec3f& position)
{
using DetourNavigator::HeightfieldShape;
@ -461,7 +461,7 @@ namespace MWWorld
mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f);
}
mNavigator.update(player.getRefData().getPosition().asVec3());
mNavigator.update(position);
if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
mRendering.configureAmbient(cell->getCell());
@ -534,6 +534,8 @@ namespace MWWorld
unloadCell (cell);
}
mNavigator.updateBounds(pos);
mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY);
osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter);
mRendering.setActiveGrid(newGrid);
@ -608,7 +610,7 @@ namespace MWWorld
if (!isCellInCollection(x, y, mActiveCells))
{
CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y);
loadCell (cell, loadingListener, changeEvent);
loadCell(cell, loadingListener, changeEvent, pos);
}
}
@ -641,7 +643,7 @@ namespace MWWorld
loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")...");
CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY);
loadCell(cell, nullptr, false);
loadCell(cell, nullptr, false, osg::Vec3f(it->mData.mX + 0.5f, it->mData.mY + 0.5f, 0) * Constants::CellSizeInUnits);
auto iter = mActiveCells.begin();
while (iter != mActiveCells.end())
@ -686,7 +688,9 @@ namespace MWWorld
loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")...");
CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName);
loadCell(cell, nullptr, false);
ESM::Position position;
MWBase::Environment::get().getWorld()->findInteriorPosition(it->mName, position);
loadCell(cell, nullptr, false, position.asVec3());
auto iter = mActiveCells.begin();
while (iter != mActiveCells.end())
@ -819,11 +823,11 @@ namespace MWWorld
loadingListener->setProgressRange(cell->count());
mNavigator.updatePlayerPosition(position.asVec3());
mNavigator.updateBounds(position.asVec3());
// Load cell.
mPagedRefs.clear();
loadCell(cell, loadingListener, changeEvent);
loadCell(cell, loadingListener, changeEvent, position.asVec3());
changePlayerCell(cell, position, adjustPlayerPos);
@ -854,8 +858,6 @@ namespace MWWorld
if (changeEvent)
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5);
mNavigator.updatePlayerPosition(position.asVec3());
changeCellGrid(position.asVec3(), x, y, changeEvent);
CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y);

@ -116,7 +116,7 @@ namespace MWWorld
osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const;
void unloadCell(CellStore* cell);
void loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn);
void loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn, const osg::Vec3f& position);
public:

@ -1525,7 +1525,12 @@ namespace MWWorld
void World::updateNavigator()
{
mPhysics->forEachAnimatedObject([&] (const MWPhysics::Object* object) { updateNavigatorObject(*object); });
mPhysics->forEachAnimatedObject([&] (const auto& pair)
{
const auto [object, changed] = pair;
if (changed)
updateNavigatorObject(*object);
});
for (const auto& door : mDoorStates)
if (const auto object = mPhysics->getObject(door.first))
@ -3827,7 +3832,8 @@ namespace MWWorld
if (object.getRefData().activate())
{
std::shared_ptr<MWWorld::Action> action = (object.getClass().activate(object, actor));
MWBase::Environment::get().getLuaManager()->objectActivated(object, actor);
std::shared_ptr<MWWorld::Action> action = object.getClass().activate(object, actor);
action->execute (actor);
}
}

@ -70,6 +70,8 @@ if (GTEST_FOUND AND GMOCK_FOUND)
esmloader/esmdata.cpp
files/hash.cpp
toutf8/toutf8.cpp
)
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
@ -93,6 +95,8 @@ if (GTEST_FOUND AND GMOCK_FOUND)
EXPECTED_MD5 bf3691034a38611534c74c3b89a7d2c3
)
target_compile_definitions(openmw_test_suite PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data")
target_compile_definitions(openmw_test_suite
PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data"
OPENMW_TEST_SUITE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
endif()

@ -40,7 +40,7 @@ namespace
osg::ref_ptr<Resource::BulletShapeInstance>(new Resource::BulletShapeInstance(bulletShape)),
shape, objectTransform
);
recastMeshManager.addObject(id, collisionShape, btTransform::getIdentity(), AreaType_ground);
recastMeshManager.addObject(id, collisionShape, btTransform::getIdentity(), AreaType_ground, [] (auto) {});
}
struct DetourNavigatorAsyncNavMeshUpdaterTest : Test

@ -37,35 +37,35 @@ namespace
TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_in_single_tile_should_return_one_tile)
{
getTilesPositions(makeTilesPositionsRange(osg::Vec3f(2, 2, 0), osg::Vec3f(31, 31, 1), mSettings), mCollect);
getTilesPositions(makeTilesPositionsRange(osg::Vec2f(2, 2), osg::Vec2f(31, 31), mSettings), mCollect);
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0)));
}
TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_x_bounds_in_two_tiles_should_return_two_tiles)
{
getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 31, 1), mSettings), mCollect);
getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(32, 31), mSettings), mCollect);
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(1, 0)));
}
TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_y_bounds_in_two_tiles_should_return_two_tiles)
{
getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 32, 1), mSettings), mCollect);
getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(31, 32), mSettings), mCollect);
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(0, 1)));
}
TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_works_only_for_x_and_y_coordinates)
{
getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 31, 32), mSettings), mCollect);
getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(31, 31), mSettings), mCollect);
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0)));
}
TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_should_work_with_negative_coordinates)
{
getTilesPositions(makeTilesPositionsRange(osg::Vec3f(-31, -31, 0), osg::Vec3f(31, 31, 1), mSettings), mCollect);
getTilesPositions(makeTilesPositionsRange(osg::Vec2f(-31, -31), osg::Vec2f(31, 31), mSettings), mCollect);
EXPECT_THAT(mTilesPositions, ElementsAre(
TilePosition(-1, -1),
@ -79,7 +79,7 @@ namespace
{
mSettings.mBorderSize = 1;
getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31.5, 31.5, 1), mSettings), mCollect);
getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(31.5, 31.5), mSettings), mCollect);
EXPECT_THAT(mTilesPositions, ElementsAre(
TilePosition(-1, -1),
@ -98,7 +98,7 @@ namespace
{
mSettings.mRecastScaleFactor = 0.5;
getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 32, 1), mSettings), mCollect);
getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(32, 32), mSettings), mCollect);
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0)));
}

@ -1132,4 +1132,16 @@ namespace
Vec3fEq(306, 56.66666412353515625, -2.6667339801788330078125)
)) << mPath;
}
TEST_F(DetourNavigatorNavigatorTest, only_one_water_per_cell_is_allowed)
{
const int cellSize1 = 100;
const float level1 = 1;
const int cellSize2 = 200;
const float level2 = 2;
mNavigator->addAgent(mAgentHalfExtents);
EXPECT_TRUE(mNavigator->addWater(mCellPosition, cellSize1, level1));
EXPECT_FALSE(mNavigator->addWater(mCellPosition, cellSize2, level2));
}
}

@ -12,14 +12,6 @@
#include <gtest/gtest.h>
namespace DetourNavigator
{
static inline bool operator ==(const TileBounds& lhs, const TileBounds& rhs)
{
return lhs.mMin == rhs.mMin && lhs.mMax == rhs.mMax;
}
}
namespace
{
template <class T>

@ -16,7 +16,8 @@ namespace
struct DetourNavigatorTileCachedRecastMeshManagerTest : Test
{
RecastSettings mSettings;
std::vector<TilePosition> mChangedTiles;
std::vector<TilePosition> mAddedTiles;
std::vector<std::pair<TilePosition, ChangeType>> mChangedTiles;
const ObjectTransform mObjectTransform {ESM::Position {{0, 0, 0}, {0, 0, 0}}, 0.0f};
const osg::ref_ptr<const Resource::BulletShape> mShape = new Resource::BulletShape;
const osg::ref_ptr<const Resource::BulletShapeInstance> mInstance = new Resource::BulletShapeInstance(mShape);
@ -29,9 +30,14 @@ namespace
mSettings.mTileSize = 64;
}
void onChangedTile(const TilePosition& tilePosition)
void onAddedTile(const TilePosition& tilePosition)
{
mChangedTiles.push_back(tilePosition);
mAddedTiles.push_back(tilePosition);
}
void onChangedTile(const TilePosition& tilePosition, ChangeType changeType)
{
mChangedTiles.emplace_back(tilePosition, changeType);
}
};
@ -60,7 +66,7 @@ namespace
TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}));
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false)
@ -68,8 +74,8 @@ namespace
TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}));
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles)
@ -78,12 +84,26 @@ namespace
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}));
for (int x = -1; x < 1; ++x)
for (int y = -1; y < 1; ++y)
ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr);
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_return_added_tiles)
{
TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
TileBounds bounds;
bounds.mMin = osg::Vec2f(182, 182);
bounds.mMax = osg::Vec2f(1000, 1000);
manager.setBounds(bounds);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground,
[&] (const auto& v) { onAddedTile(v); });
EXPECT_THAT(mAddedTiles, ElementsAre(TilePosition(0, 0)));
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles)
{
TileCachedRecastMeshManager manager(mSettings);
@ -94,14 +114,17 @@ namespace
bounds.mMin = osg::Vec2f(-1000, -1000);
bounds.mMax = osg::Vec2f(1000, 1000);
manager.setBounds(bounds);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {});
EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground,
[&] (const auto& v) { onChangedTile(v); }));
EXPECT_THAT(
mChangedTiles,
ElementsAre(TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(0, -1), TilePosition(0, 0),
TilePosition(1, -1), TilePosition(1, 0))
);
[&] (const auto& ... v) { onChangedTile(v ...); }));
EXPECT_THAT(mChangedTiles, ElementsAre(
std::pair(TilePosition(-1, -1), ChangeType::add),
std::pair(TilePosition(-1, 0), ChangeType::add),
std::pair(TilePosition(0, -1), ChangeType::update),
std::pair(TilePosition(0, 0), ChangeType::update),
std::pair(TilePosition(1, -1), ChangeType::remove),
std::pair(TilePosition(1, 0), ChangeType::remove)
));
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_not_changed_object_should_return_empty)
@ -109,10 +132,10 @@ namespace
TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground,
[&] (const auto& v) { onChangedTile(v); }));
EXPECT_EQ(mChangedTiles, std::vector<TilePosition>());
[&] (const auto& ... v) { onChangedTile(v ...); }));
EXPECT_THAT(mChangedTiles, IsEmpty());
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile)
@ -121,7 +144,7 @@ namespace
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
@ -134,7 +157,7 @@ namespace
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr);
}
@ -151,13 +174,13 @@ namespace
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {});
EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr);
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {});
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
@ -173,11 +196,11 @@ namespace
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {});
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {});
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr);
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr);
}
@ -188,7 +211,7 @@ namespace
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
manager.removeObject(ObjectId(&boxShape));
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
@ -203,13 +226,13 @@ namespace
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {});
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
@ -222,7 +245,7 @@ namespace
const auto initialRevision = manager.getRevision();
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
EXPECT_EQ(manager.getRevision(), initialRevision + 1);
}
@ -231,9 +254,9 @@ namespace
TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
const auto beforeAddRevision = manager.getRevision();
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}));
EXPECT_EQ(manager.getRevision(), beforeAddRevision);
}
@ -243,9 +266,9 @@ namespace
const btBoxShape boxShape(btVector3(20, 20, 100));
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {});
const auto beforeUpdateRevision = manager.getRevision();
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {});
EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1);
}
@ -255,9 +278,9 @@ namespace
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
const auto beforeUpdateRevision = manager.getRevision();
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {});
EXPECT_EQ(manager.getRevision(), beforeUpdateRevision);
}
@ -266,7 +289,7 @@ namespace
TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
const auto beforeRemoveRevision = manager.getRevision();
manager.removeObject(ObjectId(&boxShape));
EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1);
@ -306,7 +329,7 @@ namespace
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}));
const osg::Vec2i cellPosition(0, 0);
const int cellSize = std::numeric_limits<int>::max();
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
@ -351,7 +374,7 @@ namespace
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}));
const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
@ -369,7 +392,7 @@ namespace
const int cellSize = 8192;
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}));
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape)));
for (int x = -1; x < 12; ++x)
@ -383,10 +406,28 @@ namespace
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(nullptr, boxShape, mObjectTransform);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}));
manager.setWorldspace("other");
for (int x = -1; x < 1; ++x)
for (int y = -1; y < 1; ++y)
ASSERT_EQ(manager.getMesh("other", TilePosition(x, y)), nullptr);
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, set_bounds_should_return_changed_tiles)
{
TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
TileBounds bounds;
bounds.mMin = osg::Vec2f(182, 0);
bounds.mMax = osg::Vec2f(1000, 1000);
manager.setBounds(bounds);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
bounds.mMin = osg::Vec2f(-1000, -1000);
bounds.mMax = osg::Vec2f(0, -182);
EXPECT_THAT(manager.setBounds(bounds), ElementsAre(
std::pair(TilePosition(-1, -1), ChangeType::add),
std::pair(TilePosition(0, 0), ChangeType::remove)
));
}
}

@ -1,12 +1,12 @@
#include <gtest/gtest.h>
#include "components/esm/esmcommon.hpp"
#include "components/esm/defs.hpp"
TEST(EsmFixedString, operator__eq_ne)
{
{
SCOPED_TRACE("asdc == asdc");
ESM::NAME name;
name.assign("asdc");
constexpr ESM::NAME name("asdc");
char s[4] = {'a', 's', 'd', 'c'};
std::string ss(s, 4);
@ -16,8 +16,7 @@ TEST(EsmFixedString, operator__eq_ne)
}
{
SCOPED_TRACE("asdc == asdcx");
ESM::NAME name;
name.assign("asdc");
constexpr ESM::NAME name("asdc");
char s[5] = {'a', 's', 'd', 'c', 'x'};
std::string ss(s, 5);
@ -27,8 +26,7 @@ TEST(EsmFixedString, operator__eq_ne)
}
{
SCOPED_TRACE("asdc == asdc[NULL]");
ESM::NAME name;
name.assign("asdc");
const ESM::NAME name("asdc");
char s[5] = {'a', 's', 'd', 'c', '\0'};
std::string ss(s, 5);
@ -41,8 +39,7 @@ TEST(EsmFixedString, operator__eq_ne_const)
{
{
SCOPED_TRACE("asdc == asdc (const)");
ESM::NAME name;
name.assign("asdc");
constexpr ESM::NAME name("asdc");
const char s[4] = { 'a', 's', 'd', 'c' };
std::string ss(s, 4);
@ -52,8 +49,7 @@ TEST(EsmFixedString, operator__eq_ne_const)
}
{
SCOPED_TRACE("asdc == asdcx (const)");
ESM::NAME name;
name.assign("asdc");
constexpr ESM::NAME name("asdc");
const char s[5] = { 'a', 's', 'd', 'c', 'x' };
std::string ss(s, 5);
@ -63,8 +59,7 @@ TEST(EsmFixedString, operator__eq_ne_const)
}
{
SCOPED_TRACE("asdc == asdc[NULL] (const)");
ESM::NAME name;
name.assign("asdc");
constexpr ESM::NAME name("asdc");
const char s[5] = { 'a', 's', 'd', 'c', '\0' };
std::string ss(s, 5);
@ -148,3 +143,15 @@ TEST(EsmFixedString, assignment_operator_is_supported_for_uint32)
value = static_cast<uint32_t>(0xFEDCBA98u);
EXPECT_EQ(value, static_cast<uint32_t>(0xFEDCBA98u)) << value.toInt();
}
TEST(EsmFixedString, construction_from_uint32_is_supported)
{
constexpr ESM::NAME value(0xFEDCBA98u);
EXPECT_EQ(value, static_cast<std::uint32_t>(0xFEDCBA98u)) << value.toInt();
}
TEST(EsmFixedString, construction_from_RecNameInts_is_supported)
{
constexpr ESM::NAME value(ESM::RecNameInts::REC_ACTI);
EXPECT_EQ(value, static_cast<std::uint32_t>(ESM::RecNameInts::REC_ACTI)) << value.toInt();
}

@ -39,6 +39,7 @@ return {
-- should throw an error
incorrectRequire = function() require('counter') end,
modifySystemLib = function() math.sin = 5 end,
modifySystemLib2 = function() math.__index.sin = 5 end,
rawsetSystemLib = function() rawset(math, 'sin', 5) end,
callLoadstring = function() loadstring('print(1)') end,
setSqr = function() require('sqrlib').sqr = math.sin end,
@ -119,6 +120,7 @@ return {
// but read-only object can not be modified even with rawset
EXPECT_ERROR(LuaUtil::call(script["rawsetSystemLib"]), "bad argument #1 to 'rawset' (table expected, got userdata)");
EXPECT_ERROR(LuaUtil::call(script["modifySystemLib"]), "a userdata value");
EXPECT_ERROR(LuaUtil::call(script["modifySystemLib2"]), "a nil value");
EXPECT_EQ(LuaUtil::getMutableFromReadOnly(LuaUtil::makeReadOnly(script)), script);
}

@ -39,6 +39,10 @@ namespace
EXPECT_TRUE(get<bool>(lua, "v2 == util.vector2(3/5, 4/5)"));
lua.safe_script("_, len = util.vector2(0, 0):normalize()");
EXPECT_FLOAT_EQ(get<float>(lua, "len"), 0);
lua.safe_script("ediv0 = util.vector2(1, 0):ediv(util.vector2(0, 0))");
EXPECT_TRUE(get<bool>(lua, "ediv0.x == math.huge and ediv0.y ~= ediv0.y"));
EXPECT_TRUE(get<bool>(lua, "util.vector2(1, 2):emul(util.vector2(3, 4)) == util.vector2(3, 8)"));
EXPECT_TRUE(get<bool>(lua, "util.vector2(4, 6):ediv(util.vector2(2, 3)) == util.vector2(2, 2)"));
}
TEST(LuaUtilPackageTest, Vector3)
@ -68,6 +72,10 @@ namespace
EXPECT_TRUE(get<bool>(lua, "v2 == util.vector3(3/5, 4/5, 0)"));
lua.safe_script("_, len = util.vector3(0, 0, 0):normalize()");
EXPECT_FLOAT_EQ(get<float>(lua, "len"), 0);
lua.safe_script("ediv0 = util.vector3(1, 1, 1):ediv(util.vector3(0, 0, 0))");
EXPECT_TRUE(get<bool>(lua, "ediv0.z == math.huge"));
EXPECT_TRUE(get<bool>(lua, "util.vector3(1, 2, 3):emul(util.vector3(3, 4, 5)) == util.vector3(3, 8, 15)"));
EXPECT_TRUE(get<bool>(lua, "util.vector3(4, 6, 8):ediv(util.vector3(2, 3, 4)) == util.vector3(2, 2, 2)"));
}
TEST(LuaUtilPackageTest, Vector4)
@ -95,6 +103,10 @@ namespace
EXPECT_TRUE(get<bool>(lua, "v2 == util.vector4(3/5, 0, 0, 4/5)"));
lua.safe_script("_, len = util.vector4(0, 0, 0, 0):normalize()");
EXPECT_FLOAT_EQ(get<float>(lua, "len"), 0);
lua.safe_script("ediv0 = util.vector4(1, 1, 1, -1):ediv(util.vector4(0, 0, 0, 0))");
EXPECT_TRUE(get<bool>(lua, "ediv0.w == -math.huge"));
EXPECT_TRUE(get<bool>(lua, "util.vector4(1, 2, 3, 4):emul(util.vector4(3, 4, 5, 6)) == util.vector4(3, 8, 15, 24)"));
EXPECT_TRUE(get<bool>(lua, "util.vector4(4, 6, 8, 9):ediv(util.vector4(2, 3, 4, 3)) == util.vector4(2, 2, 2, 3)"));
}
TEST(LuaUtilPackageTest, Color)

@ -58,6 +58,7 @@ struct ContentFileTest : public ::testing::Test
("content", boost::program_options::value<std::vector<std::string>>()->default_value(std::vector<std::string>(), "")
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon")
("data-local", boost::program_options::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""));
Files::ConfigurationManager::addCommonOptions(desc);
boost::program_options::notify(variables);

@ -0,0 +1,159 @@
#include <components/to_utf8/to_utf8.hpp>
#include <gtest/gtest.h>
#include <fstream>
#ifndef OPENMW_TEST_SUITE_SOURCE_DIR
#define OPENMW_TEST_SUITE_SOURCE_DIR ""
#endif
namespace
{
using namespace testing;
using namespace ToUTF8;
struct Params
{
FromType mLegacyEncoding;
std::string mLegacyEncodingFileName;
std::string mUtf8FileName;
};
std::string readContent(const std::string& fileName)
{
std::ifstream file;
file.exceptions(std::ios::failbit | std::ios::badbit);
file.open(std::string(OPENMW_TEST_SUITE_SOURCE_DIR) + "/toutf8/data/" + fileName);
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
struct Utf8EncoderTest : TestWithParam<Params> {};
TEST(Utf8EncoderTest, getUtf8ShouldReturnEmptyAsIs)
{
Utf8Encoder encoder(FromType::CP437);
EXPECT_EQ(encoder.getUtf8(std::string_view()), std::string_view());
}
TEST(Utf8EncoderTest, getUtf8ShouldReturnAsciiOnlyAsIs)
{
std::string input;
for (int c = 1; c <= std::numeric_limits<char>::max(); ++c)
input.push_back(c);
Utf8Encoder encoder(FromType::CP437);
const std::string_view result = encoder.getUtf8(input);
EXPECT_EQ(result.data(), input.data());
EXPECT_EQ(result.size(), input.size());
}
TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilZero)
{
const std::string input("a\0b");
Utf8Encoder encoder(FromType::CP437);
const std::string_view result = encoder.getUtf8(input);
EXPECT_EQ(result, "a");
}
TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilEndOfInputForAscii)
{
const std::string input("abc");
Utf8Encoder encoder(FromType::CP437);
const std::string_view result = encoder.getUtf8(std::string_view(input.data(), 2));
EXPECT_EQ(result, "ab");
}
TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilEndOfInputForNonAscii)
{
const std::string input("a\x92" "b");
Utf8Encoder encoder(FromType::WINDOWS_1252);
const std::string_view result = encoder.getUtf8(std::string_view(input.data(), 2));
EXPECT_EQ(result, "a\xE2\x80\x99");
}
TEST_P(Utf8EncoderTest, getUtf8ShouldConvertFromLegacyEncodingToUtf8)
{
const std::string input(readContent(GetParam().mLegacyEncodingFileName));
const std::string expected(readContent(GetParam().mUtf8FileName));
Utf8Encoder encoder(GetParam().mLegacyEncoding);
const std::string_view result = encoder.getUtf8(input);
EXPECT_EQ(result, expected);
}
TEST(Utf8EncoderTest, getLegacyEncShouldReturnEmptyAsIs)
{
Utf8Encoder encoder(FromType::CP437);
EXPECT_EQ(encoder.getLegacyEnc(std::string_view()), std::string_view());
}
TEST(Utf8EncoderTest, getLegacyEncShouldReturnAsciiOnlyAsIs)
{
std::string input;
for (int c = 1; c <= std::numeric_limits<char>::max(); ++c)
input.push_back(c);
Utf8Encoder encoder(FromType::CP437);
const std::string_view result = encoder.getLegacyEnc(input);
EXPECT_EQ(result.data(), input.data());
EXPECT_EQ(result.size(), input.size());
}
TEST(Utf8EncoderTest, getLegacyEncShouldLookUpUntilZero)
{
const std::string input("a\0b");
Utf8Encoder encoder(FromType::CP437);
const std::string_view result = encoder.getLegacyEnc(input);
EXPECT_EQ(result, "a");
}
TEST(Utf8EncoderTest, getLegacyEncShouldLookUpUntilEndOfInputForAscii)
{
const std::string input("abc");
Utf8Encoder encoder(FromType::CP437);
const std::string_view result = encoder.getLegacyEnc(std::string_view(input.data(), 2));
EXPECT_EQ(result, "ab");
}
TEST(Utf8EncoderTest, getLegacyEncShouldStripIncompleteCharacters)
{
const std::string input("a\xc3\xa2\xe2\x80\x99");
Utf8Encoder encoder(FromType::WINDOWS_1252);
const std::string_view result = encoder.getLegacyEnc(std::string_view(input.data(), 5));
EXPECT_EQ(result, "a\xe2");
}
TEST_P(Utf8EncoderTest, getLegacyEncShouldConvertFromUtf8ToLegacyEncoding)
{
const std::string input(readContent(GetParam().mUtf8FileName));
const std::string expected(readContent(GetParam().mLegacyEncodingFileName));
Utf8Encoder encoder(GetParam().mLegacyEncoding);
const std::string_view result = encoder.getLegacyEnc(input);
EXPECT_EQ(result, expected);
}
INSTANTIATE_TEST_SUITE_P(Files, Utf8EncoderTest, Values(
Params {ToUTF8::WINDOWS_1251, "russian-win1251.txt", "russian-utf8.txt"},
Params {ToUTF8::WINDOWS_1252, "french-win1252.txt", "french-utf8.txt"}
));
TEST(StatelessUtf8EncoderTest, shouldCleanupBuffer)
{
std::string buffer;
StatelessUtf8Encoder encoder(FromType::WINDOWS_1252);
encoder.getUtf8(std::string_view("long string\x92"), BufferAllocationPolicy::UseGrowFactor, buffer);
const std::string shortString("short\x92");
ASSERT_GT(buffer.size(), shortString.size());
const std::string_view shortUtf8 = encoder.getUtf8(shortString, BufferAllocationPolicy::UseGrowFactor, buffer);
ASSERT_GE(buffer.size(), shortUtf8.size());
EXPECT_EQ(buffer[shortUtf8.size()], '\0') << buffer;
}
TEST(StatelessUtf8EncoderTest, withFitToRequiredSizeShouldResizeBuffer)
{
std::string buffer;
StatelessUtf8Encoder encoder(FromType::WINDOWS_1252);
const std::string_view utf8 = encoder.getUtf8(std::string_view("long string\x92"), BufferAllocationPolicy::FitToRequiredSize, buffer);
EXPECT_EQ(buffer.size(), utf8.size());
}
}

@ -175,8 +175,9 @@ add_component_dir (queries
)
add_component_dir (lua_ui
properties widget element util layers content
text textedit window image
registerscriptsettings scriptsettings
properties widget element util layers content alignment
adapter text textedit window image container
)

@ -445,8 +445,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path)
try {
ESM::ESMReader fileReader;
ToUTF8::Utf8Encoder encoder =
ToUTF8::calculateEncoding(mEncoding.toStdString());
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString()));
fileReader.setEncoder(&encoder);
fileReader.open(std::string(dir.absoluteFilePath(path2).toUtf8().constData()));

@ -137,61 +137,65 @@ namespace Debug
}
static std::unique_ptr<std::ostream> rawStdout = nullptr;
static std::unique_ptr<std::ostream> rawStderr = nullptr;
static boost::filesystem::ofstream logfile;
#if defined(_WIN32) && defined(_DEBUG)
static boost::iostreams::stream_buffer<Debug::DebugOutput> sb;
#else
static boost::iostreams::stream_buffer<Debug::Tee> coutsb;
static boost::iostreams::stream_buffer<Debug::Tee> cerrsb;
#endif
std::ostream& getRawStdout()
{
return rawStdout ? *rawStdout : std::cout;
}
int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName)
// Redirect cout and cerr to the log file
void setupLogging(const std::string& logDir, const std::string& appName, std::ios_base::openmode mode)
{
#if defined _WIN32
(void)Debug::attachParentConsole();
#endif
rawStdout = std::make_unique<std::ostream>(std::cout.rdbuf());
// Some objects used to redirect cout and cerr
// Scope must be here, so this still works inside the catch block for logging exceptions
std::streambuf* cout_rdbuf = std::cout.rdbuf ();
std::streambuf* cerr_rdbuf = std::cerr.rdbuf ();
#if defined(_WIN32) && defined(_DEBUG)
boost::iostreams::stream_buffer<Debug::DebugOutput> sb;
// Redirect cout and cerr to VS debug output when running in debug mode
sb.open(Debug::DebugOutput());
std::cout.rdbuf(&sb);
std::cerr.rdbuf(&sb);
#else
boost::iostreams::stream_buffer<Debug::Tee> coutsb;
boost::iostreams::stream_buffer<Debug::Tee> cerrsb;
std::ostream oldcout(cout_rdbuf);
std::ostream oldcerr(cerr_rdbuf);
const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log";
logfile.open(boost::filesystem::path(logDir) / logName, mode);
coutsb.open(Debug::Tee(logfile, *rawStdout));
cerrsb.open(Debug::Tee(logfile, *rawStderr));
std::cout.rdbuf(&coutsb);
std::cerr.rdbuf(&cerrsb);
#endif
}
const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log";
boost::filesystem::ofstream logfile;
int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[],
const std::string& appName, bool autoSetupLogging)
{
#if defined _WIN32
(void)Debug::attachParentConsole();
#endif
rawStdout = std::make_unique<std::ostream>(std::cout.rdbuf());
rawStderr = std::make_unique<std::ostream>(std::cerr.rdbuf());
int ret = 0;
try
{
Files::ConfigurationManager cfgMgr;
#if defined(_WIN32) && defined(_DEBUG)
// Redirect cout and cerr to VS debug output when running in debug mode
sb.open(Debug::DebugOutput());
std::cout.rdbuf (&sb);
std::cerr.rdbuf (&sb);
#else
// Redirect cout and cerr to the log file
// If we are collecting a stack trace, append to existing log file
std::ios_base::openmode mode = std::ios::out;
if(argc == 2 && strcmp(argv[1], crash_switch) == 0)
mode |= std::ios::app;
logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / logName), mode);
if (autoSetupLogging)
{
std::ios_base::openmode mode = std::ios::out;
coutsb.open (Debug::Tee(logfile, oldcout));
cerrsb.open (Debug::Tee(logfile, oldcerr));
// If we are collecting a stack trace, append to existing log file
if (argc == 2 && strcmp(argv[1], crash_switch) == 0)
mode |= std::ios::app;
std::cout.rdbuf (&coutsb);
std::cerr.rdbuf (&cerrsb);
#endif
setupLogging(cfgMgr.getLogPath().string(), appName, mode);
}
#if defined(_WIN32)
const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.dmp";
@ -217,8 +221,8 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c
}
// Restore cout and cerr
std::cout.rdbuf(cout_rdbuf);
std::cerr.rdbuf(cerr_rdbuf);
std::cout.rdbuf(rawStdout->rdbuf());
std::cerr.rdbuf(rawStderr->rdbuf());
Debug::CurrentDebugLevel = Debug::NoLevel;
return ret;

@ -133,11 +133,16 @@ namespace Debug
std::map<Level, int> mColors;
};
#endif
}
// Can be used to print messages without timestamps
std::ostream& getRawStdout();
int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName);
void setupLogging(const std::string& logDir, const std::string& appName, std::ios_base::openmode = std::ios::out);
int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[],
const std::string& appName, bool autoSetupLogging = true);
#endif

@ -8,6 +8,7 @@
#include "navmeshtilescache.hpp"
#include "waitconditiontype.hpp"
#include "navmeshdb.hpp"
#include "changetype.hpp"
#include <osg/Vec3f>
@ -32,29 +33,6 @@ namespace Loading
namespace DetourNavigator
{
enum class ChangeType
{
remove = 0,
mixed = 1,
add = 2,
update = 3,
};
inline std::ostream& operator <<(std::ostream& stream, ChangeType value)
{
switch (value) {
case ChangeType::remove:
return stream << "ChangeType::remove";
case ChangeType::mixed:
return stream << "ChangeType::mixed";
case ChangeType::add:
return stream << "ChangeType::add";
case ChangeType::update:
return stream << "ChangeType::update";
}
return stream << "ChangeType::" << static_cast<int>(value);
}
enum class JobState
{
Initial,

@ -0,0 +1,33 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CHANGETYPE_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_CHANGETYPE_H
#include <ostream>
namespace DetourNavigator
{
enum class ChangeType
{
remove = 0,
mixed = 1,
add = 2,
update = 3,
};
inline std::ostream& operator <<(std::ostream& stream, ChangeType value)
{
switch (value)
{
case ChangeType::remove:
return stream << "ChangeType::remove";
case ChangeType::mixed:
return stream << "ChangeType::mixed";
case ChangeType::add:
return stream << "ChangeType::add";
case ChangeType::update:
return stream << "ChangeType::update";
}
return stream << "ChangeType::" << static_cast<int>(value);
}
}
#endif

@ -10,15 +10,15 @@
namespace DetourNavigator
{
TilesPositionsRange makeTilesPositionsRange(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax,
TilesPositionsRange makeTilesPositionsRange(const osg::Vec2f& aabbMin, const osg::Vec2f& aabbMax,
const RecastSettings& settings)
{
osg::Vec3f min = toNavMeshCoordinates(settings, aabbMin);
osg::Vec3f max = toNavMeshCoordinates(settings, aabbMax);
osg::Vec2f min = toNavMeshCoordinates(settings, aabbMin);
osg::Vec2f max = toNavMeshCoordinates(settings, aabbMax);
const float border = getBorderSize(settings);
min -= osg::Vec3f(border, border, border);
max += osg::Vec3f(border, border, border);
min -= osg::Vec2f(border, border);
max += osg::Vec2f(border, border);
TilePosition minTile = getTilePosition(settings, min);
TilePosition maxTile = getTilePosition(settings, max);
@ -29,27 +29,27 @@ namespace DetourNavigator
if (minTile.y() > maxTile.y())
std::swap(minTile.y(), maxTile.y());
return {minTile, maxTile};
return {minTile, maxTile + osg::Vec2i(1, 1)};
}
TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform,
const RecastSettings& settings)
{
const TileBounds bounds = makeObjectTileBounds(shape, transform);
return makeTilesPositionsRange(bounds.mMin, bounds.mMax, settings);
}
TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform,
const TileBounds& bounds, const RecastSettings& settings)
{
btVector3 aabbMin;
btVector3 aabbMax;
shape.getAabb(transform, aabbMin, aabbMax);
aabbMin.setX(std::max<btScalar>(aabbMin.x(), bounds.mMin.x()));
aabbMin.setY(std::max<btScalar>(aabbMin.y(), bounds.mMin.y()));
aabbMax.setX(std::min<btScalar>(aabbMax.x(), bounds.mMax.x()));
aabbMax.setY(std::min<btScalar>(aabbMax.y(), bounds.mMax.y()));
return makeTilesPositionsRange(Misc::Convert::toOsg(aabbMin), Misc::Convert::toOsg(aabbMax), settings);
if (const auto intersection = getIntersection(bounds, makeObjectTileBounds(shape, transform)))
return makeTilesPositionsRange(intersection->mMin, intersection->mMax, settings);
return {};
}
TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift,
const RecastSettings& settings)
{
using Misc::Convert::toOsg;
const int halfCellSize = cellSize / 2;
const btTransform transform(btMatrix3x3::getIdentity(), shift);
btVector3 aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0));
@ -61,6 +61,19 @@ namespace DetourNavigator
aabbMax.setX(std::max(aabbMin.x(), aabbMax.x()));
aabbMax.setY(std::max(aabbMin.y(), aabbMax.y()));
return makeTilesPositionsRange(toOsg(aabbMin), toOsg(aabbMax), settings);
return makeTilesPositionsRange(Misc::Convert::toOsgXY(aabbMin), Misc::Convert::toOsgXY(aabbMax), settings);
}
TilesPositionsRange getIntersection(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept
{
const int beginX = std::max(a.mBegin.x(), b.mBegin.x());
const int endX = std::min(a.mEnd.x(), b.mEnd.x());
if (beginX > endX)
return {};
const int beginY = std::max(a.mBegin.y(), b.mBegin.y());
const int endY = std::min(a.mEnd.y(), b.mEnd.y());
if (beginY > endY)
return {};
return TilesPositionsRange {TilePosition(beginX, beginY), TilePosition(endX, endY)};
}
}

@ -10,7 +10,7 @@ class btCollisionShape;
namespace osg
{
class Vec3f;
class Vec2f;
}
namespace DetourNavigator
@ -19,12 +19,15 @@ namespace DetourNavigator
struct TilesPositionsRange
{
TilePosition mMin;
TilePosition mMax;
TilePosition mBegin;
TilePosition mEnd;
};
TilesPositionsRange makeTilesPositionsRange(const osg::Vec3f& aabbMin,
const osg::Vec3f& aabbMax, const RecastSettings& settings);
TilesPositionsRange makeTilesPositionsRange(const osg::Vec2f& aabbMin,
const osg::Vec2f& aabbMax, const RecastSettings& settings);
TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape,
const btTransform& transform, const RecastSettings& settings);
TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape,
const btTransform& transform, const TileBounds& bounds, const RecastSettings& settings);
@ -33,12 +36,25 @@ namespace DetourNavigator
const RecastSettings& settings);
template <class Callback>
void getTilesPositions(const TilesPositionsRange& range, Callback&& callback)
inline void getTilesPositions(const TilesPositionsRange& range, Callback&& callback)
{
for (int tileX = range.mMin.x(); tileX <= range.mMax.x(); ++tileX)
for (int tileY = range.mMin.y(); tileY <= range.mMax.y(); ++tileY)
for (int tileX = range.mBegin.x(); tileX < range.mEnd.x(); ++tileX)
for (int tileY = range.mBegin.y(); tileY < range.mEnd.y(); ++tileY)
callback(TilePosition {tileX, tileY});
}
inline bool isInTilesPositionsRange(int begin, int end, int coordinate)
{
return begin <= coordinate && coordinate < end;
}
inline bool isInTilesPositionsRange(const TilesPositionsRange& range, const TilePosition& position)
{
return isInTilesPositionsRange(range.mBegin.x(), range.mEnd.x(), position.x())
&& isInTilesPositionsRange(range.mBegin.y(), range.mEnd.y(), position.y());
}
TilesPositionsRange getIntersection(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept;
}
#endif

@ -83,6 +83,12 @@ namespace DetourNavigator
*/
virtual void setWorldspace(std::string_view worldspace) = 0;
/**
* @brief updateBounds should be called before adding object from loading cell
* @param playerPosition corresponds to the bounds center
*/
virtual void updateBounds(const osg::Vec3f& playerPosition) = 0;
/**
* @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes
* @param id is used to distinguish different objects

@ -38,6 +38,11 @@ namespace DetourNavigator
mNavMeshManager.setWorldspace(worldspace);
}
void NavigatorImpl::updateBounds(const osg::Vec3f& playerPosition)
{
mNavMeshManager.updateBounds(playerPosition);
}
bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform)
{
const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform);
@ -158,6 +163,7 @@ namespace DetourNavigator
const TilePosition tilePosition = getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition));
if (mLastPlayerPosition.has_value() && *mLastPlayerPosition == tilePosition)
return;
mNavMeshManager.updateBounds(playerPosition);
update(playerPosition);
mLastPlayerPosition = tilePosition;
}

@ -24,6 +24,8 @@ namespace DetourNavigator
void setWorldspace(std::string_view worldspace) override;
void updateBounds(const osg::Vec3f& playerPosition) override;
bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override;
bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override;

@ -72,6 +72,8 @@ namespace DetourNavigator
void update(const osg::Vec3f& /*playerPosition*/) override {}
void updateBounds(const osg::Vec3f& /*playerPosition*/) override {}
void updatePlayerPosition(const osg::Vec3f& /*playerPosition*/) override {};
void setUpdatesEnabled(bool /*enabled*/) override {}

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

Loading…
Cancel
Save