mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-21 06:53:53 +00:00
merge in master
This commit is contained in:
commit
0f43455dc3
203 changed files with 4113 additions and 2272 deletions
|
@ -218,6 +218,7 @@ Programmers
|
||||||
tlmullis
|
tlmullis
|
||||||
tri4ng1e
|
tri4ng1e
|
||||||
Thoronador
|
Thoronador
|
||||||
|
Tobias Tribble (zackogenic)
|
||||||
Tom Lowe (Vulpen)
|
Tom Lowe (Vulpen)
|
||||||
Tom Mason (wheybags)
|
Tom Mason (wheybags)
|
||||||
Torben Leif Carrington (TorbenC)
|
Torben Leif Carrington (TorbenC)
|
||||||
|
@ -235,7 +236,6 @@ Programmers
|
||||||
zelurker
|
zelurker
|
||||||
Noah Gooder
|
Noah Gooder
|
||||||
|
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
@ -249,6 +249,7 @@ Documentation
|
||||||
Joakim Berg (lysol90)
|
Joakim Berg (lysol90)
|
||||||
Ryan Tucker (Ravenwing)
|
Ryan Tucker (Ravenwing)
|
||||||
sir_herrbatka
|
sir_herrbatka
|
||||||
|
David Nagy (zuzaman)
|
||||||
|
|
||||||
Packagers
|
Packagers
|
||||||
---------
|
---------
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
Bug #4700: Editor: Incorrect command implementation
|
Bug #4700: Editor: Incorrect command implementation
|
||||||
Bug #4744: Invisible particles must still be processed
|
Bug #4744: Invisible particles must still be processed
|
||||||
Bug #4949: Incorrect particle lighting
|
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 #5088: Sky abruptly changes direction during certain weather transitions
|
||||||
Bug #5100: Persuasion doesn't always clamp the resulting disposition
|
Bug #5100: Persuasion doesn't always clamp the resulting disposition
|
||||||
Bug #5120: Scripted object spawning updates physics system
|
Bug #5120: Scripted object spawning updates physics system
|
||||||
|
@ -36,7 +37,6 @@
|
||||||
Bug #5788: Texture editing parses the selected indexes wrongly
|
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 #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 #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 #5863: GetEffect should return true after the player has teleported
|
||||||
Bug #5913: Failed assertion during Ritual of Trees quest
|
Bug #5913: Failed assertion during Ritual of Trees quest
|
||||||
Bug #5928: Glow in the Dahrk functionality used without mod installed
|
Bug #5928: Glow in the Dahrk functionality used without mod installed
|
||||||
|
@ -101,8 +101,10 @@
|
||||||
Bug #6519: Effects tooltips for ingredients work incorrectly
|
Bug #6519: Effects tooltips for ingredients work incorrectly
|
||||||
Bug #6523: Disintegrate Weapon is resisted by Resist Magicka instead of Sanctuary
|
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 #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 #890: OpenMW-CS: Column filtering
|
||||||
Feature #1465: "Reset" argument for AI functions
|
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 #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 #2780: A way to see current OpenMW version in the console
|
||||||
Feature #3616: Allow Zoom levels on the World Map
|
Feature #3616: Allow Zoom levels on the World Map
|
||||||
|
@ -128,7 +130,9 @@
|
||||||
Feature #6288: Preserve the "blocked" record flag for referenceable objects.
|
Feature #6288: Preserve the "blocked" record flag for referenceable objects.
|
||||||
Feature #6380: Commas are treated as whitespace in vanilla
|
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 #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 #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 #6201: Remove the "Note: No relevant classes found. No output generated" warnings
|
||||||
Task #6264: Remove the old classes in animation.cpp
|
Task #6264: Remove the old classes in animation.cpp
|
||||||
Task #6553: Simplify interpreter instruction registration
|
Task #6553: Simplify interpreter instruction registration
|
||||||
|
@ -270,6 +274,7 @@
|
||||||
Bug #6142: Groundcover plugins change cells flags
|
Bug #6142: Groundcover plugins change cells flags
|
||||||
Bug #6276: Deleted groundcover instances are not deleted in game
|
Bug #6276: Deleted groundcover instances are not deleted in game
|
||||||
Bug #6294: Game crashes with empty pathgrid
|
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 #390: 3rd person look "over the shoulder"
|
||||||
Feature #832: OpenMW-CS: Handle deleted references
|
Feature #832: OpenMW-CS: Handle deleted references
|
||||||
Feature #1536: Show more information about level on menu
|
Feature #1536: Show more information about level on menu
|
||||||
|
|
|
@ -484,9 +484,9 @@ int clone(Arguments& info)
|
||||||
if (i <= 0)
|
if (i <= 0)
|
||||||
break;
|
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);
|
record->save(esm);
|
||||||
if (typeName.toInt() == ESM::REC_CELL) {
|
if (typeName.toInt() == ESM::REC_CELL) {
|
||||||
|
@ -498,7 +498,7 @@ int clone(Arguments& info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
esm.endRecord(typeName.toString());
|
esm.endRecord(typeName);
|
||||||
|
|
||||||
saved++;
|
saved++;
|
||||||
int perc = recordCount == 0 ? 100 : (int)((saved / (float)recordCount)*100);
|
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")
|
("encoding", boost::program_options::value<std::string>()->default_value("win1252"), "encoding of the save file")
|
||||||
;
|
;
|
||||||
p_desc.add("mwsave", 1).add("output", 1);
|
p_desc.add("mwsave", 1).add("output", 1);
|
||||||
|
Files::ConfigurationManager::addCommonOptions(desc);
|
||||||
|
|
||||||
bpo::variables_map variables;
|
bpo::variables_map variables;
|
||||||
|
|
||||||
|
|
|
@ -414,57 +414,23 @@ bool Launcher::MainDialog::setupGameData()
|
||||||
|
|
||||||
bool Launcher::MainDialog::setupGraphicsSettings()
|
bool Launcher::MainDialog::setupGraphicsSettings()
|
||||||
{
|
{
|
||||||
// This method is almost a copy of OMW::Engine::loadSettings(). They should definitely
|
mEngineSettings.clear(); // Ensure to clear previous settings in case we had already loaded settings.
|
||||||
// remain consistent, and possibly be merged into a shared component. At the very least
|
try
|
||||||
// the filenames should be in the CfgMgr component.
|
{
|
||||||
|
boost::program_options::variables_map variables;
|
||||||
// Ensure to clear previous settings in case we had already loaded settings.
|
boost::program_options::options_description desc;
|
||||||
mEngineSettings.clear();
|
mCfgMgr.addCommonOptions(desc);
|
||||||
|
mCfgMgr.readConfiguration(variables, desc, true);
|
||||||
// Create the settings manager and load default settings file
|
mEngineSettings.load(mCfgMgr);
|
||||||
const std::string localDefault = (mCfgMgr.getLocalPath() / "defaults.bin").string();
|
return true;
|
||||||
const std::string globalDefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string();
|
}
|
||||||
std::string defaultPath;
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
// Prefer the defaults.bin in the current directory.
|
cfgError(tr("Error reading OpenMW configuration files"),
|
||||||
if (boost::filesystem::exists(localDefault))
|
tr("<br>The problem may be due to an incomplete installation of OpenMW.<br> \
|
||||||
defaultPath = localDefault;
|
Reinstalling OpenMW may resolve the problem.<br>") + e.what());
|
||||||
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;
|
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);
|
|
||||||
}
|
|
||||||
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()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Launcher::MainDialog::loadSettings()
|
void Launcher::MainDialog::loadSettings()
|
||||||
|
|
|
@ -84,27 +84,11 @@ namespace NavMeshTool
|
||||||
("process-interior-cells", bpo::value<bool>()->implicit_value(true)
|
("process-interior-cells", bpo::value<bool>()->implicit_value(true)
|
||||||
->default_value(false), "build navmesh for interior cells")
|
->default_value(false), "build navmesh for interior cells")
|
||||||
;
|
;
|
||||||
|
Files::ConfigurationManager::addCommonOptions(result);
|
||||||
|
|
||||||
return 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[])
|
int runNavMeshTool(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
bpo::options_description desc = makeOptionsDescription();
|
bpo::options_description desc = makeOptionsDescription();
|
||||||
|
@ -165,7 +149,7 @@ namespace NavMeshTool
|
||||||
VFS::registerArchives(&vfs, fileCollections, archives, true);
|
VFS::registerArchives(&vfs, fileCollections, archives, true);
|
||||||
|
|
||||||
Settings::Manager settings;
|
Settings::Manager settings;
|
||||||
loadSettings(config, settings);
|
settings.load(config);
|
||||||
|
|
||||||
const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game");
|
const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game");
|
||||||
|
|
||||||
|
|
|
@ -177,8 +177,8 @@ namespace NavMeshTool
|
||||||
|
|
||||||
DetourNavigator::getTilesPositions(
|
DetourNavigator::getTilesPositions(
|
||||||
DetourNavigator::makeTilesPositionsRange(
|
DetourNavigator::makeTilesPositionsRange(
|
||||||
Misc::Convert::toOsg(input->mAabb.m_min),
|
Misc::Convert::toOsgXY(input->mAabb.m_min),
|
||||||
Misc::Convert::toOsg(input->mAabb.m_max),
|
Misc::Convert::toOsgXY(input->mAabb.m_max),
|
||||||
settings.mRecast
|
settings.mRecast
|
||||||
),
|
),
|
||||||
[&] (const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); }
|
[&] (const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); }
|
||||||
|
|
|
@ -178,7 +178,7 @@ namespace NavMeshTool
|
||||||
static_cast<btScalar>(cellPosition.y() * ESM::Land::REAL_SIZE),
|
static_cast<btScalar>(cellPosition.y() * ESM::Land::REAL_SIZE),
|
||||||
minHeight
|
minHeight
|
||||||
);
|
);
|
||||||
aabb.m_min = btVector3(
|
aabb.m_max = btVector3(
|
||||||
static_cast<btScalar>((cellPosition.x() + 1) * ESM::Land::REAL_SIZE),
|
static_cast<btScalar>((cellPosition.x() + 1) * ESM::Land::REAL_SIZE),
|
||||||
static_cast<btScalar>((cellPosition.y() + 1) * ESM::Land::REAL_SIZE),
|
static_cast<btScalar>((cellPosition.y() + 1) * ESM::Land::REAL_SIZE),
|
||||||
maxHeight
|
maxHeight
|
||||||
|
@ -298,12 +298,14 @@ namespace NavMeshTool
|
||||||
const ObjectId objectId(++objectsCounter);
|
const ObjectId objectId(++objectsCounter);
|
||||||
const CollisionShape shape(object.getShapeInstance(), *object.getCollisionObject().getCollisionShape(), object.getObjectTransform());
|
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())
|
if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get())
|
||||||
{
|
{
|
||||||
const CollisionShape avoidShape(object.getShapeInstance(), *avoid, object.getObjectTransform());
|
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));
|
data.mObjects.emplace_back(std::move(object));
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include <components/debug/debugging.hpp>
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/fallback/validate.hpp>
|
#include <components/fallback/validate.hpp>
|
||||||
#include <components/misc/rng.hpp>
|
#include <components/misc/rng.hpp>
|
||||||
|
@ -20,7 +21,7 @@
|
||||||
using namespace Fallback;
|
using namespace Fallback;
|
||||||
|
|
||||||
CS::Editor::Editor (int argc, char **argv)
|
CS::Editor::Editor (int argc, char **argv)
|
||||||
: mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr),
|
: mConfigVariables(readConfiguration()), mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr),
|
||||||
mPid(""), mLock(), mMerge (mDocumentManager),
|
mPid(""), mLock(), mMerge (mDocumentManager),
|
||||||
mIpcServerName ("org.openmw.OpenCS"), mServer(nullptr), mClientSocket(nullptr)
|
mIpcServerName ("org.openmw.OpenCS"), mServer(nullptr), mClientSocket(nullptr)
|
||||||
{
|
{
|
||||||
|
@ -82,7 +83,7 @@ CS::Editor::~Editor ()
|
||||||
remove(mPid.string().c_str())); // ignore any error
|
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::variables_map variables;
|
||||||
boost::program_options::options_description desc("Syntax: openmw-cs <options>\nAllowed options");
|
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)")
|
->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)
|
("script-blacklist-use", boost::program_options::value<bool>()->implicit_value(true)
|
||||||
->default_value(true), "enable script blacklisting");
|
->default_value(true), "enable script blacklisting");
|
||||||
|
Files::ConfigurationManager::addCommonOptions(desc);
|
||||||
|
|
||||||
boost::program_options::notify(variables);
|
boost::program_options::notify(variables);
|
||||||
|
|
||||||
mCfgMgr.readConfiguration(variables, desc, false);
|
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);
|
Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap);
|
||||||
|
|
||||||
|
@ -360,7 +370,7 @@ int CS::Editor::run()
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ESM::ESMReader fileReader;
|
ESM::ESMReader fileReader;
|
||||||
ToUTF8::Utf8Encoder encoder = ToUTF8::calculateEncoding(mEncodingName);
|
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncodingName));
|
||||||
fileReader.setEncoder(&encoder);
|
fileReader.setEncoder(&encoder);
|
||||||
fileReader.open(mFileToLoad.string());
|
fileReader.open(mFileToLoad.string());
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ namespace CS
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
Files::ConfigurationManager mCfgMgr;
|
Files::ConfigurationManager mCfgMgr;
|
||||||
|
boost::program_options::variables_map mConfigVariables;
|
||||||
CSMPrefs::State mSettingsState;
|
CSMPrefs::State mSettingsState;
|
||||||
CSMDoc::DocumentManager mDocumentManager;
|
CSMDoc::DocumentManager mDocumentManager;
|
||||||
CSVDoc::StartupDialogue mStartup;
|
CSVDoc::StartupDialogue mStartup;
|
||||||
|
@ -58,6 +59,8 @@ namespace CS
|
||||||
Files::PathContainer mDataDirs;
|
Files::PathContainer mDataDirs;
|
||||||
std::string mEncodingName;
|
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);
|
std::pair<Files::PathContainer, std::vector<std::string> > readConfig(bool quiet=false);
|
||||||
///< \return data paths
|
///< \return data paths
|
||||||
|
|
||||||
|
|
|
@ -79,5 +79,5 @@ int runApplication(int argc, char *argv[])
|
||||||
|
|
||||||
int main(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()
|
void CSMPrefs::State::load()
|
||||||
{
|
{
|
||||||
// default settings file
|
mSettings.load(mConfigurationManager);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMPrefs::State::declare()
|
void CSMPrefs::State::declare()
|
||||||
|
|
|
@ -518,28 +518,6 @@ void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame)
|
||||||
mNewGame = 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)
|
void OMW::Engine::createWindow(Settings::Manager& settings)
|
||||||
{
|
{
|
||||||
int screen = settings.getInt("screen", "Video");
|
int screen = settings.getInt("screen", "Video");
|
||||||
|
@ -694,18 +672,18 @@ void OMW::Engine::setWindowIcon()
|
||||||
void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||||
{
|
{
|
||||||
mEnvironment.setStateManager (
|
mEnvironment.setStateManager (
|
||||||
new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles));
|
std::make_unique<MWState::StateManager> (mCfgMgr.getUserDataPath() / "saves", mContentFiles));
|
||||||
|
|
||||||
createWindow(settings);
|
createWindow(settings);
|
||||||
|
|
||||||
osg::ref_ptr<osg::Group> rootNode (new osg::Group);
|
osg::ref_ptr<osg::Group> rootNode (new osg::Group);
|
||||||
mViewer->setSceneData(rootNode);
|
mViewer->setSceneData(rootNode);
|
||||||
|
|
||||||
mVFS.reset(new VFS::Manager(mFSStrict));
|
mVFS = std::make_unique<VFS::Manager>(mFSStrict);
|
||||||
|
|
||||||
VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true);
|
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()->setUnRefImageDataAfterApply(false); // keep to Off for now to allow better state sharing
|
||||||
mResourceSystem->getSceneManager()->setFilterSettings(
|
mResourceSystem->getSceneManager()->setFilterSettings(
|
||||||
Settings::Manager::getString("texture mag filter", "General"),
|
Settings::Manager::getString("texture mag filter", "General"),
|
||||||
|
@ -734,8 +712,9 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||||
|
|
||||||
mViewer->addEventHandler(mScreenCaptureHandler);
|
mViewer->addEventHandler(mScreenCaptureHandler);
|
||||||
|
|
||||||
mLuaManager = new MWLua::LuaManager(mVFS.get(), (mResDir / "lua_libs").string());
|
auto luaMgr = std::make_unique<MWLua::LuaManager>(mVFS.get(), (mResDir / "lua_libs").string());
|
||||||
mEnvironment.setLuaManager(mLuaManager);
|
mLuaManager = luaMgr.get();
|
||||||
|
mEnvironment.setLuaManager(std::move(luaMgr));
|
||||||
|
|
||||||
// Create input and UI first to set up a bootstrapping environment for
|
// Create input and UI first to set up a bootstrapping environment for
|
||||||
// showing a loading screen and keeping the window responsive while doing so
|
// 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->setName("GUI Root");
|
||||||
guiRoot->setNodeMask(MWRender::Mask_GUI);
|
guiRoot->setNodeMask(MWRender::Mask_GUI);
|
||||||
rootNode->addChild(guiRoot);
|
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,
|
mCfgMgr.getLogPath().string() + std::string("/"), myguiResources,
|
||||||
mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts,
|
mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts,
|
||||||
Version::getOpenmwVersionDescription(mResDir.string()), mCfgMgr.getUserConfigPath().string(), shadersSupported);
|
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);
|
auto inputMgr = std::make_unique<MWInput::InputManager>(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
|
||||||
mEnvironment.setInputManager (input);
|
mEnvironment.setInputManager (std::move(inputMgr));
|
||||||
|
|
||||||
// Create sound system
|
// Create sound system
|
||||||
mEnvironment.setSoundManager (new MWSound::SoundManager(mVFS.get(), mUseSound));
|
mEnvironment.setSoundManager (std::make_unique<MWSound::SoundManager>(mVFS.get(), mUseSound));
|
||||||
|
|
||||||
if (!mSkipMenu)
|
if (!mSkipMenu)
|
||||||
{
|
{
|
||||||
const std::string& logo = Fallback::Map::getString("Movies_Company_Logo");
|
const std::string& logo = Fallback::Map::getString("Movies_Company_Logo");
|
||||||
if (!logo.empty())
|
if (!logo.empty())
|
||||||
window->playVideo(logo, true);
|
mEnvironment.getWindowManager()->playVideo(logo, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the world
|
// 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,
|
mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName,
|
||||||
mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()));
|
mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()));
|
||||||
mEnvironment.getWorld()->setupPlayer();
|
mEnvironment.getWorld()->setupPlayer();
|
||||||
|
|
||||||
window->setStore(mEnvironment.getWorld()->getStore());
|
windowMgrInternal->setStore(mEnvironment.getWorld()->getStore());
|
||||||
window->initUI();
|
windowMgrInternal->initUI();
|
||||||
|
|
||||||
//Load translation data
|
//Load translation data
|
||||||
mTranslationDataStorage.setEncoder(mEncoder);
|
mTranslationDataStorage.setEncoder(mEncoder);
|
||||||
|
@ -845,16 +826,15 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||||
mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full);
|
mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full);
|
||||||
mScriptContext->setExtensions (&mExtensions);
|
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>()));
|
mScriptBlacklistUse ? mScriptBlacklist : std::vector<std::string>()));
|
||||||
|
|
||||||
// Create game mechanics system
|
// Create game mechanics system
|
||||||
MWMechanics::MechanicsManager* mechanics = new MWMechanics::MechanicsManager;
|
mEnvironment.setMechanicsManager (std::make_unique<MWMechanics::MechanicsManager>());
|
||||||
mEnvironment.setMechanicsManager (mechanics);
|
|
||||||
|
|
||||||
// Create dialog system
|
// Create dialog system
|
||||||
mEnvironment.setJournal (new MWDialogue::Journal);
|
mEnvironment.setJournal (std::make_unique<MWDialogue::Journal>());
|
||||||
mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage));
|
mEnvironment.setDialogueManager (std::make_unique<MWDialogue::DialogueManager>(mExtensions, mTranslationDataStorage));
|
||||||
mEnvironment.setResourceSystem(mResourceSystem.get());
|
mEnvironment.setResourceSystem(mResourceSystem.get());
|
||||||
|
|
||||||
// scripts
|
// scripts
|
||||||
|
@ -975,8 +955,7 @@ void OMW::Engine::go()
|
||||||
|
|
||||||
// Load settings
|
// Load settings
|
||||||
Settings::Manager settings;
|
Settings::Manager settings;
|
||||||
std::string settingspath;
|
std::string settingspath = settings.load(mCfgMgr);
|
||||||
settingspath = loadSettings (settings);
|
|
||||||
|
|
||||||
MWClass::registerClasses();
|
MWClass::registerClasses();
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
||||||
typedef std::vector<std::string> StringsVector;
|
typedef std::vector<std::string> StringsVector;
|
||||||
|
|
||||||
bpo::options_description desc = OpenMW::makeOptionsDescription();
|
bpo::options_description desc = OpenMW::makeOptionsDescription();
|
||||||
|
Files::ConfigurationManager::addCommonOptions(desc);
|
||||||
|
|
||||||
bpo::variables_map variables;
|
bpo::variables_map variables;
|
||||||
|
|
||||||
|
@ -61,9 +62,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc);
|
|
||||||
cfgMgr.readConfiguration(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());
|
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::MaybeQuotedPath>().string());
|
||||||
Log(Debug::Info) << v.describe();
|
Log(Debug::Info) << v.describe();
|
||||||
|
@ -230,7 +231,7 @@ extern "C" int SDL_main(int argc, char**argv)
|
||||||
int main(int argc, char**argv)
|
int main(int argc, char**argv)
|
||||||
#endif
|
#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.
|
// 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 *MWBase::Environment::sThis = nullptr;
|
||||||
|
|
||||||
MWBase::Environment::Environment()
|
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;
|
sThis = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
MWBase::Environment::~Environment()
|
MWBase::Environment::~Environment()
|
||||||
{
|
{
|
||||||
cleanup();
|
|
||||||
sThis = nullptr;
|
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)
|
void MWBase::Environment::setResourceSystem (Resource::ResourceSystem *resourceSystem)
|
||||||
|
@ -105,61 +101,61 @@ float MWBase::Environment::getFrameRateLimit() const
|
||||||
MWBase::World *MWBase::Environment::getWorld() const
|
MWBase::World *MWBase::Environment::getWorld() const
|
||||||
{
|
{
|
||||||
assert (mWorld);
|
assert (mWorld);
|
||||||
return mWorld;
|
return mWorld.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
MWBase::SoundManager *MWBase::Environment::getSoundManager() const
|
MWBase::SoundManager *MWBase::Environment::getSoundManager() const
|
||||||
{
|
{
|
||||||
assert (mSoundManager);
|
assert (mSoundManager);
|
||||||
return mSoundManager;
|
return mSoundManager.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
MWBase::ScriptManager *MWBase::Environment::getScriptManager() const
|
MWBase::ScriptManager *MWBase::Environment::getScriptManager() const
|
||||||
{
|
{
|
||||||
assert (mScriptManager);
|
assert (mScriptManager);
|
||||||
return mScriptManager;
|
return mScriptManager.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
MWBase::WindowManager *MWBase::Environment::getWindowManager() const
|
MWBase::WindowManager *MWBase::Environment::getWindowManager() const
|
||||||
{
|
{
|
||||||
assert (mWindowManager);
|
assert (mWindowManager);
|
||||||
return mWindowManager;
|
return mWindowManager.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
MWBase::MechanicsManager *MWBase::Environment::getMechanicsManager() const
|
MWBase::MechanicsManager *MWBase::Environment::getMechanicsManager() const
|
||||||
{
|
{
|
||||||
assert (mMechanicsManager);
|
assert (mMechanicsManager);
|
||||||
return mMechanicsManager;
|
return mMechanicsManager.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
MWBase::DialogueManager *MWBase::Environment::getDialogueManager() const
|
MWBase::DialogueManager *MWBase::Environment::getDialogueManager() const
|
||||||
{
|
{
|
||||||
assert (mDialogueManager);
|
assert (mDialogueManager);
|
||||||
return mDialogueManager;
|
return mDialogueManager.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
MWBase::Journal *MWBase::Environment::getJournal() const
|
MWBase::Journal *MWBase::Environment::getJournal() const
|
||||||
{
|
{
|
||||||
assert (mJournal);
|
assert (mJournal);
|
||||||
return mJournal;
|
return mJournal.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
MWBase::InputManager *MWBase::Environment::getInputManager() const
|
MWBase::InputManager *MWBase::Environment::getInputManager() const
|
||||||
{
|
{
|
||||||
assert (mInputManager);
|
assert (mInputManager);
|
||||||
return mInputManager;
|
return mInputManager.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
MWBase::StateManager *MWBase::Environment::getStateManager() const
|
MWBase::StateManager *MWBase::Environment::getStateManager() const
|
||||||
{
|
{
|
||||||
assert (mStateManager);
|
assert (mStateManager);
|
||||||
return mStateManager;
|
return mStateManager.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
MWBase::LuaManager *MWBase::Environment::getLuaManager() const
|
MWBase::LuaManager *MWBase::Environment::getLuaManager() const
|
||||||
{
|
{
|
||||||
assert (mLuaManager);
|
assert (mLuaManager);
|
||||||
return mLuaManager;
|
return mLuaManager.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Resource::ResourceSystem *MWBase::Environment::getResourceSystem() const
|
Resource::ResourceSystem *MWBase::Environment::getResourceSystem() const
|
||||||
|
@ -174,35 +170,17 @@ float MWBase::Environment::getFrameDuration() const
|
||||||
|
|
||||||
void MWBase::Environment::cleanup()
|
void MWBase::Environment::cleanup()
|
||||||
{
|
{
|
||||||
delete mMechanicsManager;
|
mMechanicsManager.reset();
|
||||||
mMechanicsManager = nullptr;
|
mDialogueManager.reset();
|
||||||
|
mJournal.reset();
|
||||||
delete mDialogueManager;
|
mScriptManager.reset();
|
||||||
mDialogueManager = nullptr;
|
mWindowManager.reset();
|
||||||
|
mWorld.reset();
|
||||||
delete mJournal;
|
mSoundManager.reset();
|
||||||
mJournal = nullptr;
|
mInputManager.reset();
|
||||||
|
mStateManager.reset();
|
||||||
delete mScriptManager;
|
mLuaManager.reset();
|
||||||
mScriptManager = nullptr;
|
mResourceSystem = 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MWBase::Environment& MWBase::Environment::get()
|
const MWBase::Environment& MWBase::Environment::get()
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef GAME_BASE_ENVIRONMENT_H
|
#ifndef GAME_BASE_ENVIRONMENT_H
|
||||||
#define GAME_BASE_ENVIRONMENT_H
|
#define GAME_BASE_ENVIRONMENT_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace osg
|
namespace osg
|
||||||
{
|
{
|
||||||
class Stats;
|
class Stats;
|
||||||
|
@ -28,25 +30,23 @@ namespace MWBase
|
||||||
///
|
///
|
||||||
/// This class allows each mw-subsystem to access any others subsystem's top-level manager class.
|
/// 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
|
class Environment
|
||||||
{
|
{
|
||||||
static Environment *sThis;
|
static Environment *sThis;
|
||||||
|
|
||||||
World *mWorld;
|
std::unique_ptr<World> mWorld;
|
||||||
SoundManager *mSoundManager;
|
std::unique_ptr<SoundManager> mSoundManager;
|
||||||
ScriptManager *mScriptManager;
|
std::unique_ptr<ScriptManager> mScriptManager;
|
||||||
WindowManager *mWindowManager;
|
std::unique_ptr<WindowManager> mWindowManager;
|
||||||
MechanicsManager *mMechanicsManager;
|
std::unique_ptr<MechanicsManager> mMechanicsManager;
|
||||||
DialogueManager *mDialogueManager;
|
std::unique_ptr<DialogueManager> mDialogueManager;
|
||||||
Journal *mJournal;
|
std::unique_ptr<Journal> mJournal;
|
||||||
InputManager *mInputManager;
|
std::unique_ptr<InputManager> mInputManager;
|
||||||
StateManager *mStateManager;
|
std::unique_ptr<StateManager> mStateManager;
|
||||||
LuaManager *mLuaManager;
|
std::unique_ptr<LuaManager> mLuaManager;
|
||||||
Resource::ResourceSystem *mResourceSystem;
|
Resource::ResourceSystem* mResourceSystem{};
|
||||||
float mFrameDuration;
|
float mFrameDuration{};
|
||||||
float mFrameRateLimit;
|
float mFrameRateLimit{};
|
||||||
|
|
||||||
Environment (const Environment&);
|
Environment (const Environment&);
|
||||||
///< not implemented
|
///< not implemented
|
||||||
|
@ -60,25 +60,25 @@ namespace MWBase
|
||||||
|
|
||||||
~Environment();
|
~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);
|
void setResourceSystem (Resource::ResourceSystem *resourceSystem);
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ namespace MWBase
|
||||||
virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0;
|
virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0;
|
||||||
virtual void objectRemovedFromScene(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 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
|
// TODO: notify LuaManager about other events
|
||||||
// virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object,
|
// 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;
|
// const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) = 0;
|
||||||
|
@ -56,7 +57,9 @@ namespace MWBase
|
||||||
bool mRun = false;
|
bool mRun = false;
|
||||||
float mMovement = 0;
|
float mMovement = 0;
|
||||||
float mSideMovement = 0;
|
float mSideMovement = 0;
|
||||||
float mTurn = 0;
|
float mPitchChange = 0;
|
||||||
|
float mYawChange = 0;
|
||||||
|
int mUse = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual ActorControls* getActorControls(const MWWorld::Ptr&) const = 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:
|
case SelectWrapper::Function_PcHealthPercent:
|
||||||
{
|
{
|
||||||
MWWorld::Ptr player = MWMechanics::getPlayer();
|
MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||||
|
return select.selectCompare(static_cast<int>(player.getClass().getCreatureStats(player).getHealth().getRatio() * 100));
|
||||||
float ratio = player.getClass().getCreatureStats (player).getHealth().getCurrent() /
|
|
||||||
player.getClass().getCreatureStats (player).getHealth().getModified();
|
|
||||||
|
|
||||||
return select.selectCompare (static_cast<int>(ratio*100));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case SelectWrapper::Function_PcDynamicStat:
|
case SelectWrapper::Function_PcDynamicStat:
|
||||||
|
@ -276,10 +272,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
|
||||||
|
|
||||||
case SelectWrapper::Function_HealthPercent:
|
case SelectWrapper::Function_HealthPercent:
|
||||||
{
|
{
|
||||||
float ratio = mActor.getClass().getCreatureStats (mActor).getHealth().getCurrent() /
|
return select.selectCompare(static_cast<int>(mActor.getClass().getCreatureStats(mActor).getHealth().getRatio() * 100));
|
||||||
mActor.getClass().getCreatureStats (mActor).getHealth().getModified();
|
|
||||||
|
|
||||||
return select.selectCompare (static_cast<int>(ratio*100));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
#include <components/esm3/queststate.hpp>
|
#include <components/esm3/queststate.hpp>
|
||||||
#include <components/esm3/journalentry.hpp>
|
#include <components/esm3/journalentry.hpp>
|
||||||
|
|
||||||
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
|
|
||||||
|
@ -93,7 +95,16 @@ namespace MWDialogue
|
||||||
StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index, actor);
|
StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index, actor);
|
||||||
|
|
||||||
Quest& quest = getQuest (id);
|
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
|
// there is no need to show empty entries in journal
|
||||||
if (!entry.getText().empty())
|
if (!entry.getText().empty())
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "quest.hpp"
|
#include "quest.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <components/esm3/queststate.hpp>
|
#include <components/esm3/queststate.hpp>
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
@ -50,42 +52,33 @@ namespace MWDialogue
|
||||||
return mFinished;
|
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 =
|
const ESM::Dialogue *dialogue =
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (entry.mTopic);
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (entry.mTopic);
|
||||||
|
|
||||||
for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin());
|
auto info = std::find_if(dialogue->mInfo.begin(), dialogue->mInfo.end(), [&](const auto& info) { return info.mId == entry.mInfoId; });
|
||||||
iter!=dialogue->mInfo.end(); ++iter)
|
|
||||||
if (iter->mId == entry.mInfoId)
|
|
||||||
{
|
|
||||||
index = iter->mData.mJournalIndex;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index==-1)
|
if (info == dialogue->mInfo.end() || info->mData.mJournalIndex == -1)
|
||||||
throw std::runtime_error ("unknown journal entry for topic " + mTopic);
|
throw std::runtime_error ("unknown journal entry for topic " + mTopic);
|
||||||
|
|
||||||
for (auto &info : dialogue->mInfo)
|
if (info->mQuestStatus == ESM::DialInfo::QS_Finished || info->mQuestStatus == ESM::DialInfo::QS_Restart)
|
||||||
{
|
mFinished = info->mQuestStatus == ESM::DialInfo::QS_Finished;
|
||||||
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 (index > mIndex)
|
if (info->mData.mJournalIndex > mIndex)
|
||||||
mIndex = index;
|
mIndex = info->mData.mJournalIndex;
|
||||||
|
|
||||||
for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter)
|
for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter)
|
||||||
if (iter->mInfoId==entry.mInfoId)
|
if (iter->mInfoId==entry.mInfoId)
|
||||||
return;
|
return info->mQuestStatus == ESM::DialInfo::QS_Restart;
|
||||||
|
|
||||||
mEntries.push_back (entry); // we want slicing here
|
mEntries.push_back (entry); // we want slicing here
|
||||||
|
return info->mQuestStatus == ESM::DialInfo::QS_Restart;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Quest::write (ESM::QuestState& state) const
|
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.
|
///< Calling this function with a non-existent index will throw an exception.
|
||||||
|
|
||||||
bool isFinished() const;
|
bool isFinished() const;
|
||||||
|
void setFinished(bool finished);
|
||||||
|
|
||||||
void addEntry (const JournalEntry& entry) override;
|
bool addEntry (const JournalEntry& entry) override;
|
||||||
///< Add entry and adjust index accordingly.
|
///< 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.
|
/// \note Redundant entries are ignored, but the index is still adjusted.
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace MWDialogue
|
||||||
Topic::~Topic()
|
Topic::~Topic()
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void Topic::addEntry (const JournalEntry& entry)
|
bool Topic::addEntry (const JournalEntry& entry)
|
||||||
{
|
{
|
||||||
if (entry.mTopic!=mTopic)
|
if (entry.mTopic!=mTopic)
|
||||||
throw std::runtime_error ("topic does not match: " + 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)
|
for (Topic::TEntryIter it = mEntries.begin(); it != mEntries.end(); ++it)
|
||||||
{
|
{
|
||||||
if (it->mInfoId == entry.mInfoId)
|
if (it->mInfoId == entry.mInfoId)
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mEntries.push_back (entry); // we want slicing here
|
mEntries.push_back (entry); // we want slicing here
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Topic::insertEntry (const ESM::JournalEntry& entry)
|
void Topic::insertEntry (const ESM::JournalEntry& entry)
|
||||||
|
|
|
@ -35,7 +35,7 @@ namespace MWDialogue
|
||||||
|
|
||||||
virtual ~Topic();
|
virtual ~Topic();
|
||||||
|
|
||||||
virtual void addEntry (const JournalEntry& entry);
|
virtual bool addEntry (const JournalEntry& entry);
|
||||||
///< Add entry
|
///< Add entry
|
||||||
///
|
///
|
||||||
/// \note Redundant entries are ignored.
|
/// \note Redundant entries are ignored.
|
||||||
|
|
|
@ -608,7 +608,7 @@ namespace MWGui
|
||||||
mEnemyHealth->setProgressRange(100);
|
mEnemyHealth->setProgressRange(100);
|
||||||
// Health is usually cast to int before displaying. Actors die whenever they are < 1 health.
|
// 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 :)
|
// 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();
|
static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fNPCHealthBarFade")->mValue.getFloat();
|
||||||
if (fNPCHealthBarFade > 0.f)
|
if (fNPCHealthBarFade > 0.f)
|
||||||
|
|
|
@ -399,7 +399,7 @@ namespace MWGui
|
||||||
skillWidget = mSkillList->createWidget<Widgets::MWSkill>("MW_StatNameValue", coord1, MyGUI::Align::Default,
|
skillWidget = mSkillList->createWidget<Widgets::MWSkill>("MW_StatNameValue", coord1, MyGUI::Align::Default,
|
||||||
std::string("Skill") + MyGUI::utility::toString(i));
|
std::string("Skill") + MyGUI::utility::toString(i));
|
||||||
skillWidget->setSkillNumber(skillId);
|
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);
|
ToolTips::createSkillToolTip(skillWidget, skillId);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
#include "settingswindow.hpp"
|
#include "settingswindow.hpp"
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <numeric>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
#include <MyGUI_ScrollBar.h>
|
#include <MyGUI_ScrollBar.h>
|
||||||
#include <MyGUI_Window.h>
|
#include <MyGUI_Window.h>
|
||||||
#include <MyGUI_ComboBox.h>
|
#include <MyGUI_ComboBox.h>
|
||||||
#include <MyGUI_ScrollView.h>
|
#include <MyGUI_ScrollView.h>
|
||||||
#include <MyGUI_Gui.h>
|
#include <MyGUI_Gui.h>
|
||||||
#include <MyGUI_TabControl.h>
|
#include <MyGUI_TabControl.h>
|
||||||
|
#include <MyGUI_TabItem.h>
|
||||||
|
|
||||||
#include <SDL_video.h>
|
#include <SDL_video.h>
|
||||||
|
|
||||||
#include <iomanip>
|
|
||||||
#include <numeric>
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/misc/stringops.hpp>
|
#include <components/misc/stringops.hpp>
|
||||||
#include <components/misc/constants.hpp>
|
#include <components/misc/constants.hpp>
|
||||||
|
@ -21,6 +23,7 @@
|
||||||
#include <components/resource/resourcesystem.hpp>
|
#include <components/resource/resourcesystem.hpp>
|
||||||
#include <components/resource/scenemanager.hpp>
|
#include <components/resource/scenemanager.hpp>
|
||||||
#include <components/sceneutil/lightmanager.hpp>
|
#include <components/sceneutil/lightmanager.hpp>
|
||||||
|
#include <components/lua_ui/scriptsettings.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
@ -33,7 +36,6 @@
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
std::string textureMipmappingToStr(const std::string& val)
|
std::string textureMipmappingToStr(const std::string& val)
|
||||||
{
|
{
|
||||||
if (val == "linear") return "Trilinear";
|
if (val == "linear") return "Trilinear";
|
||||||
|
@ -205,9 +207,9 @@ namespace MWGui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsWindow::SettingsWindow() :
|
SettingsWindow::SettingsWindow() : WindowBase("openmw_settings_window.layout")
|
||||||
WindowBase("openmw_settings_window.layout"),
|
, mKeyboardMode(true)
|
||||||
mKeyboardMode(true)
|
, mCurrentPage(-1)
|
||||||
{
|
{
|
||||||
bool terrain = Settings::Manager::getBool("distant terrain", "Terrain");
|
bool terrain = Settings::Manager::getBool("distant terrain", "Terrain");
|
||||||
const std::string widgetName = terrain ? "RenderingDistanceSlider" : "LargeRenderingDistanceSlider";
|
const std::string widgetName = terrain ? "RenderingDistanceSlider" : "LargeRenderingDistanceSlider";
|
||||||
|
@ -236,6 +238,12 @@ namespace MWGui
|
||||||
getWidget(mLightingMethodButton, "LightingMethodButton");
|
getWidget(mLightingMethodButton, "LightingMethodButton");
|
||||||
getWidget(mLightsResetButton, "LightsResetButton");
|
getWidget(mLightsResetButton, "LightsResetButton");
|
||||||
getWidget(mMaxLights, "MaxLights");
|
getWidget(mMaxLights, "MaxLights");
|
||||||
|
getWidget(mScriptFilter, "ScriptFilter");
|
||||||
|
getWidget(mScriptList, "ScriptList");
|
||||||
|
getWidget(mScriptBox, "ScriptBox");
|
||||||
|
getWidget(mScriptView, "ScriptView");
|
||||||
|
getWidget(mScriptAdapter, "ScriptAdapter");
|
||||||
|
getWidget(mScriptDisabled, "ScriptDisabled");
|
||||||
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
// hide gamma controls since it currently does not work under Linux
|
// hide gamma controls since it currently does not work under Linux
|
||||||
|
@ -321,6 +329,9 @@ namespace MWGui
|
||||||
|
|
||||||
mKeyboardSwitch->setStateSelected(true);
|
mKeyboardSwitch->setStateSelected(true);
|
||||||
mControllerSwitch->setStateSelected(false);
|
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*/)
|
void SettingsWindow::onTabChanged(MyGUI::TabControl* /*_sender*/, size_t /*index*/)
|
||||||
|
@ -699,6 +710,118 @@ namespace MWGui
|
||||||
mControlsBox->setVisibleVScroll(true);
|
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)
|
void SettingsWindow::onRebindAction(MyGUI::Widget* _sender)
|
||||||
{
|
{
|
||||||
int actionId = *_sender->getUserData<int>();
|
int actionId = *_sender->getUserData<int>();
|
||||||
|
@ -744,12 +867,15 @@ namespace MWGui
|
||||||
updateControlsBox();
|
updateControlsBox();
|
||||||
updateLightSettings();
|
updateLightSettings();
|
||||||
resetScrollbars();
|
resetScrollbars();
|
||||||
|
renderScriptSettings();
|
||||||
|
resizeScriptSettings();
|
||||||
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton);
|
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsWindow::onWindowResize(MyGUI::Window *_sender)
|
void SettingsWindow::onWindowResize(MyGUI::Window *_sender)
|
||||||
{
|
{
|
||||||
layoutControlsBox();
|
layoutControlsBox();
|
||||||
|
resizeScriptSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsWindow::computeMinimumWindowSize()
|
void SettingsWindow::computeMinimumWindowSize()
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef MWGUI_SETTINGS_H
|
#ifndef MWGUI_SETTINGS_H
|
||||||
#define MWGUI_SETTINGS_H
|
#define MWGUI_SETTINGS_H
|
||||||
|
|
||||||
|
#include <components/lua_ui/adapter.hpp>
|
||||||
|
|
||||||
#include "windowbase.hpp"
|
#include "windowbase.hpp"
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
|
@ -44,6 +46,14 @@ namespace MWGui
|
||||||
MyGUI::Button* mControllerSwitch;
|
MyGUI::Button* mControllerSwitch;
|
||||||
bool mKeyboardMode; //if true, setting up the keyboard. Otherwise, it's controller
|
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 onTabChanged(MyGUI::TabControl* _sender, size_t index);
|
||||||
void onOkButtonClicked(MyGUI::Widget* _sender);
|
void onOkButtonClicked(MyGUI::Widget* _sender);
|
||||||
void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos);
|
void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos);
|
||||||
|
@ -71,12 +81,17 @@ namespace MWGui
|
||||||
|
|
||||||
void onWindowResize(MyGUI::Window* _sender);
|
void onWindowResize(MyGUI::Window* _sender);
|
||||||
|
|
||||||
|
void onScriptFilterChange(MyGUI::EditBox*);
|
||||||
|
void onScriptListSelection(MyGUI::ListBox*, size_t index);
|
||||||
|
|
||||||
void apply();
|
void apply();
|
||||||
|
|
||||||
void configureWidgets(MyGUI::Widget* widget, bool init);
|
void configureWidgets(MyGUI::Widget* widget, bool init);
|
||||||
void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value);
|
void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value);
|
||||||
|
|
||||||
void layoutControlsBox();
|
void layoutControlsBox();
|
||||||
|
void resizeScriptSettings();
|
||||||
|
void renderScriptSettings();
|
||||||
|
|
||||||
void computeMinimumWindowSize();
|
void computeMinimumWindowSize();
|
||||||
|
|
||||||
|
|
|
@ -179,7 +179,7 @@ namespace MWGui
|
||||||
void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value)
|
void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value)
|
||||||
{
|
{
|
||||||
int current = static_cast<int>(value.getCurrent());
|
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
|
// Fatigue can be negative
|
||||||
if (id != "FBar")
|
if (id != "FBar")
|
||||||
|
|
|
@ -181,8 +181,6 @@ namespace MWGui
|
||||||
public:
|
public:
|
||||||
MWSpell();
|
MWSpell();
|
||||||
|
|
||||||
typedef MWMechanics::Stat<int> SpellValue;
|
|
||||||
|
|
||||||
void setSpellId(const std::string &id);
|
void setSpellId(const std::string &id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -215,8 +213,6 @@ namespace MWGui
|
||||||
public:
|
public:
|
||||||
MWEffectList();
|
MWEffectList();
|
||||||
|
|
||||||
typedef MWMechanics::Stat<int> EnchantmentValue;
|
|
||||||
|
|
||||||
enum EffectFlags
|
enum EffectFlags
|
||||||
{
|
{
|
||||||
EF_NoTarget = 0x01, // potions have no target (target is always the player)
|
EF_NoTarget = 0x01, // potions have no target (target is always the player)
|
||||||
|
|
|
@ -6,10 +6,13 @@
|
||||||
#include <components/lua/luastate.hpp>
|
#include <components/lua/luastate.hpp>
|
||||||
#include <components/settings/settings.hpp>
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include <apps/openmw/mwbase/luamanager.hpp>
|
||||||
#include "../mwworld/class.hpp"
|
|
||||||
#include "../mwworld/inventorystore.hpp"
|
#include <apps/openmw/mwworld/action.hpp>
|
||||||
#include "../mwworld/player.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
|
namespace MWLua
|
||||||
{
|
{
|
||||||
|
@ -145,4 +148,24 @@ namespace MWLua
|
||||||
tryEquipToSlot(anySlot, item);
|
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;
|
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
|
#endif // MWLUA_ACTIONS_H
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
#include "localscripts.hpp"
|
#include "localscripts.hpp"
|
||||||
|
|
||||||
|
#include <components/esm3/loadcell.hpp>
|
||||||
|
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwmechanics/aisequence.hpp"
|
#include "../mwmechanics/aisequence.hpp"
|
||||||
#include "../mwmechanics/aicombat.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"
|
#include "luamanagerimp.hpp"
|
||||||
|
|
||||||
|
@ -27,9 +35,11 @@ namespace MWLua
|
||||||
[](ActorControls& c, const TYPE& v) { c.FIELD = v; c.mChanged = true; })
|
[](ActorControls& c, const TYPE& v) { c.FIELD = v; c.mChanged = true; })
|
||||||
controls["movement"] = CONTROL(float, mMovement);
|
controls["movement"] = CONTROL(float, mMovement);
|
||||||
controls["sideMovement"] = CONTROL(float, mSideMovement);
|
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["run"] = CONTROL(bool, mRun);
|
||||||
controls["jump"] = CONTROL(bool, mJump);
|
controls["jump"] = CONTROL(bool, mJump);
|
||||||
|
controls["use"] = CONTROL(int, mUse);
|
||||||
#undef CONTROL
|
#undef CONTROL
|
||||||
|
|
||||||
sol::usertype<SelfObject> selfAPI =
|
sol::usertype<SelfObject> selfAPI =
|
||||||
|
@ -58,35 +68,110 @@ namespace MWLua
|
||||||
}
|
}
|
||||||
context.mLuaManager->addAction(std::make_unique<SetEquipmentAction>(context.mLua, obj.id(), std::move(eqp)));
|
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
|
||||||
{
|
{
|
||||||
const MWWorld::Ptr& ptr = self.ptr();
|
switch (p.getTypeId())
|
||||||
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
|
{
|
||||||
MWWorld::Ptr target;
|
case MWMechanics::AiPackageTypeId::Wander: return "Wander";
|
||||||
if (ai.getCombatTarget(target))
|
case MWMechanics::AiPackageTypeId::Travel: return "Travel";
|
||||||
return LObject(getId(target), worldView->getObjectRegistry());
|
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
|
else
|
||||||
return {};
|
return LObject(getId(target), worldView->getObjectRegistry());
|
||||||
};
|
});
|
||||||
selfAPI["stopCombat"] = [](SelfObject& self)
|
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();
|
const MWWorld::Ptr& ptr = self.ptr();
|
||||||
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
|
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
|
||||||
ai.stopCombat();
|
if (ai.isEmpty())
|
||||||
|
return sol::nullopt;
|
||||||
|
else
|
||||||
|
return *ai.begin();
|
||||||
};
|
};
|
||||||
selfAPI["startCombat"] = [](SelfObject& self, const LObject& target)
|
selfAPI["_iterateAndFilterAiSequence"] = [](SelfObject& self, sol::function callback)
|
||||||
|
{
|
||||||
|
const MWWorld::Ptr& ptr = self.ptr();
|
||||||
|
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
|
||||||
|
|
||||||
|
ai.erasePackagesIf([&](auto& entry)
|
||||||
|
{
|
||||||
|
bool keep = LuaUtil::call(callback, entry).template get<bool>();
|
||||||
|
return !keep;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
selfAPI["_startAiCombat"] = [](SelfObject& self, const LObject& target)
|
||||||
{
|
{
|
||||||
const MWWorld::Ptr& ptr = self.ptr();
|
const MWWorld::Ptr& ptr = self.ptr();
|
||||||
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
|
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
|
||||||
ai.stack(MWMechanics::AiCombat(target.ptr()), ptr);
|
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)
|
LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj, ESM::LuaScriptCfg::Flags autoStartMode)
|
||||||
: LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id()), autoStartMode), mData(obj)
|
: LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id()), autoStartMode), mData(obj)
|
||||||
{
|
{
|
||||||
this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData));
|
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)
|
void LocalScripts::receiveEngineEvent(const EngineEvent& event)
|
||||||
|
@ -104,6 +189,10 @@ namespace MWLua
|
||||||
mData.mIsActive = false;
|
mData.mIsActive = false;
|
||||||
callEngineHandlers(mOnInactiveHandlers);
|
callEngineHandlers(mOnInactiveHandlers);
|
||||||
}
|
}
|
||||||
|
else if constexpr (std::is_same_v<EventT, OnActivated>)
|
||||||
|
{
|
||||||
|
callEngineHandlers(mOnActivatedHandlers, arg.mActivatingActor);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
static_assert(std::is_same_v<EventT, OnConsume>);
|
static_assert(std::is_same_v<EventT, OnConsume>);
|
||||||
|
|
|
@ -33,11 +33,15 @@ namespace MWLua
|
||||||
|
|
||||||
struct OnActive {};
|
struct OnActive {};
|
||||||
struct OnInactive {};
|
struct OnInactive {};
|
||||||
|
struct OnActivated
|
||||||
|
{
|
||||||
|
LObject mActivatingActor;
|
||||||
|
};
|
||||||
struct OnConsume
|
struct OnConsume
|
||||||
{
|
{
|
||||||
std::string mRecordId;
|
std::string mRecordId;
|
||||||
};
|
};
|
||||||
using EngineEvent = std::variant<OnActive, OnInactive, OnConsume>;
|
using EngineEvent = std::variant<OnActive, OnInactive, OnConsume, OnActivated>;
|
||||||
|
|
||||||
void receiveEngineEvent(const EngineEvent&);
|
void receiveEngineEvent(const EngineEvent&);
|
||||||
|
|
||||||
|
@ -48,6 +52,7 @@ namespace MWLua
|
||||||
EngineHandlerList mOnActiveHandlers{"onActive"};
|
EngineHandlerList mOnActiveHandlers{"onActive"};
|
||||||
EngineHandlerList mOnInactiveHandlers{"onInactive"};
|
EngineHandlerList mOnInactiveHandlers{"onInactive"};
|
||||||
EngineHandlerList mOnConsumeHandlers{"onConsume"};
|
EngineHandlerList mOnConsumeHandlers{"onConsume"};
|
||||||
|
EngineHandlerList mOnActivatedHandlers{"onActivated"};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,10 @@
|
||||||
namespace MWLua
|
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);
|
sol::table res(lua.sol(), sol::create);
|
||||||
for (const std::string& v : values)
|
for (const std::string_view& v : values)
|
||||||
res[v] = v;
|
res[v] = v;
|
||||||
return LuaUtil::makeReadOnly(res);
|
return LuaUtil::makeReadOnly(res);
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ namespace MWLua
|
||||||
{
|
{
|
||||||
auto* lua = context.mLua;
|
auto* lua = context.mLua;
|
||||||
sol::table api(lua->sol(), sol::create);
|
sol::table api(lua->sol(), sol::create);
|
||||||
api["API_REVISION"] = 14;
|
api["API_REVISION"] = 17;
|
||||||
api["quit"] = [lua]()
|
api["quit"] = [lua]()
|
||||||
{
|
{
|
||||||
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
|
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
|
||||||
|
@ -62,8 +62,11 @@ namespace MWLua
|
||||||
addTimeBindings(api, context, false);
|
addTimeBindings(api, context, false);
|
||||||
api["OBJECT_TYPE"] = definitionList(*lua,
|
api["OBJECT_TYPE"] = definitionList(*lua,
|
||||||
{
|
{
|
||||||
"Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient",
|
ObjectTypeName::Activator, ObjectTypeName::Armor, ObjectTypeName::Book, ObjectTypeName::Clothing,
|
||||||
"Light", "Miscellaneous", "NPC", "Player", "Potion", "Static", "Weapon"
|
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>({
|
api["EQUIPMENT_SLOT"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, int>({
|
||||||
{"Helmet", MWWorld::InventoryStore::Slot_Helmet},
|
{"Helmet", MWWorld::InventoryStore::Slot_Helmet},
|
||||||
|
|
|
@ -114,6 +114,7 @@ namespace MWLua
|
||||||
|
|
||||||
void LuaManager::update()
|
void LuaManager::update()
|
||||||
{
|
{
|
||||||
|
static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua");
|
||||||
if (mPlayer.isEmpty())
|
if (mPlayer.isEmpty())
|
||||||
return; // The game is not started yet.
|
return; // The game is not started yet.
|
||||||
|
|
||||||
|
@ -172,7 +173,8 @@ namespace MWLua
|
||||||
LObject obj(e.mDest, objectRegistry);
|
LObject obj(e.mDest, objectRegistry);
|
||||||
if (!obj.isValid())
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts();
|
LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts();
|
||||||
|
@ -204,7 +206,7 @@ namespace MWLua
|
||||||
GObject obj(id, objectRegistry);
|
GObject obj(id, objectRegistry);
|
||||||
if (obj.isValid())
|
if (obj.isValid())
|
||||||
mGlobalScripts.actorActive(obj);
|
mGlobalScripts.actorActive(obj);
|
||||||
else
|
else if (luaDebug)
|
||||||
Log(Debug::Verbose) << "Can not call onActorActive engine handler: object" << idToString(id) << " is already removed";
|
Log(Debug::Verbose) << "Can not call onActorActive engine handler: object" << idToString(id) << " is already removed";
|
||||||
}
|
}
|
||||||
mActorAddedEvents.clear();
|
mActorAddedEvents.clear();
|
||||||
|
@ -352,6 +354,11 @@ namespace MWLua
|
||||||
mLocalEngineEvents.push_back({getId(toPtr), LocalScripts::OnConsume{std::string(recordId)}});
|
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
|
MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const
|
||||||
{
|
{
|
||||||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||||
|
|
|
@ -49,6 +49,7 @@ namespace MWLua
|
||||||
void deregisterObject(const MWWorld::Ptr& ptr) override;
|
void deregisterObject(const MWWorld::Ptr& ptr) override;
|
||||||
void inputEvent(const InputEvent& event) override { mInputEvents.push_back(event); }
|
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 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;
|
MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override;
|
||||||
|
|
||||||
|
|
|
@ -17,20 +17,24 @@ namespace MWLua
|
||||||
};
|
};
|
||||||
|
|
||||||
const static std::unordered_map<ESM::RecNameInts, LuaObjectTypeInfo> luaObjectTypeInfo = {
|
const static std::unordered_map<ESM::RecNameInts, LuaObjectTypeInfo> luaObjectTypeInfo = {
|
||||||
{ESM::REC_ACTI, {"Activator", ESM::LuaScriptCfg::sActivator}},
|
{ESM::REC_ACTI, {ObjectTypeName::Activator, ESM::LuaScriptCfg::sActivator}},
|
||||||
{ESM::REC_ARMO, {"Armor", ESM::LuaScriptCfg::sArmor}},
|
{ESM::REC_ARMO, {ObjectTypeName::Armor, ESM::LuaScriptCfg::sArmor}},
|
||||||
{ESM::REC_BOOK, {"Book", ESM::LuaScriptCfg::sBook}},
|
{ESM::REC_BOOK, {ObjectTypeName::Book, ESM::LuaScriptCfg::sBook}},
|
||||||
{ESM::REC_CLOT, {"Clothing", ESM::LuaScriptCfg::sClothing}},
|
{ESM::REC_CLOT, {ObjectTypeName::Clothing, ESM::LuaScriptCfg::sClothing}},
|
||||||
{ESM::REC_CONT, {"Container", ESM::LuaScriptCfg::sContainer}},
|
{ESM::REC_CONT, {ObjectTypeName::Container, ESM::LuaScriptCfg::sContainer}},
|
||||||
{ESM::REC_CREA, {"Creature", ESM::LuaScriptCfg::sCreature}},
|
{ESM::REC_CREA, {ObjectTypeName::Creature, ESM::LuaScriptCfg::sCreature}},
|
||||||
{ESM::REC_DOOR, {"Door", ESM::LuaScriptCfg::sDoor}},
|
{ESM::REC_DOOR, {ObjectTypeName::Door, ESM::LuaScriptCfg::sDoor}},
|
||||||
{ESM::REC_INGR, {"Ingredient", ESM::LuaScriptCfg::sIngredient}},
|
{ESM::REC_INGR, {ObjectTypeName::Ingredient, ESM::LuaScriptCfg::sIngredient}},
|
||||||
{ESM::REC_LIGH, {"Light", ESM::LuaScriptCfg::sLight}},
|
{ESM::REC_LIGH, {ObjectTypeName::Light, ESM::LuaScriptCfg::sLight}},
|
||||||
{ESM::REC_MISC, {"Miscellaneous", ESM::LuaScriptCfg::sMiscItem}},
|
{ESM::REC_MISC, {ObjectTypeName::MiscItem, ESM::LuaScriptCfg::sMiscItem}},
|
||||||
{ESM::REC_NPC_, {"NPC", ESM::LuaScriptCfg::sNPC}},
|
{ESM::REC_NPC_, {ObjectTypeName::NPC, ESM::LuaScriptCfg::sNPC}},
|
||||||
{ESM::REC_ALCH, {"Potion", ESM::LuaScriptCfg::sPotion}},
|
{ESM::REC_ALCH, {ObjectTypeName::Potion, ESM::LuaScriptCfg::sPotion}},
|
||||||
{ESM::REC_STAT, {"Static"}},
|
{ESM::REC_STAT, {ObjectTypeName::Static}},
|
||||||
{ESM::REC_WEAP, {"Weapon", ESM::LuaScriptCfg::sWeapon}},
|
{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)
|
std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback)
|
||||||
|
|
|
@ -14,6 +14,31 @@
|
||||||
|
|
||||||
namespace MWLua
|
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.
|
// ObjectId is a unique identifier of a game object.
|
||||||
// It can change only if the order of content files was change.
|
// It can change only if the order of content files was change.
|
||||||
using ObjectId = ESM::RefNum;
|
using ObjectId = ESM::RefNum;
|
||||||
|
|
|
@ -72,7 +72,7 @@ namespace MWLua
|
||||||
else
|
else
|
||||||
throw std::runtime_error("Index out of range");
|
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>>
|
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();
|
const MWWorld::Class& cls = o.ptr().getClass();
|
||||||
return cls.getWalkSpeed(o.ptr());
|
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>)
|
if constexpr (std::is_same_v<ObjectT, GObject>)
|
||||||
{ // Only for global scripts
|
{ // Only for global scripts
|
||||||
|
@ -301,8 +309,38 @@ namespace MWLua
|
||||||
inventoryT[sol::meta_function::to_string] =
|
inventoryT[sol::meta_function::to_string] =
|
||||||
[](const InventoryT& inv) { return "Inventory[" + inv.mObj.toString() + "]"; };
|
[](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();
|
const MWWorld::Ptr& ptr = inventory.mObj.ptr();
|
||||||
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
|
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
|
||||||
ObjectIdList list = std::make_shared<std::vector<ObjectId>>();
|
ObjectIdList list = std::make_shared<std::vector<ObjectId>>();
|
||||||
|
@ -310,39 +348,12 @@ namespace MWLua
|
||||||
while (it.getType() != -1)
|
while (it.getType() != -1)
|
||||||
{
|
{
|
||||||
const MWWorld::Ptr& item = *(it++);
|
const MWWorld::Ptr& item = *(it++);
|
||||||
context.mWorldView->getObjectRegistry()->registerPtr(item);
|
worldView->getObjectRegistry()->registerPtr(item);
|
||||||
list->push_back(getId(item));
|
list->push_back(getId(item));
|
||||||
}
|
}
|
||||||
return ObjectList<ObjectT>{list};
|
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)
|
inventoryT["countOf"] = [](const InventoryT& inventory, const std::string& recordId)
|
||||||
{
|
{
|
||||||
const MWWorld::Ptr& ptr = inventory.mObj.ptr();
|
const MWWorld::Ptr& ptr = inventory.mObj.ptr();
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
#include <components/lua_ui/element.hpp>
|
#include <components/lua_ui/element.hpp>
|
||||||
#include <components/lua_ui/layers.hpp>
|
#include <components/lua_ui/layers.hpp>
|
||||||
#include <components/lua_ui/content.hpp>
|
#include <components/lua_ui/content.hpp>
|
||||||
|
#include <components/lua_ui/registerscriptsettings.hpp>
|
||||||
|
#include <components/lua_ui/alignment.hpp>
|
||||||
|
|
||||||
#include "context.hpp"
|
#include "context.hpp"
|
||||||
#include "actions.hpp"
|
#include "actions.hpp"
|
||||||
|
@ -43,7 +45,7 @@ namespace MWLua
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
catch (std::exception&)
|
||||||
{
|
{
|
||||||
// prevent any actions on a potentially corrupted widget
|
// prevent any actions on a potentially corrupted widget
|
||||||
mElement->mRoot = nullptr;
|
mElement->mRoot = nullptr;
|
||||||
|
@ -238,6 +240,14 @@ namespace MWLua
|
||||||
typeTable.set(it.second, it.first);
|
typeTable.set(it.second, it.first);
|
||||||
api["TYPE"] = LuaUtil::makeReadOnly(typeTable);
|
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);
|
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
|
// Check for command effects having ended and remove package if necessary
|
||||||
void adjustCommandedActor (const MWWorld::Ptr& actor)
|
void adjustCommandedActor (const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
|
if (!isCommanded(actor))
|
||||||
|
return;
|
||||||
|
|
||||||
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||||
|
|
||||||
bool hasCommandPackage = false;
|
stats.getAiSequence().erasePackageIf([](auto& entry)
|
||||||
|
|
||||||
auto it = stats.getAiSequence().begin();
|
|
||||||
for (; it != stats.getAiSequence().end(); ++it)
|
|
||||||
{
|
{
|
||||||
if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Follow &&
|
if (entry->getTypeId() == MWMechanics::AiPackageTypeId::Follow &&
|
||||||
static_cast<const MWMechanics::AiFollow*>(it->get())->isCommanded())
|
static_cast<const MWMechanics::AiFollow*>(entry.get())->isCommanded())
|
||||||
{
|
{
|
||||||
hasCommandPackage = true;
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
return false;
|
||||||
|
});
|
||||||
if (!isCommanded(actor) && hasCommandPackage)
|
|
||||||
stats.getAiSequence().erase(it);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka)
|
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.
|
// AI processing is only done within given distance to the player.
|
||||||
bool inProcessingRange = distSqr <= mActorsProcessingRange*mActorsProcessingRange;
|
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 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()
|
if (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead()
|
||||||
|| !iter->first.getClass().getCreatureStats(iter->first).getAiSequence().isInCombat()
|
|| !iter->first.getClass().getCreatureStats(iter->first).getAiSequence().isInCombat()
|
||||||
|
@ -1524,25 +1518,31 @@ namespace MWMechanics
|
||||||
CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
||||||
float speedFactor = isPlayer ? 1.f : mov.mSpeedFactor;
|
float speedFactor = isPlayer ? 1.f : mov.mSpeedFactor;
|
||||||
osg::Vec2f movement = osg::Vec2f(mov.mPosition[0], mov.mPosition[1]) * speedFactor;
|
osg::Vec2f movement = osg::Vec2f(mov.mPosition[0], mov.mPosition[1]) * speedFactor;
|
||||||
|
float rotationX = mov.mRotation[0];
|
||||||
float rotationZ = mov.mRotation[2];
|
float rotationZ = mov.mRotation[2];
|
||||||
bool jump = mov.mPosition[2] == 1;
|
bool jump = mov.mPosition[2] == 1;
|
||||||
bool runFlag = stats.getMovementFlag(MWMechanics::CreatureStats::Flag_Run);
|
bool runFlag = stats.getMovementFlag(MWMechanics::CreatureStats::Flag_Run);
|
||||||
|
bool attackingOrSpell = stats.getAttackingOrSpell();
|
||||||
if (luaControls->mChanged)
|
if (luaControls->mChanged)
|
||||||
{
|
{
|
||||||
mov.mPosition[0] = luaControls->mSideMovement;
|
mov.mPosition[0] = luaControls->mSideMovement;
|
||||||
mov.mPosition[1] = luaControls->mMovement;
|
mov.mPosition[1] = luaControls->mMovement;
|
||||||
mov.mPosition[2] = luaControls->mJump ? 1 : 0;
|
mov.mPosition[2] = luaControls->mJump ? 1 : 0;
|
||||||
|
mov.mRotation[0] = luaControls->mPitchChange;
|
||||||
mov.mRotation[1] = 0;
|
mov.mRotation[1] = 0;
|
||||||
mov.mRotation[2] = luaControls->mTurn;
|
mov.mRotation[2] = luaControls->mYawChange;
|
||||||
mov.mSpeedFactor = osg::Vec2(luaControls->mMovement, luaControls->mSideMovement).length();
|
mov.mSpeedFactor = osg::Vec2(luaControls->mMovement, luaControls->mSideMovement).length();
|
||||||
stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, luaControls->mRun);
|
stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, luaControls->mRun);
|
||||||
|
stats.setAttackingOrSpell(luaControls->mUse == 1);
|
||||||
luaControls->mChanged = false;
|
luaControls->mChanged = false;
|
||||||
}
|
}
|
||||||
luaControls->mSideMovement = movement.x();
|
luaControls->mSideMovement = movement.x();
|
||||||
luaControls->mMovement = movement.y();
|
luaControls->mMovement = movement.y();
|
||||||
luaControls->mTurn = rotationZ;
|
luaControls->mPitchChange = rotationX;
|
||||||
|
luaControls->mYawChange = rotationZ;
|
||||||
luaControls->mJump = jump;
|
luaControls->mJump = jump;
|
||||||
luaControls->mRun = runFlag;
|
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> Actors::getActorsFollowing(const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
std::list<MWWorld::Ptr> list;
|
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)
|
if (package->followTargetThroughDoors() && package->getTarget() == actor)
|
||||||
list.push_back(iter.first);
|
list.push_back(iter.first);
|
||||||
|
@ -2061,7 +2061,7 @@ namespace MWMechanics
|
||||||
std::list<int> Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor)
|
std::list<int> Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor)
|
||||||
{
|
{
|
||||||
std::list<int> list;
|
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)
|
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> Actors::getActorsFollowingByIndex(const MWWorld::Ptr &actor)
|
||||||
{
|
{
|
||||||
std::map<int, MWWorld::Ptr> map;
|
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)
|
if (package->followTargetThroughDoors() && package->getTarget() == actor)
|
||||||
{
|
{
|
||||||
|
|
|
@ -141,7 +141,7 @@ namespace MWMechanics
|
||||||
if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage);
|
if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage);
|
||||||
if (storage.mRotateMove)
|
if (storage.mRotateMove)
|
||||||
return false;
|
return false;
|
||||||
storage.updateAttack(characterController);
|
storage.updateAttack(actor, characterController);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -168,7 +168,7 @@ namespace MWMechanics
|
||||||
if (!canFight(actor, target))
|
if (!canFight(actor, target))
|
||||||
{
|
{
|
||||||
storage.stopAttack();
|
storage.stopAttack();
|
||||||
characterController.setAttackingOrSpell(false);
|
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false);
|
||||||
storage.mActionCooldown = 0.f;
|
storage.mActionCooldown = 0.f;
|
||||||
// Continue combat if target is player or player follower/escorter and an attack has been attempted
|
// 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());
|
const std::list<MWWorld::Ptr>& playerFollowersAndEscorters = MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(MWMechanics::getPlayer());
|
||||||
|
@ -299,7 +299,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
storage.mUseCustomDestination = false;
|
storage.mUseCustomDestination = false;
|
||||||
storage.stopAttack();
|
storage.stopAttack();
|
||||||
characterController.setAttackingOrSpell(false);
|
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false);
|
||||||
currentAction.reset(new ActionFlee());
|
currentAction.reset(new ActionFlee());
|
||||||
actionCooldown = currentAction->getActionCooldown();
|
actionCooldown = currentAction->getActionCooldown();
|
||||||
storage.startFleeing();
|
storage.startFleeing();
|
||||||
|
@ -575,7 +575,7 @@ namespace MWMechanics
|
||||||
if (mAttackCooldown <= 0)
|
if (mAttackCooldown <= 0)
|
||||||
{
|
{
|
||||||
mAttack = true; // attack starts just now
|
mAttack = true; // attack starts just now
|
||||||
characterController.setAttackingOrSpell(true);
|
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(true);
|
||||||
|
|
||||||
if (!distantCombat)
|
if (!distantCombat)
|
||||||
characterController.setAIAttackType(chooseBestAttack(weapon));
|
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()))
|
if (mAttack && (characterController.getAttackStrength() >= mStrength || characterController.readyToPrepareAttack()))
|
||||||
{
|
{
|
||||||
mAttack = false;
|
mAttack = false;
|
||||||
}
|
}
|
||||||
characterController.setAttackingOrSpell(mAttack);
|
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiCombatStorage::stopAttack()
|
void AiCombatStorage::stopAttack()
|
||||||
|
|
|
@ -88,7 +88,7 @@ namespace MWMechanics
|
||||||
void stopCombatMove();
|
void stopCombatMove();
|
||||||
void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController,
|
void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController,
|
||||||
const ESM::Weapon* weapon, bool distantCombat);
|
const ESM::Weapon* weapon, bool distantCombat);
|
||||||
void updateAttack(CharacterController& characterController);
|
void updateAttack(const MWWorld::Ptr& actor, CharacterController& characterController);
|
||||||
void stopAttack();
|
void stopAttack();
|
||||||
|
|
||||||
void startFleeing();
|
void startFleeing();
|
||||||
|
|
|
@ -476,8 +476,7 @@ namespace MWMechanics
|
||||||
static const float fAIFleeHealthMult = gmst.find("fAIFleeHealthMult")->mValue.getFloat();
|
static const float fAIFleeHealthMult = gmst.find("fAIFleeHealthMult")->mValue.getFloat();
|
||||||
static const float fAIFleeFleeMult = gmst.find("fAIFleeFleeMult")->mValue.getFloat();
|
static const float fAIFleeFleeMult = gmst.find("fAIFleeFleeMult")->mValue.getFloat();
|
||||||
|
|
||||||
float healthPercentage = (stats.getHealth().getModified() == 0.0f)
|
float healthPercentage = stats.getHealth().getRatio(false);
|
||||||
? 1.0f : stats.getHealth().getCurrent() / stats.getHealth().getModified();
|
|
||||||
float rating = (1.0f - healthPercentage) * fAIFleeHealthMult + flee * fAIFleeFleeMult;
|
float rating = (1.0f - healthPercentage) * fAIFleeHealthMult + flee * fAIFleeFleeMult;
|
||||||
|
|
||||||
static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->mValue.getInteger();
|
static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->mValue.getInteger();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "aisequence.hpp"
|
#include "aisequence.hpp"
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/esm3/aisequence.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.
|
// We need to keep an AiWander storage, if present - it has a state machine.
|
||||||
// Not sure about another temporary storages
|
// Not sure about another temporary storages
|
||||||
sequence.mAiState.copy<AiWanderStorage>(mAiState);
|
sequence.mAiState.copy<AiWanderStorage>(mAiState);
|
||||||
|
|
||||||
|
mNumCombatPackages = sequence.mNumCombatPackages;
|
||||||
|
mNumPursuitPackages = sequence.mNumPursuitPackages;
|
||||||
}
|
}
|
||||||
|
|
||||||
AiSequence::AiSequence() : mDone (false), mLastAiPackage(AiPackageTypeId::None) {}
|
AiSequence::AiSequence() : mDone (false), mLastAiPackage(AiPackageTypeId::None) {}
|
||||||
|
@ -58,6 +62,28 @@ AiSequence::~AiSequence()
|
||||||
clear();
|
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
|
AiPackageTypeId AiSequence::getTypeId() const
|
||||||
{
|
{
|
||||||
if (mPackages.empty())
|
if (mPackages.empty())
|
||||||
|
@ -87,42 +113,30 @@ bool AiSequence::getCombatTargets(std::vector<MWWorld::Ptr> &targetActors) const
|
||||||
return !targetActors.empty();
|
return !targetActors.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::list<std::unique_ptr<AiPackage>>::const_iterator AiSequence::begin() const
|
AiPackages::iterator AiSequence::erase(AiPackages::iterator package)
|
||||||
{
|
|
||||||
return mPackages.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::list<std::unique_ptr<AiPackage>>::const_iterator AiSequence::end() const
|
|
||||||
{
|
|
||||||
return mPackages.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AiSequence::erase(std::list<std::unique_ptr<AiPackage>>::const_iterator package)
|
|
||||||
{
|
{
|
||||||
// Not sure if manually terminated packages should trigger mDone, probably not?
|
// Not sure if manually terminated packages should trigger mDone, probably not?
|
||||||
for(auto it = mPackages.begin(); it != mPackages.end(); ++it)
|
auto& ptr = *package;
|
||||||
{
|
onPackageRemoved(*ptr);
|
||||||
if (package == it)
|
|
||||||
{
|
return mPackages.erase(package);
|
||||||
mPackages.erase(it);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw std::runtime_error("can't find package to erase");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AiSequence::isInCombat() const
|
bool AiSequence::isInCombat() const
|
||||||
{
|
{
|
||||||
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
|
return mNumCombatPackages > 0;
|
||||||
{
|
}
|
||||||
if ((*it)->getTypeId() == AiPackageTypeId::Combat)
|
|
||||||
return true;
|
bool AiSequence::isInPursuit() const
|
||||||
}
|
{
|
||||||
return false;
|
return mNumPursuitPackages > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AiSequence::isEngagedWithActor() const
|
bool AiSequence::isEngagedWithActor() const
|
||||||
{
|
{
|
||||||
|
if (!isInCombat())
|
||||||
|
return false;
|
||||||
|
|
||||||
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
|
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
|
||||||
{
|
{
|
||||||
if ((*it)->getTypeId() == AiPackageTypeId::Combat)
|
if ((*it)->getTypeId() == AiPackageTypeId::Combat)
|
||||||
|
@ -137,16 +151,18 @@ bool AiSequence::isEngagedWithActor() const
|
||||||
|
|
||||||
bool AiSequence::hasPackage(AiPackageTypeId typeId) 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 package->getTypeId() == typeId;
|
||||||
return true;
|
});
|
||||||
}
|
return it != mPackages.end();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const
|
bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const
|
||||||
{
|
{
|
||||||
|
if (!isInCombat())
|
||||||
|
return false;
|
||||||
|
|
||||||
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
|
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
|
||||||
{
|
{
|
||||||
if ((*it)->getTypeId() == AiPackageTypeId::Combat)
|
if ((*it)->getTypeId() == AiPackageTypeId::Combat)
|
||||||
|
@ -158,27 +174,31 @@ bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use std::list::remove_if for all these methods when we switch to C++20
|
void AiSequence::removePackagesById(AiPackageTypeId id)
|
||||||
void AiSequence::stopCombat()
|
|
||||||
{
|
{
|
||||||
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
|
else
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AiSequence::stopCombat()
|
||||||
|
{
|
||||||
|
removePackagesById(AiPackageTypeId::Combat);
|
||||||
|
}
|
||||||
|
|
||||||
void AiSequence::stopCombat(const std::vector<MWWorld::Ptr>& targets)
|
void AiSequence::stopCombat(const std::vector<MWWorld::Ptr>& targets)
|
||||||
{
|
{
|
||||||
for(auto it = mPackages.begin(); it != mPackages.end(); )
|
for(auto it = mPackages.begin(); it != mPackages.end(); )
|
||||||
{
|
{
|
||||||
if ((*it)->getTypeId() == AiPackageTypeId::Combat && std::find(targets.begin(), targets.end(), (*it)->getTarget()) != targets.end())
|
if ((*it)->getTypeId() == AiPackageTypeId::Combat && std::find(targets.begin(), targets.end(), (*it)->getTarget()) != targets.end())
|
||||||
{
|
{
|
||||||
it = mPackages.erase(it);
|
it = erase(it);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
++it;
|
++it;
|
||||||
|
@ -187,15 +207,7 @@ void AiSequence::stopCombat(const std::vector<MWWorld::Ptr>& targets)
|
||||||
|
|
||||||
void AiSequence::stopPursuit()
|
void AiSequence::stopPursuit()
|
||||||
{
|
{
|
||||||
for(auto it = mPackages.begin(); it != mPackages.end(); )
|
removePackagesById(AiPackageTypeId::Pursue);
|
||||||
{
|
|
||||||
if ((*it)->getTypeId() == AiPackageTypeId::Pursue)
|
|
||||||
{
|
|
||||||
it = mPackages.erase(it);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AiSequence::isPackageDone() const
|
bool AiSequence::isPackageDone() const
|
||||||
|
@ -214,112 +226,117 @@ namespace
|
||||||
|
|
||||||
void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange)
|
void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange)
|
||||||
{
|
{
|
||||||
if(actor != getPlayer())
|
if (actor == getPlayer())
|
||||||
{
|
{
|
||||||
if (mPackages.empty())
|
// Players don't use this.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPackages.empty())
|
||||||
|
{
|
||||||
|
mLastAiPackage = AiPackageTypeId::None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 bestRating = 0.f;
|
||||||
|
|
||||||
|
for (auto it = mPackages.begin(); it != mPackages.end();)
|
||||||
{
|
{
|
||||||
mLastAiPackage = AiPackageTypeId::None;
|
if ((*it)->getTypeId() != AiPackageTypeId::Combat) break;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto packageIt = mPackages.begin();
|
MWWorld::Ptr target = (*it)->getTarget();
|
||||||
MWMechanics::AiPackage* package = packageIt->get();
|
|
||||||
if (!package->alwaysActive() && outOfRange)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto packageTypeId = package->getTypeId();
|
// target disappeared (e.g. summoned creatures)
|
||||||
// workaround ai packages not being handled as in the vanilla engine
|
if (target.isEmpty())
|
||||||
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 bestRating = 0.f;
|
|
||||||
|
|
||||||
for (auto it = mPackages.begin(); it != mPackages.end();)
|
|
||||||
{
|
{
|
||||||
if ((*it)->getTypeId() != AiPackageTypeId::Combat) break;
|
it = erase(it);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
const ESM::Position &targetPos = target.getRefData().getPosition();
|
|
||||||
|
|
||||||
float distTo = (targetPos.asVec3() - vActorPos).length2();
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
mDone = false;
|
float rating = MWMechanics::getBestActionRating(actor, target);
|
||||||
|
|
||||||
|
const ESM::Position &targetPos = target.getRefData().getPosition();
|
||||||
|
|
||||||
|
float distTo = (targetPos.asVec3() - vActorPos).length2();
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
|
||||||
|
assert(!mPackages.empty());
|
||||||
|
|
||||||
|
if (nearestDist < std::numeric_limits<float>::max() && mPackages.begin() != itActualCombat)
|
||||||
{
|
{
|
||||||
Log(Debug::Error) << "Error during AiSequence::execute: " << e.what();
|
assert(itActualCombat != mPackages.end());
|
||||||
|
// move combat package with nearest target to the front
|
||||||
|
std::rotate(mPackages.begin(), itActualCombat, std::next(itActualCombat));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packageIt = mPackages.begin();
|
||||||
|
package = packageIt->get();
|
||||||
|
packageTypeId = package->getTypeId();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
erase(packageIt);
|
||||||
|
if (isActualAiPackage(packageTypeId))
|
||||||
|
mDone = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mDone = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "Error during AiSequence::execute: " << e.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiSequence::clear()
|
void AiSequence::clear()
|
||||||
{
|
{
|
||||||
mPackages.clear();
|
mPackages.clear();
|
||||||
|
mNumCombatPackages = 0;
|
||||||
|
mNumPursuitPackages = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther)
|
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())
|
if((*it)->canCancel())
|
||||||
{
|
{
|
||||||
it = mPackages.erase(it);
|
it = erase(it);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
++it;
|
++it;
|
||||||
|
@ -383,11 +400,13 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
|
||||||
|
|
||||||
if((*it)->getPriority() <= package.getPriority())
|
if((*it)->getPriority() <= package.getPriority())
|
||||||
{
|
{
|
||||||
|
onPackageAdded(package);
|
||||||
mPackages.insert(it, package.clone());
|
mPackages.insert(it, package.clone());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPackageAdded(package);
|
||||||
mPackages.push_back(package.clone());
|
mPackages.push_back(package.clone());
|
||||||
|
|
||||||
// Make sure that temporary storage is empty
|
// Make sure that temporary storage is empty
|
||||||
|
@ -445,6 +464,8 @@ void AiSequence::fill(const ESM::AIPackageList &list)
|
||||||
ESM::AITarget data = esmPackage.mTarget;
|
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);
|
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));
|
mPackages.push_back(std::move(package));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -514,6 +535,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
|
||||||
if (!package.get())
|
if (!package.get())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
onPackageAdded(*package);
|
||||||
mPackages.push_back(std::move(package));
|
mPackages.push_back(std::move(package));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
#ifndef GAME_MWMECHANICS_AISEQUENCE_H
|
#ifndef GAME_MWMECHANICS_AISEQUENCE_H
|
||||||
#define GAME_MWMECHANICS_AISEQUENCE_H
|
#define GAME_MWMECHANICS_AISEQUENCE_H
|
||||||
|
|
||||||
#include <list>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "aistate.hpp"
|
#include "aistate.hpp"
|
||||||
#include "aipackagetypeid.hpp"
|
#include "aipackagetypeid.hpp"
|
||||||
|
@ -22,8 +23,6 @@ namespace ESM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
class AiPackage;
|
class AiPackage;
|
||||||
|
@ -33,15 +32,20 @@ namespace MWMechanics
|
||||||
struct AiTemporaryBase;
|
struct AiTemporaryBase;
|
||||||
typedef DerivedClassStorage<AiTemporaryBase> AiState;
|
typedef DerivedClassStorage<AiTemporaryBase> AiState;
|
||||||
|
|
||||||
|
using AiPackages = std::vector<std::shared_ptr<AiPackage>>;
|
||||||
|
|
||||||
/// \brief Sequence of AI-packages for a single actor
|
/// \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. **/
|
/** The top-most AI package is run each frame. When completed, it is removed from the stack. **/
|
||||||
class AiSequence
|
class AiSequence
|
||||||
{
|
{
|
||||||
///AiPackages to run though
|
///AiPackages to run though
|
||||||
std::list<std::unique_ptr<AiPackage>> mPackages;
|
AiPackages mPackages;
|
||||||
|
|
||||||
///Finished with top AIPackage, set for one frame
|
///Finished with top AIPackage, set for one frame
|
||||||
bool mDone;
|
bool mDone{};
|
||||||
|
|
||||||
|
int mNumCombatPackages{};
|
||||||
|
int mNumPursuitPackages{};
|
||||||
|
|
||||||
///Copy AiSequence
|
///Copy AiSequence
|
||||||
void copy (const AiSequence& sequence);
|
void copy (const AiSequence& sequence);
|
||||||
|
@ -50,6 +54,11 @@ namespace MWMechanics
|
||||||
AiPackageTypeId mLastAiPackage;
|
AiPackageTypeId mLastAiPackage;
|
||||||
AiState mAiState;
|
AiState mAiState;
|
||||||
|
|
||||||
|
void onPackageAdded(const AiPackage& package);
|
||||||
|
void onPackageRemoved(const AiPackage& package);
|
||||||
|
|
||||||
|
AiPackages::iterator erase(AiPackages::iterator package);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
///Default constructor
|
///Default constructor
|
||||||
AiSequence();
|
AiSequence();
|
||||||
|
@ -63,10 +72,31 @@ namespace MWMechanics
|
||||||
virtual ~AiSequence();
|
virtual ~AiSequence();
|
||||||
|
|
||||||
/// Iterator may be invalidated by any function calls other than begin() or end().
|
/// Iterator may be invalidated by any function calls other than begin() or end().
|
||||||
std::list<std::unique_ptr<AiPackage>>::const_iterator begin() const;
|
AiPackages::const_iterator begin() const { return mPackages.begin(); }
|
||||||
std::list<std::unique_ptr<AiPackage>>::const_iterator end() const;
|
AiPackages::const_iterator end() const { return mPackages.end(); }
|
||||||
|
|
||||||
void erase(std::list<std::unique_ptr<AiPackage>>::const_iterator package);
|
/// 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
|
/// Returns currently executing AiPackage type
|
||||||
/** \see enum class AiPackageTypeId **/
|
/** \see enum class AiPackageTypeId **/
|
||||||
|
@ -87,6 +117,12 @@ namespace MWMechanics
|
||||||
/// Is there any combat package?
|
/// Is there any combat package?
|
||||||
bool isInCombat () const;
|
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?
|
/// Are we in combat with any other actor, who's also engaging us?
|
||||||
bool isEngagedWithActor () const;
|
bool isEngagedWithActor () const;
|
||||||
|
|
||||||
|
|
|
@ -55,9 +55,9 @@ namespace
|
||||||
|
|
||||||
std::string getBestAttack (const ESM::Weapon* weapon)
|
std::string getBestAttack (const ESM::Weapon* weapon)
|
||||||
{
|
{
|
||||||
int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2;
|
int slash = weapon->mData.mSlash[0] + weapon->mData.mSlash[1];
|
||||||
int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2;
|
int chop = weapon->mData.mChop[0] + weapon->mData.mChop[1];
|
||||||
int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2;
|
int thrust = weapon->mData.mThrust[0] + weapon->mData.mThrust[1];
|
||||||
if (slash == chop && slash == thrust)
|
if (slash == chop && slash == thrust)
|
||||||
return "slash";
|
return "slash";
|
||||||
else if (thrust >= chop && thrust >= slash)
|
else if (thrust >= chop && thrust >= slash)
|
||||||
|
@ -435,6 +435,8 @@ std::string CharacterController::getWeaponAnimation(int weaponType) const
|
||||||
else if (isRealWeapon)
|
else if (isRealWeapon)
|
||||||
weaponGroup = oneHandFallback;
|
weaponGroup = oneHandFallback;
|
||||||
}
|
}
|
||||||
|
else if (weaponType == ESM::Weapon::HandToHand && !mPtr.getClass().isBipedal(mPtr))
|
||||||
|
return "attack1";
|
||||||
|
|
||||||
return weaponGroup;
|
return weaponGroup;
|
||||||
}
|
}
|
||||||
|
@ -707,9 +709,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
||||||
if (mPtr.getClass().isActor())
|
if (mPtr.getClass().isActor())
|
||||||
refreshHitRecoilAnims(idle);
|
refreshHitRecoilAnims(idle);
|
||||||
|
|
||||||
std::string weap;
|
std::string weap = getWeaponType(mWeaponType)->mShortGroup;
|
||||||
if (mPtr.getClass().hasInventoryStore(mPtr))
|
|
||||||
weap = getWeaponType(mWeaponType)->mShortGroup;
|
|
||||||
|
|
||||||
refreshJumpAnims(weap, jump, idle, force);
|
refreshJumpAnims(weap, jump, idle, force);
|
||||||
refreshMovementAnims(weap, movement, idle, force);
|
refreshMovementAnims(weap, movement, idle, force);
|
||||||
|
@ -854,7 +854,6 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
|
||||||
, mSecondsOfSwimming(0)
|
, mSecondsOfSwimming(0)
|
||||||
, mSecondsOfRunning(0)
|
, mSecondsOfRunning(0)
|
||||||
, mTurnAnimationThreshold(0)
|
, mTurnAnimationThreshold(0)
|
||||||
, mAttackingOrSpell(false)
|
|
||||||
, mCastingManualSpell(false)
|
, mCastingManualSpell(false)
|
||||||
, mTimeUntilWake(0.f)
|
, mTimeUntilWake(0.f)
|
||||||
, mIsMovingBackward(false)
|
, 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
|
bool CharacterController::updateCarriedLeftVisible(const int weaptype) const
|
||||||
{
|
{
|
||||||
// Shields/torches shouldn't be visible during any operation involving two hands
|
// 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);
|
return mAnimation->updateCarriedLeftVisible(weaptype);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CharacterController::updateWeaponState(CharacterState& idle)
|
bool CharacterController::updateState(CharacterState& idle)
|
||||||
{
|
{
|
||||||
const MWWorld::Class &cls = mPtr.getClass();
|
const MWWorld::Class &cls = mPtr.getClass();
|
||||||
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
||||||
|
@ -1277,11 +1185,10 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
{
|
{
|
||||||
forcestateupdate = true;
|
forcestateupdate = true;
|
||||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||||
mAttackingOrSpell = false;
|
setAttackingOrSpell(false);
|
||||||
mAnimation->disable(mCurrentWeapon);
|
mAnimation->disable(mCurrentWeapon);
|
||||||
mAnimation->showWeapons(true);
|
mAnimation->showWeapons(true);
|
||||||
if (mPtr == getPlayer())
|
stats.setAttackingOrSpell(false);
|
||||||
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!isKnockedOut() && !isKnockedDown() && !isRecovery())
|
if(!isKnockedOut() && !isKnockedDown() && !isRecovery())
|
||||||
|
@ -1388,7 +1295,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
}
|
}
|
||||||
|
|
||||||
mWeaponType = weaptype;
|
mWeaponType = weaptype;
|
||||||
mCurrentWeapon = getWeaponAnimation(mWeaponType);
|
mCurrentWeapon = weapgroup;
|
||||||
|
|
||||||
if(!upSoundId.empty() && !isStillWeapon)
|
if(!upSoundId.empty() && !isStillWeapon)
|
||||||
{
|
{
|
||||||
|
@ -1456,17 +1363,15 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
float complete;
|
float complete;
|
||||||
bool animPlaying;
|
bool animPlaying;
|
||||||
ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass;
|
ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass;
|
||||||
if(mAttackingOrSpell)
|
if(getAttackingOrSpell())
|
||||||
{
|
{
|
||||||
MWWorld::Ptr player = getPlayer();
|
|
||||||
|
|
||||||
bool resetIdle = ammunition;
|
bool resetIdle = ammunition;
|
||||||
if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block))
|
if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block))
|
||||||
{
|
{
|
||||||
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
|
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
|
||||||
mAttackStrength = 0;
|
mAttackStrength = 0;
|
||||||
|
|
||||||
// Randomize attacks for non-bipedal creatures with Weapon flag
|
// Randomize attacks for non-bipedal creatures
|
||||||
if (mPtr.getClass().getType() == ESM::Creature::sRecordId &&
|
if (mPtr.getClass().getType() == ESM::Creature::sRecordId &&
|
||||||
!mPtr.getClass().isBipedal(mPtr) &&
|
!mPtr.getClass().isBipedal(mPtr) &&
|
||||||
(!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon)))
|
(!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon)))
|
||||||
|
@ -1478,11 +1383,9 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
{
|
{
|
||||||
// Unset casting flag, otherwise pressing the mouse button down would
|
// Unset casting flag, otherwise pressing the mouse button down would
|
||||||
// continue casting every frame if there is no animation
|
// continue casting every frame if there is no animation
|
||||||
mAttackingOrSpell = false;
|
setAttackingOrSpell(false);
|
||||||
if (mPtr == player)
|
if (mPtr == getPlayer())
|
||||||
{
|
{
|
||||||
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
|
|
||||||
|
|
||||||
// For the player, set the spell we want to cast
|
// For the player, set the spell we want to cast
|
||||||
// This has to be done at the start of the casting animation,
|
// 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)
|
// *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,
|
weapSpeed, startKey, stopKey,
|
||||||
0.0f, 0);
|
0.0f, 0);
|
||||||
if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f)
|
if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f)
|
||||||
|
{
|
||||||
mUpperBodyState = UpperCharState_StartToMinAttack;
|
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.
|
// 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 minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack");
|
||||||
float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack");
|
float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack");
|
||||||
if (mAttackingOrSpell || minAttackTime == maxAttackTime)
|
if (getAttackingOrSpell() || minAttackTime == maxAttackTime)
|
||||||
{
|
{
|
||||||
start = mAttackType+" min attack";
|
start = mAttackType+" min attack";
|
||||||
stop = mAttackType+" max attack";
|
stop = mAttackType+" max attack";
|
||||||
|
@ -2350,11 +2260,7 @@ void CharacterController::update(float duration)
|
||||||
|
|
||||||
if (!mSkipAnim)
|
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.
|
forcestateupdate = updateState(idlestate) || forcestateupdate;
|
||||||
if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr))
|
|
||||||
forcestateupdate = updateWeaponState(idlestate) || forcestateupdate;
|
|
||||||
else
|
|
||||||
forcestateupdate = updateCreatureState() || forcestateupdate;
|
|
||||||
|
|
||||||
refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate);
|
refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate);
|
||||||
updateIdleStormState(inwater);
|
updateIdleStormState(inwater);
|
||||||
|
@ -2647,7 +2553,7 @@ void CharacterController::forceStateUpdate()
|
||||||
// Make sure we canceled the current attack or spellcasting,
|
// Make sure we canceled the current attack or spellcasting,
|
||||||
// because we disabled attack animations anyway.
|
// because we disabled attack animations anyway.
|
||||||
mCastingManualSpell = false;
|
mCastingManualSpell = false;
|
||||||
mAttackingOrSpell = false;
|
setAttackingOrSpell(false);
|
||||||
if (mUpperBodyState != UpperCharState_Nothing)
|
if (mUpperBodyState != UpperCharState_Nothing)
|
||||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||||
|
|
||||||
|
@ -2845,12 +2751,12 @@ bool CharacterController::isRunning() const
|
||||||
|
|
||||||
void CharacterController::setAttackingOrSpell(bool attackingOrSpell)
|
void CharacterController::setAttackingOrSpell(bool attackingOrSpell)
|
||||||
{
|
{
|
||||||
mAttackingOrSpell = attackingOrSpell;
|
mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(attackingOrSpell);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::castSpell(const std::string& spellId, bool manualSpell)
|
void CharacterController::castSpell(const std::string& spellId, bool manualSpell)
|
||||||
{
|
{
|
||||||
mAttackingOrSpell = true;
|
setAttackingOrSpell(true);
|
||||||
mCastingManualSpell = manualSpell;
|
mCastingManualSpell = manualSpell;
|
||||||
ActionSpell action = ActionSpell(spellId);
|
ActionSpell action = ActionSpell(spellId);
|
||||||
action.prepare(mPtr);
|
action.prepare(mPtr);
|
||||||
|
@ -2883,10 +2789,7 @@ bool CharacterController::readyToStartAttack() const
|
||||||
if (mHitState != CharState_None && mHitState != CharState_Block)
|
if (mHitState != CharState_None && mHitState != CharState_Block)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr))
|
return mUpperBodyState == UpperCharState_WeapEquiped;
|
||||||
return mUpperBodyState == UpperCharState_WeapEquiped;
|
|
||||||
else
|
|
||||||
return mUpperBodyState == UpperCharState_Nothing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float CharacterController::getAttackStrength() const
|
float CharacterController::getAttackStrength() const
|
||||||
|
@ -2894,6 +2797,11 @@ float CharacterController::getAttackStrength() const
|
||||||
return mAttackStrength;
|
return mAttackStrength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CharacterController::getAttackingOrSpell()
|
||||||
|
{
|
||||||
|
return mPtr.getClass().getCreatureStats(mPtr).getAttackingOrSpell();
|
||||||
|
}
|
||||||
|
|
||||||
void CharacterController::setActive(int active)
|
void CharacterController::setActive(int active)
|
||||||
{
|
{
|
||||||
mAnimation->setActive(active);
|
mAnimation->setActive(active);
|
||||||
|
|
|
@ -188,7 +188,6 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
||||||
|
|
||||||
std::string mAttackType; // slash, chop or thrust
|
std::string mAttackType; // slash, chop or thrust
|
||||||
|
|
||||||
bool mAttackingOrSpell;
|
|
||||||
bool mCastingManualSpell;
|
bool mCastingManualSpell;
|
||||||
|
|
||||||
float mTimeUntilWake;
|
float mTimeUntilWake;
|
||||||
|
@ -206,8 +205,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
||||||
|
|
||||||
void clearAnimQueue(bool clearPersistAnims = false);
|
void clearAnimQueue(bool clearPersistAnims = false);
|
||||||
|
|
||||||
bool updateWeaponState(CharacterState& idle);
|
bool updateState(CharacterState& idle);
|
||||||
bool updateCreatureState();
|
|
||||||
void updateIdleStormState(bool inwater);
|
void updateIdleStormState(bool inwater);
|
||||||
|
|
||||||
std::string chooseRandomAttackAnimation() const;
|
std::string chooseRandomAttackAnimation() const;
|
||||||
|
@ -235,6 +233,10 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
||||||
|
|
||||||
std::string getWeaponAnimation(int weaponType) const;
|
std::string getWeaponAnimation(int weaponType) const;
|
||||||
|
|
||||||
|
bool getAttackingOrSpell();
|
||||||
|
void setAttackingOrSpell(bool attackingOrSpell);
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim);
|
CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim);
|
||||||
virtual ~CharacterController();
|
virtual ~CharacterController();
|
||||||
|
@ -285,7 +287,6 @@ public:
|
||||||
bool isAttackingOrSpell() const;
|
bool isAttackingOrSpell() const;
|
||||||
|
|
||||||
void setVisibility(float visibility);
|
void setVisibility(float visibility);
|
||||||
void setAttackingOrSpell(bool attackingOrSpell);
|
|
||||||
void castSpell(const std::string& spellId, bool manualSpell=false);
|
void castSpell(const std::string& spellId, bool manualSpell=false);
|
||||||
void setAIAttackType(const std::string& attackType);
|
void setAIAttackType(const std::string& attackType);
|
||||||
static void setAttackTypeRandomly(std::string& attackType);
|
static void setAttackTypeRandomly(std::string& attackType);
|
||||||
|
|
|
@ -24,9 +24,8 @@ namespace MWMechanics
|
||||||
mHitRecovery(false), mBlock(false), mMovementFlags(0),
|
mHitRecovery(false), mBlock(false), mMovementFlags(0),
|
||||||
mFallHeight(0), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1),
|
mFallHeight(0), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1),
|
||||||
mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0)
|
mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0)
|
||||||
|
, mAttackingOrSpell(false)
|
||||||
{
|
{
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
mAiSettings[i] = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AiSequence& CreatureStats::getAiSequence() const
|
const AiSequence& CreatureStats::getAiSequence() const
|
||||||
|
@ -157,9 +156,8 @@ namespace MWMechanics
|
||||||
float agility = getAttribute(ESM::Attribute::Agility).getModified();
|
float agility = getAttribute(ESM::Attribute::Agility).getModified();
|
||||||
float endurance = getAttribute(ESM::Attribute::Endurance).getModified();
|
float endurance = getAttribute(ESM::Attribute::Endurance).getModified();
|
||||||
DynamicStat<float> fatigue = getFatigue();
|
DynamicStat<float> fatigue = getFatigue();
|
||||||
float diff = (strength+willpower+agility+endurance) - fatigue.getBase();
|
|
||||||
float currentToBaseRatio = fatigue.getBase() > 0 ? (fatigue.getCurrent() / fatigue.getBase()) : 0;
|
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);
|
fatigue.setCurrent(fatigue.getBase() * currentToBaseRatio, false, true);
|
||||||
setFatigue(fatigue);
|
setFatigue(fatigue);
|
||||||
}
|
}
|
||||||
|
@ -195,8 +193,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
mDead = true;
|
mDead = true;
|
||||||
|
|
||||||
mDynamic[index].setModifier(0);
|
|
||||||
mDynamic[index].setCurrentModifier(0);
|
|
||||||
mDynamic[index].setCurrent(0);
|
mDynamic[index].setCurrent(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,10 +276,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
if (mDead)
|
if (mDead)
|
||||||
{
|
{
|
||||||
if (mDynamic[0].getModified() < 1)
|
mDynamic[0].setCurrent(mDynamic[0].getBase());
|
||||||
mDynamic[0].setModified(1, 0);
|
|
||||||
|
|
||||||
mDynamic[0].setCurrent(mDynamic[0].getModified());
|
|
||||||
mDead = false;
|
mDead = false;
|
||||||
mDeathAnimationFinished = false;
|
mDeathAnimationFinished = false;
|
||||||
}
|
}
|
||||||
|
@ -414,9 +407,8 @@ namespace MWMechanics
|
||||||
double magickaFactor = base + mMagicEffects.get(EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1;
|
double magickaFactor = base + mMagicEffects.get(EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1;
|
||||||
|
|
||||||
DynamicStat<float> magicka = getMagicka();
|
DynamicStat<float> magicka = getMagicka();
|
||||||
float diff = (static_cast<int>(magickaFactor*intelligence)) - magicka.getBase();
|
|
||||||
float currentToBaseRatio = magicka.getBase() > 0 ? magicka.getCurrent() / magicka.getBase() : 0;
|
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);
|
magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true);
|
||||||
setMagicka(magicka);
|
setMagicka(magicka);
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,6 +92,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int mLevel;
|
int mLevel;
|
||||||
|
bool mAttackingOrSpell;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CreatureStats();
|
CreatureStats();
|
||||||
|
@ -124,7 +125,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
const MagicEffects & getMagicEffects() const;
|
const MagicEffects & getMagicEffects() const;
|
||||||
|
|
||||||
bool getAttackingOrSpell() const;
|
bool getAttackingOrSpell() const { return mAttackingOrSpell; }
|
||||||
|
|
||||||
int getLevel() const;
|
int getLevel() const;
|
||||||
|
|
||||||
|
@ -149,7 +150,7 @@ namespace MWMechanics
|
||||||
/// Set Modifier for each magic effect according to \a effects. Does not touch Base values.
|
/// Set Modifier for each magic effect according to \a effects. Does not touch Base values.
|
||||||
void modifyMagicEffects(const MagicEffects &effects);
|
void modifyMagicEffects(const MagicEffects &effects);
|
||||||
|
|
||||||
void setAttackingOrSpell(bool attackingOrSpell);
|
void setAttackingOrSpell(bool attackingOrSpell) { mAttackingOrSpell = attackingOrSpell; }
|
||||||
|
|
||||||
void setLevel(int level);
|
void setLevel(int level);
|
||||||
|
|
||||||
|
|
|
@ -1318,7 +1318,7 @@ namespace MWMechanics
|
||||||
// once the bounty has been paid.
|
// once the bounty has been paid.
|
||||||
actor.getClass().getNpcStats(actor).setCrimeId(id);
|
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);
|
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.
|
// 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.
|
// 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);
|
startCombat(victim, player);
|
||||||
|
|
||||||
// Set the crime ID, which we will use to calm down participants
|
// 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.
|
// 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.
|
// 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,
|
// 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)
|
// 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);
|
std::string script = target.getClass().getScript(target);
|
||||||
if (!script.empty() && target.getRefData().getLocals().hasVar(script, "onpchitme") && attacker == player)
|
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);
|
peaceful = (fight == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1467,7 +1467,7 @@ namespace MWMechanics
|
||||||
const MWMechanics::AiSequence& seq = target.getClass().getCreatureStats(target).getAiSequence();
|
const MWMechanics::AiSequence& seq = target.getClass().getCreatureStats(target).getAiSequence();
|
||||||
return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker)
|
return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker)
|
||||||
&& !isAggressive(target, attacker) && !seq.isEngagedWithActor()
|
&& !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)
|
void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker)
|
||||||
|
|
|
@ -64,8 +64,7 @@ namespace
|
||||||
auto& creatureStats = target.getClass().getCreatureStats(target);
|
auto& creatureStats = target.getClass().getCreatureStats(target);
|
||||||
auto stat = creatureStats.getDynamic(index);
|
auto stat = creatureStats.getDynamic(index);
|
||||||
float current = stat.getCurrent();
|
float current = stat.getCurrent();
|
||||||
stat.setModified(stat.getModified() + magnitude, 0);
|
stat.setBase(std::max(0.f, stat.getBase() + magnitude));
|
||||||
stat.setCurrentModified(stat.getCurrentModified() + magnitude);
|
|
||||||
stat.setCurrent(current + magnitude);
|
stat.setCurrent(current + magnitude);
|
||||||
creatureStats.setDynamic(index, stat);
|
creatureStats.setDynamic(index, stat);
|
||||||
}
|
}
|
||||||
|
@ -980,12 +979,10 @@ void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellPara
|
||||||
if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f)
|
if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f)
|
||||||
{
|
{
|
||||||
auto& seq = target.getClass().getCreatureStats(target).getAiSequence();
|
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();
|
return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow && static_cast<const MWMechanics::AiFollow*>(package.get())->isCommanded();
|
||||||
});
|
});
|
||||||
if(it != seq.end())
|
|
||||||
seq.erase(it);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ESM::MagicEffect::ExtraSpell:
|
case ESM::MagicEffect::ExtraSpell:
|
||||||
|
|
|
@ -5,174 +5,41 @@
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
template<typename T>
|
template<typename T>
|
||||||
Stat<T>::Stat() : mBase (0), mModified (0), mCurrentModified (0) {}
|
Stat<T>::Stat() : mBase (0), mModifier (0) {}
|
||||||
template<typename T>
|
template<typename T>
|
||||||
Stat<T>::Stat(T base) : mBase (base), mModified (base), mCurrentModified (base) {}
|
Stat<T>::Stat(T base, T modified) : mBase (base), mModifier (modified) {}
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
T Stat<T>::getModified(bool capped) const
|
T Stat<T>::getModified(bool capped) const
|
||||||
{
|
{
|
||||||
if(!capped)
|
if(capped)
|
||||||
return mModified;
|
return std::max({}, mModifier + mBase);
|
||||||
return std::max(static_cast<T>(0), mModified);
|
return mModifier + mBase;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void Stat<T>::writeState (ESM::StatState<T>& state) const
|
void Stat<T>::writeState (ESM::StatState<T>& state) const
|
||||||
{
|
{
|
||||||
state.mBase = mBase;
|
state.mBase = mBase;
|
||||||
state.mMod = mCurrentModified;
|
state.mMod = mModifier;
|
||||||
}
|
}
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void Stat<T>::readState (const ESM::StatState<T>& state)
|
void Stat<T>::readState (const ESM::StatState<T>& state)
|
||||||
{
|
{
|
||||||
mBase = state.mBase;
|
mBase = state.mBase;
|
||||||
mModified = state.mBase;
|
mModifier = state.mMod;
|
||||||
mCurrentModified = state.mMod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
DynamicStat<T>::DynamicStat() : mStatic (0), mCurrent (0) {}
|
DynamicStat<T>::DynamicStat() : mStatic(0, 0), mCurrent(0) {}
|
||||||
template<typename T>
|
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>
|
template<typename T>
|
||||||
DynamicStat<T>::DynamicStat(T base, T modified, T current) : mStatic(base, modified), mCurrent (current) {}
|
DynamicStat<T>::DynamicStat(T base, T modified, T current) : mStatic(base, modified), mCurrent (current) {}
|
||||||
template<typename T>
|
template<typename T>
|
||||||
DynamicStat<T>::DynamicStat(const Stat<T> &stat, T current) : mStatic(stat), mCurrent (current) {}
|
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>
|
template<typename T>
|
||||||
void DynamicStat<T>::setCurrent (const T& value, bool allowDecreaseBelowZero, bool allowIncreaseAboveModified)
|
void DynamicStat<T>::setCurrent (const T& value, bool allowDecreaseBelowZero, bool allowIncreaseAboveModified)
|
||||||
{
|
{
|
||||||
|
@ -197,22 +64,18 @@ namespace MWMechanics
|
||||||
mCurrent = 0;
|
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>
|
template<typename T>
|
||||||
void DynamicStat<T>::setCurrentModifier(const T& modifier, bool allowCurrentDecreaseBelowZero)
|
T DynamicStat<T>::getRatio(bool nanIsZero) const
|
||||||
{
|
{
|
||||||
T diff = modifier - mStatic.getCurrentModifier();
|
T modified = getModified();
|
||||||
mStatic.setCurrentModifier(modifier);
|
if(modified == T{})
|
||||||
|
{
|
||||||
// The (modifier > 0) check here allows increase over modified only if the modifier is positive (a fortify effect is active).
|
if(nanIsZero)
|
||||||
setCurrent (getCurrent() + diff, allowCurrentDecreaseBelowZero, (modifier > 0));
|
return modified;
|
||||||
|
return {1};
|
||||||
|
}
|
||||||
|
return getCurrent() / modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
|
|
@ -16,38 +16,22 @@ namespace MWMechanics
|
||||||
class Stat
|
class Stat
|
||||||
{
|
{
|
||||||
T mBase;
|
T mBase;
|
||||||
T mModified;
|
T mModifier;
|
||||||
T mCurrentModified;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
typedef T Type;
|
typedef T Type;
|
||||||
|
|
||||||
Stat();
|
Stat();
|
||||||
Stat(T base);
|
|
||||||
Stat(T base, T modified);
|
Stat(T base, T modified);
|
||||||
|
|
||||||
const T& getBase() const;
|
const T& getBase() const { return mBase; };
|
||||||
|
|
||||||
T getModified(bool capped = true) const;
|
T getModified(bool capped = true) const;
|
||||||
T getCurrentModified() const;
|
T getModifier() const { return mModifier; };
|
||||||
T getModifier() const;
|
|
||||||
T getCurrentModifier() const;
|
|
||||||
|
|
||||||
/// Set base and modified to \a value.
|
void setBase(const T& value) { mBase = value; };
|
||||||
void set (const T& value);
|
|
||||||
|
|
||||||
/// Set base and adjust modified accordingly.
|
void setModifier(const T& modifier) { mModifier = modifier; };
|
||||||
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 writeState (ESM::StatState<T>& state) const;
|
void writeState (ESM::StatState<T>& state) const;
|
||||||
void readState (const ESM::StatState<T>& state);
|
void readState (const ESM::StatState<T>& state);
|
||||||
|
@ -57,7 +41,7 @@ namespace MWMechanics
|
||||||
inline bool operator== (const Stat<T>& left, const Stat<T>& right)
|
inline bool operator== (const Stat<T>& left, const Stat<T>& right)
|
||||||
{
|
{
|
||||||
return left.getBase()==right.getBase() &&
|
return left.getBase()==right.getBase() &&
|
||||||
left.getModified()==right.getModified();
|
left.getModifier()==right.getModifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
@ -80,27 +64,18 @@ namespace MWMechanics
|
||||||
DynamicStat(T base, T modified, T current);
|
DynamicStat(T base, T modified, T current);
|
||||||
DynamicStat(const Stat<T> &stat, T current);
|
DynamicStat(const Stat<T> &stat, T current);
|
||||||
|
|
||||||
const T& getBase() const;
|
const T& getBase() const { return mStatic.getBase(); };
|
||||||
T getModified() const;
|
T getModified(bool capped = true) const { return mStatic.getModified(capped); };
|
||||||
T getCurrentModified() const;
|
const T& getCurrent() const { return mCurrent; };
|
||||||
const T& getCurrent() const;
|
T getRatio(bool nanIsZero = true) const;
|
||||||
|
|
||||||
/// Set base, modified and current to \a value.
|
/// Set base and adjust current accordingly.
|
||||||
void set (const T& value);
|
void setBase(const T& value) { mStatic.setBase(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 setCurrent (const T& value, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false);
|
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 writeState (ESM::StatState<T>& state) const;
|
||||||
void readState (const ESM::StatState<T>& state);
|
void readState (const ESM::StatState<T>& state);
|
||||||
|
@ -110,7 +85,7 @@ namespace MWMechanics
|
||||||
inline bool operator== (const DynamicStat<T>& left, const DynamicStat<T>& right)
|
inline bool operator== (const DynamicStat<T>& left, const DynamicStat<T>& right)
|
||||||
{
|
{
|
||||||
return left.getBase()==right.getBase() &&
|
return left.getBase()==right.getBase() &&
|
||||||
left.getModified()==right.getModified() &&
|
left.getModifier()==right.getModifier() &&
|
||||||
left.getCurrent()==right.getCurrent();
|
left.getCurrent()==right.getCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include <BulletCollision/BroadphaseCollision/btDbvtBroadphase.h>
|
#include <BulletCollision/BroadphaseCollision/btDbvtBroadphase.h>
|
||||||
#include <BulletCollision/CollisionShapes/btCollisionShape.h>
|
#include <BulletCollision/CollisionShapes/btCollisionShape.h>
|
||||||
|
|
||||||
|
@ -111,17 +113,49 @@ namespace
|
||||||
return ptr.getPosition() * interpolationFactor + ptr.getPreviousPosition() * (1.f - interpolationFactor);
|
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
|
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
|
struct InitPosition
|
||||||
{
|
{
|
||||||
const btCollisionWorld* mCollisionWorld;
|
const btCollisionWorld* mCollisionWorld;
|
||||||
void operator()(MWPhysics::ActorSimulation& sim) const
|
void operator()(MWPhysics::ActorSimulation& sim) const
|
||||||
{
|
{
|
||||||
auto& [actorPtr, frameData] = sim;
|
auto locked = sim.lock();
|
||||||
const auto actor = actorPtr.lock();
|
if (!locked.has_value())
|
||||||
if (actor == nullptr)
|
|
||||||
return;
|
return;
|
||||||
|
auto& [actor, frameDataRef] = *locked;
|
||||||
|
auto& frameData = frameDataRef.get();
|
||||||
actor->applyOffsetChange();
|
actor->applyOffsetChange();
|
||||||
frameData.mPosition = actor->getPosition();
|
frameData.mPosition = actor->getPosition();
|
||||||
if (frameData.mWaterCollision && frameData.mPosition.z() < frameData.mWaterlevel && actor->canMoveToWaterSurface(frameData.mWaterlevel, mCollisionWorld))
|
if (frameData.mWaterCollision && frameData.mPosition.z() < frameData.mWaterlevel && actor->canMoveToWaterSurface(frameData.mWaterlevel, mCollisionWorld))
|
||||||
|
@ -138,7 +172,7 @@ namespace
|
||||||
frameData.mStuckFrames = actor->getStuckFrames();
|
frameData.mStuckFrames = actor->getStuckFrames();
|
||||||
frameData.mLastStuckPosition = actor->getLastStuckPosition();
|
frameData.mLastStuckPosition = actor->getLastStuckPosition();
|
||||||
}
|
}
|
||||||
void operator()(MWPhysics::ProjectileSimulation& sim) const
|
void operator()(MWPhysics::ProjectileSimulation& /*sim*/) const
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -146,11 +180,11 @@ namespace
|
||||||
struct PreStep
|
struct PreStep
|
||||||
{
|
{
|
||||||
btCollisionWorld* mCollisionWorld;
|
btCollisionWorld* mCollisionWorld;
|
||||||
void operator()(MWPhysics::ActorSimulation& sim) const
|
void operator()(const LockedActorSimulation& sim) const
|
||||||
{
|
{
|
||||||
MWPhysics::MovementSolver::unstuck(sim.second, mCollisionWorld);
|
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
|
struct UpdatePosition
|
||||||
{
|
{
|
||||||
btCollisionWorld* mCollisionWorld;
|
btCollisionWorld* mCollisionWorld;
|
||||||
void operator()(MWPhysics::ActorSimulation& sim) const
|
void operator()(const LockedActorSimulation& sim) const
|
||||||
{
|
{
|
||||||
auto& [actorPtr, frameData] = sim;
|
auto& [actor, frameDataRef] = sim;
|
||||||
const auto actor = actorPtr.lock();
|
auto& frameData = frameDataRef.get();
|
||||||
if (actor == nullptr)
|
|
||||||
return;
|
|
||||||
if (actor->setPosition(frameData.mPosition))
|
if (actor->setPosition(frameData.mPosition))
|
||||||
{
|
{
|
||||||
frameData.mPosition = actor->getPosition(); // account for potential position change made by script
|
frameData.mPosition = actor->getPosition(); // account for potential position change made by script
|
||||||
|
@ -171,12 +203,10 @@ namespace
|
||||||
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
|
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void operator()(MWPhysics::ProjectileSimulation& sim) const
|
void operator()(const LockedProjectileSimulation& sim) const
|
||||||
{
|
{
|
||||||
auto& [projPtr, frameData] = sim;
|
auto& [proj, frameDataRef] = sim;
|
||||||
const auto proj = projPtr.lock();
|
auto& frameData = frameDataRef.get();
|
||||||
if (proj == nullptr)
|
|
||||||
return;
|
|
||||||
proj->setPosition(frameData.mPosition);
|
proj->setPosition(frameData.mPosition);
|
||||||
proj->updateCollisionObjectPosition();
|
proj->updateCollisionObjectPosition();
|
||||||
mCollisionWorld->updateSingleAabb(proj->getCollisionObject());
|
mCollisionWorld->updateSingleAabb(proj->getCollisionObject());
|
||||||
|
@ -188,11 +218,11 @@ namespace
|
||||||
const float mPhysicsDt;
|
const float mPhysicsDt;
|
||||||
const btCollisionWorld* mCollisionWorld;
|
const btCollisionWorld* mCollisionWorld;
|
||||||
const MWPhysics::WorldFrameData& mWorldFrameData;
|
const MWPhysics::WorldFrameData& mWorldFrameData;
|
||||||
void operator()(MWPhysics::ActorSimulation& sim) const
|
void operator()(const LockedActorSimulation& sim) const
|
||||||
{
|
{
|
||||||
MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld, mWorldFrameData);
|
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);
|
MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld);
|
||||||
}
|
}
|
||||||
|
@ -206,10 +236,11 @@ namespace
|
||||||
const MWPhysics::PhysicsTaskScheduler* scheduler;
|
const MWPhysics::PhysicsTaskScheduler* scheduler;
|
||||||
void operator()(MWPhysics::ActorSimulation& sim) const
|
void operator()(MWPhysics::ActorSimulation& sim) const
|
||||||
{
|
{
|
||||||
auto& [actorPtr, frameData] = sim;
|
auto locked = sim.lock();
|
||||||
const auto actor = actorPtr.lock();
|
if (!locked.has_value())
|
||||||
if (actor == nullptr)
|
|
||||||
return;
|
return;
|
||||||
|
auto& [actor, frameDataRef] = *locked;
|
||||||
|
auto& frameData = frameDataRef.get();
|
||||||
auto ptr = actor->getPtr();
|
auto ptr = actor->getPtr();
|
||||||
|
|
||||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||||
|
@ -241,10 +272,10 @@ namespace
|
||||||
}
|
}
|
||||||
void operator()(MWPhysics::ProjectileSimulation& sim) const
|
void operator()(MWPhysics::ProjectileSimulation& sim) const
|
||||||
{
|
{
|
||||||
auto& [projPtr, frameData] = sim;
|
auto locked = sim.lock();
|
||||||
const auto proj = projPtr.lock();
|
if (!locked.has_value())
|
||||||
if (proj == nullptr)
|
|
||||||
return;
|
return;
|
||||||
|
auto& [proj, frameData] = *locked;
|
||||||
proj->setSimulationPosition(::interpolateMovements(*proj, mTimeAccum, mPhysicsDt));
|
proj->setSimulationPosition(::interpolateMovements(*proj, mTimeAccum, mPhysicsDt));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -612,12 +643,10 @@ namespace MWPhysics
|
||||||
|
|
||||||
void PhysicsTaskScheduler::updateActorsPositions()
|
void PhysicsTaskScheduler::updateActorsPositions()
|
||||||
{
|
{
|
||||||
const Visitors::UpdatePosition vis{mCollisionWorld};
|
const Visitors::UpdatePosition impl{mCollisionWorld};
|
||||||
for (auto& sim : mSimulations)
|
const Visitors::WithLockedPtr<Visitors::UpdatePosition, MaybeExclusiveLock> vis{impl, mCollisionWorldMutex, mNumThreads};
|
||||||
{
|
for (Simulation& sim : mSimulations)
|
||||||
MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads);
|
|
||||||
std::visit(vis, sim);
|
std::visit(vis, sim);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2)
|
bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2)
|
||||||
|
@ -641,12 +670,10 @@ namespace MWPhysics
|
||||||
{
|
{
|
||||||
mPreStepBarrier->wait([this] { afterPreStep(); });
|
mPreStepBarrier->wait([this] { afterPreStep(); });
|
||||||
int job = 0;
|
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)
|
while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
|
||||||
{
|
|
||||||
MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads);
|
|
||||||
std::visit(vis, mSimulations[job]);
|
std::visit(vis, mSimulations[job]);
|
||||||
}
|
|
||||||
|
|
||||||
mPostStepBarrier->wait([this] { afterPostStep(); });
|
mPostStepBarrier->wait([this] { afterPostStep(); });
|
||||||
}
|
}
|
||||||
|
@ -697,12 +724,10 @@ namespace MWPhysics
|
||||||
updateAabbs();
|
updateAabbs();
|
||||||
if (!mRemainingSteps)
|
if (!mRemainingSteps)
|
||||||
return;
|
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)
|
for (auto& sim : mSimulations)
|
||||||
{
|
|
||||||
MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads);
|
|
||||||
std::visit(vis, sim);
|
std::visit(vis, sim);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PhysicsTaskScheduler::afterPostStep()
|
void PhysicsTaskScheduler::afterPostStep()
|
||||||
|
|
|
@ -112,6 +112,7 @@ namespace MWPhysics
|
||||||
assert (mShapeInstance->mCollisionShape->isCompound());
|
assert (mShapeInstance->mCollisionShape->isCompound());
|
||||||
|
|
||||||
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->mCollisionShape.get());
|
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->mCollisionShape.get());
|
||||||
|
bool result = false;
|
||||||
for (const auto& [recIndex, shapeIndex] : mShapeInstance->mAnimatedShapes)
|
for (const auto& [recIndex, shapeIndex] : mShapeInstance->mAnimatedShapes)
|
||||||
{
|
{
|
||||||
auto nodePathFound = mRecIndexToNodePath.find(recIndex);
|
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
|
// Note: we can not apply scaling here for now since we treat scaled shapes
|
||||||
// as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now
|
// as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now
|
||||||
if (!(transform == compound->getChildTransform(shapeIndex)))
|
if (!(transform == compound->getChildTransform(shapeIndex)))
|
||||||
|
{
|
||||||
compound->updateChildTransform(shapeIndex, transform);
|
compound->updateChildTransform(shapeIndex, transform);
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -490,7 +490,7 @@ namespace MWPhysics
|
||||||
mObjects.emplace(ptr.mRef, obj);
|
mObjects.emplace(ptr.mRef, obj);
|
||||||
|
|
||||||
if (obj->isAnimated())
|
if (obj->isAnimated())
|
||||||
mAnimatedObjects.insert(obj.get());
|
mAnimatedObjects.emplace(obj.get(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PhysicsSystem::remove(const MWWorld::Ptr &ptr)
|
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)
|
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())
|
if (animatedObject->animateCollisionShapes())
|
||||||
{
|
{
|
||||||
auto obj = mObjects.find(animatedObject->getPtr().mRef);
|
auto obj = mObjects.find(animatedObject->getPtr().mRef);
|
||||||
assert(obj != mObjects.end());
|
assert(obj != mObjects.end());
|
||||||
mTaskScheduler->updateSingleAabb(obj->second);
|
mTaskScheduler->updateSingleAabb(obj->second);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
changed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
#include <optional>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include <osg/Quat>
|
#include <osg/Quat>
|
||||||
#include <osg/BoundingBox>
|
#include <osg/BoundingBox>
|
||||||
|
@ -117,8 +119,26 @@ namespace MWPhysics
|
||||||
osg::Vec3f mStormDirection;
|
osg::Vec3f mStormDirection;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ActorSimulation = std::pair<std::weak_ptr<Actor>, ActorFrameData>;
|
template <class Ptr, class FrameData>
|
||||||
using ProjectileSimulation = std::pair<std::weak_ptr<Projectile>, ProjectileFrameData>;
|
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>;
|
using Simulation = std::variant<ActorSimulation, ProjectileSimulation>;
|
||||||
|
|
||||||
class PhysicsSystem : public RayCastingInterface
|
class PhysicsSystem : public RayCastingInterface
|
||||||
|
@ -280,7 +300,7 @@ namespace MWPhysics
|
||||||
using ObjectMap = std::unordered_map<const MWWorld::LiveCellRefBase*, std::shared_ptr<Object>>;
|
using ObjectMap = std::unordered_map<const MWWorld::LiveCellRefBase*, std::shared_ptr<Object>>;
|
||||||
ObjectMap mObjects;
|
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;
|
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->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT);
|
||||||
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT);
|
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->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->setRenderOrder(osg::Camera::PRE_RENDER);
|
||||||
|
|
||||||
camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static);
|
camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static);
|
||||||
|
@ -460,8 +460,10 @@ void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell)
|
||||||
yOffset++;
|
yOffset++;
|
||||||
mBounds.yMin() = fog->mBounds.mMinY - yOffset * mMapWorldSize;
|
mBounds.yMin() = fog->mBounds.mMinY - yOffset * mMapWorldSize;
|
||||||
}
|
}
|
||||||
mBounds.xMax() = std::max(mBounds.xMax(), fog->mBounds.mMaxX);
|
if (fog->mBounds.mMaxX > mBounds.xMax())
|
||||||
mBounds.yMax() = std::max(mBounds.yMax(), fog->mBounds.mMaxY);
|
mBounds.xMax() = fog->mBounds.mMaxX;
|
||||||
|
if (fog->mBounds.mMaxY > mBounds.yMax())
|
||||||
|
mBounds.yMax() = fog->mBounds.mMaxY;
|
||||||
|
|
||||||
if(xOffset != 0 || yOffset != 0)
|
if(xOffset != 0 || yOffset != 0)
|
||||||
Log(Debug::Warning) << "Warning: expanding fog by " << xOffset << ", " << yOffset;
|
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);
|
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
|
// color accumulation pass
|
||||||
bin->drawImplementation(renderInfo, previous);
|
bin->drawImplementation(renderInfo, previous);
|
||||||
|
|
||||||
auto primaryFBO = postProcessor->getMsaaFbo() ? postProcessor->getMsaaFbo() : postProcessor->getFbo();
|
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->pushStateSet(mStateSet);
|
||||||
state->apply();
|
state->apply();
|
||||||
|
@ -349,7 +349,7 @@ public:
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// fallback to standard depth clear when we are not rendering our main scene via an intermediate FBO
|
// 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);
|
bin->drawImplementation(renderInfo, previous);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -957,7 +957,7 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
||||||
removeIndividualPart(ESM::PRT_Weapon);
|
removeIndividualPart(ESM::PRT_Weapon);
|
||||||
// If we remove/hide weapon from player, we should reset attack animation as well
|
// If we remove/hide weapon from player, we should reset attack animation as well
|
||||||
if (mPtr == MWMechanics::getPlayer())
|
if (mPtr == MWMechanics::getPlayer())
|
||||||
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
|
mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateHolsteredWeapon(!mShowWeapons);
|
updateHolsteredWeapon(!mShowWeapons);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "postprocessor.hpp"
|
#include "postprocessor.hpp"
|
||||||
|
|
||||||
|
#include <SDL_opengl_glext.h>
|
||||||
|
|
||||||
#include <osg/Group>
|
#include <osg/Group>
|
||||||
#include <osg/Camera>
|
#include <osg/Camera>
|
||||||
#include <osg/Callback>
|
#include <osg/Callback>
|
||||||
|
@ -155,7 +157,7 @@ namespace MWRender
|
||||||
PostProcessor::PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode)
|
PostProcessor::PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode)
|
||||||
: mViewer(viewer)
|
: mViewer(viewer)
|
||||||
, mRootNode(new osg::Group)
|
, mRootNode(new osg::Group)
|
||||||
, mDepthFormat(GL_DEPTH_COMPONENT24)
|
, mDepthFormat(GL_DEPTH24_STENCIL8_EXT)
|
||||||
{
|
{
|
||||||
bool softParticles = Settings::Manager::getBool("soft particles", "Shaders");
|
bool softParticles = Settings::Manager::getBool("soft particles", "Shaders");
|
||||||
|
|
||||||
|
@ -183,9 +185,9 @@ namespace MWRender
|
||||||
if (SceneUtil::AutoDepth::isReversed())
|
if (SceneUtil::AutoDepth::isReversed())
|
||||||
{
|
{
|
||||||
if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float"))
|
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"))
|
else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float"))
|
||||||
mDepthFormat = GL_DEPTH_COMPONENT32F_NV;
|
mDepthFormat = GL_DEPTH32F_STENCIL8_NV;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO: Once we have post-processing implemented we want to skip this return and continue with setup.
|
// 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()->addCullCallback(new CullCallback);
|
||||||
mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
|
mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
|
||||||
mViewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, mSceneTex);
|
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()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this));
|
||||||
mViewer->getCamera()->setUserData(this);
|
mViewer->getCamera()->setUserData(this);
|
||||||
|
@ -236,7 +238,7 @@ namespace MWRender
|
||||||
|
|
||||||
mFbo = new osg::FrameBufferObject;
|
mFbo = new osg::FrameBufferObject;
|
||||||
mFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(mSceneTex));
|
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
|
// 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
|
// 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> colorRB = new osg::RenderBuffer(width, height, mSceneTex->getInternalFormat(), samples);
|
||||||
osg::ref_ptr<osg::RenderBuffer> depthRB = new osg::RenderBuffer(width, height, mDepthTex->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::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"))
|
if (const auto depthProxy = std::getenv("OPENMW_ENABLE_DEPTH_CLEAR_PROXY"))
|
||||||
|
@ -264,8 +266,8 @@ namespace MWRender
|
||||||
{
|
{
|
||||||
mDepthTex = new osg::Texture2D;
|
mDepthTex = new osg::Texture2D;
|
||||||
mDepthTex->setTextureSize(width, height);
|
mDepthTex->setTextureSize(width, height);
|
||||||
mDepthTex->setSourceFormat(GL_DEPTH_COMPONENT);
|
mDepthTex->setSourceFormat(GL_DEPTH_STENCIL_EXT);
|
||||||
mDepthTex->setSourceType(SceneUtil::isFloatingPointDepthFormat(getDepthFormat()) ? GL_FLOAT : GL_UNSIGNED_INT);
|
mDepthTex->setSourceType(SceneUtil::isFloatingPointDepthFormat(getDepthFormat()) ? GL_FLOAT_32_UNSIGNED_INT_24_8_REV : GL_UNSIGNED_INT_24_8_EXT);
|
||||||
mDepthTex->setInternalFormat(mDepthFormat);
|
mDepthTex->setInternalFormat(mDepthFormat);
|
||||||
mDepthTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
|
mDepthTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
|
||||||
mDepthTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);
|
mDepthTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);
|
||||||
|
|
|
@ -538,6 +538,8 @@ namespace MWRender
|
||||||
SceneUtil::setCameraClearDepth(mViewer->getCamera());
|
SceneUtil::setCameraClearDepth(mViewer->getCamera());
|
||||||
|
|
||||||
updateProjectionMatrix();
|
updateProjectionMatrix();
|
||||||
|
|
||||||
|
mViewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderingManager::~RenderingManager()
|
RenderingManager::~RenderingManager()
|
||||||
|
@ -656,11 +658,10 @@ namespace MWRender
|
||||||
if (relativeLuminance < mMinimumAmbientLuminance)
|
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
|
// 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)
|
if (ambient.r() == 0.f && ambient.g() == 0.f && ambient.b() == 0.f)
|
||||||
ambient = osg::Vec4(mMinimumAmbientLuminance, mMinimumAmbientLuminance, mMinimumAmbientLuminance, ambient.a());
|
ambient = osg::Vec4(mMinimumAmbientLuminance, mMinimumAmbientLuminance, mMinimumAmbientLuminance, ambient.a());
|
||||||
else
|
else
|
||||||
ambient *= targetBrightnessIncreaseFactor;
|
ambient *= mMinimumAmbientLuminance / relativeLuminance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -348,7 +348,7 @@ namespace MWRender
|
||||||
|
|
||||||
rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & ~(Mask_GUI|Mask_FirstPerson));
|
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);
|
renderCameraToImage(rttCamera.get(),image,w,h);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "../mwbase/scriptmanager.hpp"
|
#include "../mwbase/scriptmanager.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
#include "../mwbase/inputmanager.hpp"
|
#include "../mwbase/inputmanager.hpp"
|
||||||
|
#include "../mwbase/luamanager.hpp"
|
||||||
|
|
||||||
#include "../mwworld/action.hpp"
|
#include "../mwworld/action.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
|
@ -417,6 +418,7 @@ namespace MWScript
|
||||||
|
|
||||||
void InterpreterContext::executeActivation(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor)
|
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));
|
std::shared_ptr<MWWorld::Action> action = (ptr.getClass().activate(ptr, actor));
|
||||||
action->execute (actor);
|
action->execute (actor);
|
||||||
if (action->getTarget() != MWWorld::Ptr() && action->getTarget() != ptr)
|
if (action->getTarget() != MWWorld::Ptr() && action->getTarget() != ptr)
|
||||||
|
|
|
@ -218,8 +218,8 @@ namespace MWScript
|
||||||
MWMechanics::DynamicStat<float> stat (ptr.getClass().getCreatureStats (ptr)
|
MWMechanics::DynamicStat<float> stat (ptr.getClass().getCreatureStats (ptr)
|
||||||
.getDynamic (mIndex));
|
.getDynamic (mIndex));
|
||||||
|
|
||||||
stat.setModified (value, 0);
|
stat.setBase(value);
|
||||||
stat.setCurrent(value);
|
stat.setCurrent(stat.getModified(false), true, true);
|
||||||
|
|
||||||
ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat);
|
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)
|
float current = stat.getCurrent();
|
||||||
.getDynamic (mIndex));
|
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);
|
stats.setDynamic (mIndex, stat);
|
||||||
stat.setCurrentModified (diff + stat.getCurrentModified());
|
|
||||||
|
|
||||||
stat.setCurrent (diff + current);
|
|
||||||
|
|
||||||
ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -325,17 +324,9 @@ namespace MWScript
|
||||||
void execute (Interpreter::Runtime& runtime) override
|
void execute (Interpreter::Runtime& runtime) override
|
||||||
{
|
{
|
||||||
MWWorld::Ptr ptr = R()(runtime);
|
MWWorld::Ptr ptr = R()(runtime);
|
||||||
|
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||||
|
|
||||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
|
runtime.push(stats.getDynamic(mIndex).getRatio());
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -185,6 +185,11 @@ namespace
|
||||||
else if constexpr (std::is_same_v<T, ESM::NPC>)
|
else if constexpr (std::is_same_v<T, ESM::NPC>)
|
||||||
MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory, &state.mNpcStats);
|
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())
|
if (state.mRef.mRefNum.hasContentFile())
|
||||||
{
|
{
|
||||||
|
|
|
@ -198,7 +198,11 @@ namespace MWWorld
|
||||||
for(std::size_t i = 0; i < ESM::Attribute::Length; ++i)
|
for(std::size_t i = 0; i < ESM::Attribute::Length; ++i)
|
||||||
creatureStats.mAttributes[i].mMod = 0.f;
|
creatureStats.mAttributes[i].mMod = 0.f;
|
||||||
for(std::size_t i = 0; i < 3; ++i)
|
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)
|
for(std::size_t i = 0; i < 4; ++i)
|
||||||
creatureStats.mAiSettings[i].mMod = 0.f;
|
creatureStats.mAiSettings[i].mMod = 0.f;
|
||||||
if(npcStats)
|
if(npcStats)
|
||||||
|
@ -207,4 +211,13 @@ namespace MWWorld
|
||||||
npcStats->mSkills[i].mMod = 0.f;
|
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,
|
void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory,
|
||||||
ESM::NpcStats* npcStats = nullptr);
|
ESM::NpcStats* npcStats = nullptr);
|
||||||
|
|
||||||
|
void convertStats(ESM::CreatureStats& creatureStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -39,7 +39,6 @@ namespace MWWorld
|
||||||
mTeleported(false),
|
mTeleported(false),
|
||||||
mCurrentCrimeId(-1),
|
mCurrentCrimeId(-1),
|
||||||
mPaidCrimeId(-1),
|
mPaidCrimeId(-1),
|
||||||
mAttackingOrSpell(false),
|
|
||||||
mJumping(false)
|
mJumping(false)
|
||||||
{
|
{
|
||||||
ESM::CellRef cellRef;
|
ESM::CellRef cellRef;
|
||||||
|
@ -266,12 +265,7 @@ namespace MWWorld
|
||||||
|
|
||||||
void Player::setAttackingOrSpell(bool attackingOrSpell)
|
void Player::setAttackingOrSpell(bool attackingOrSpell)
|
||||||
{
|
{
|
||||||
mAttackingOrSpell = attackingOrSpell;
|
getPlayer().getClass().getCreatureStats(getPlayer()).setAttackingOrSpell(attackingOrSpell);
|
||||||
}
|
|
||||||
|
|
||||||
bool Player::getAttackingOrSpell() const
|
|
||||||
{
|
|
||||||
return mAttackingOrSpell;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::setJumping(bool jumping)
|
void Player::setJumping(bool jumping)
|
||||||
|
@ -314,7 +308,6 @@ namespace MWWorld
|
||||||
mAutoMove = false;
|
mAutoMove = false;
|
||||||
mForwardBackward = 0;
|
mForwardBackward = 0;
|
||||||
mTeleported = false;
|
mTeleported = false;
|
||||||
mAttackingOrSpell = false;
|
|
||||||
mJumping = false;
|
mJumping = false;
|
||||||
mCurrentCrimeId = -1;
|
mCurrentCrimeId = -1;
|
||||||
mPaidCrimeId = -1;
|
mPaidCrimeId = -1;
|
||||||
|
@ -390,6 +383,8 @@ namespace MWWorld
|
||||||
}
|
}
|
||||||
if (reader.getFormat() < 17)
|
if (reader.getFormat() < 17)
|
||||||
convertMagicEffects(player.mObject.mCreatureStats, player.mObject.mInventory, &player.mObject.mNpcStats);
|
convertMagicEffects(player.mObject.mCreatureStats, player.mObject.mInventory, &player.mObject.mNpcStats);
|
||||||
|
else if(reader.getFormat() < 20)
|
||||||
|
convertStats(player.mObject.mCreatureStats);
|
||||||
|
|
||||||
if (!player.mObject.mEnabled)
|
if (!player.mObject.mEnabled)
|
||||||
{
|
{
|
||||||
|
|
|
@ -56,7 +56,6 @@ namespace MWWorld
|
||||||
float mSaveSkills[ESM::Skill::Length];
|
float mSaveSkills[ESM::Skill::Length];
|
||||||
float mSaveAttributes[ESM::Attribute::Length];
|
float mSaveAttributes[ESM::Attribute::Length];
|
||||||
|
|
||||||
bool mAttackingOrSpell;
|
|
||||||
bool mJumping;
|
bool mJumping;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -112,7 +111,6 @@ namespace MWWorld
|
||||||
void setTeleported(bool teleported);
|
void setTeleported(bool teleported);
|
||||||
|
|
||||||
void setAttackingOrSpell(bool attackingOrSpell);
|
void setAttackingOrSpell(bool attackingOrSpell);
|
||||||
bool getAttackingOrSpell() const;
|
|
||||||
|
|
||||||
void setJumping(bool jumping);
|
void setJumping(bool jumping);
|
||||||
bool getJumping() const;
|
bool getJumping() const;
|
||||||
|
|
|
@ -360,7 +360,7 @@ namespace MWWorld
|
||||||
mActiveCells.erase(cell);
|
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;
|
using DetourNavigator::HeightfieldShape;
|
||||||
|
|
||||||
|
@ -461,7 +461,7 @@ namespace MWWorld
|
||||||
mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f);
|
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))
|
if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
|
||||||
mRendering.configureAmbient(cell->getCell());
|
mRendering.configureAmbient(cell->getCell());
|
||||||
|
@ -534,6 +534,8 @@ namespace MWWorld
|
||||||
unloadCell (cell);
|
unloadCell (cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mNavigator.updateBounds(pos);
|
||||||
|
|
||||||
mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY);
|
mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY);
|
||||||
osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter);
|
osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter);
|
||||||
mRendering.setActiveGrid(newGrid);
|
mRendering.setActiveGrid(newGrid);
|
||||||
|
@ -608,7 +610,7 @@ namespace MWWorld
|
||||||
if (!isCellInCollection(x, y, mActiveCells))
|
if (!isCellInCollection(x, y, mActiveCells))
|
||||||
{
|
{
|
||||||
CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y);
|
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())+")...");
|
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);
|
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();
|
auto iter = mActiveCells.begin();
|
||||||
while (iter != mActiveCells.end())
|
while (iter != mActiveCells.end())
|
||||||
|
@ -686,7 +688,9 @@ namespace MWWorld
|
||||||
loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")...");
|
loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")...");
|
||||||
|
|
||||||
CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName);
|
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();
|
auto iter = mActiveCells.begin();
|
||||||
while (iter != mActiveCells.end())
|
while (iter != mActiveCells.end())
|
||||||
|
@ -819,11 +823,11 @@ namespace MWWorld
|
||||||
|
|
||||||
loadingListener->setProgressRange(cell->count());
|
loadingListener->setProgressRange(cell->count());
|
||||||
|
|
||||||
mNavigator.updatePlayerPosition(position.asVec3());
|
mNavigator.updateBounds(position.asVec3());
|
||||||
|
|
||||||
// Load cell.
|
// Load cell.
|
||||||
mPagedRefs.clear();
|
mPagedRefs.clear();
|
||||||
loadCell(cell, loadingListener, changeEvent);
|
loadCell(cell, loadingListener, changeEvent, position.asVec3());
|
||||||
|
|
||||||
changePlayerCell(cell, position, adjustPlayerPos);
|
changePlayerCell(cell, position, adjustPlayerPos);
|
||||||
|
|
||||||
|
@ -854,8 +858,6 @@ namespace MWWorld
|
||||||
if (changeEvent)
|
if (changeEvent)
|
||||||
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5);
|
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5);
|
||||||
|
|
||||||
mNavigator.updatePlayerPosition(position.asVec3());
|
|
||||||
|
|
||||||
changeCellGrid(position.asVec3(), x, y, changeEvent);
|
changeCellGrid(position.asVec3(), x, y, changeEvent);
|
||||||
|
|
||||||
CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y);
|
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;
|
osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const;
|
||||||
|
|
||||||
void unloadCell(CellStore* cell);
|
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:
|
public:
|
||||||
|
|
||||||
|
|
|
@ -1525,7 +1525,12 @@ namespace MWWorld
|
||||||
|
|
||||||
void World::updateNavigator()
|
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)
|
for (const auto& door : mDoorStates)
|
||||||
if (const auto object = mPhysics->getObject(door.first))
|
if (const auto object = mPhysics->getObject(door.first))
|
||||||
|
@ -3827,7 +3832,8 @@ namespace MWWorld
|
||||||
|
|
||||||
if (object.getRefData().activate())
|
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);
|
action->execute (actor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,8 @@ if (GTEST_FOUND AND GMOCK_FOUND)
|
||||||
esmloader/esmdata.cpp
|
esmloader/esmdata.cpp
|
||||||
|
|
||||||
files/hash.cpp
|
files/hash.cpp
|
||||||
|
|
||||||
|
toutf8/toutf8.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
|
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
|
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()
|
endif()
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace
|
||||||
osg::ref_ptr<Resource::BulletShapeInstance>(new Resource::BulletShapeInstance(bulletShape)),
|
osg::ref_ptr<Resource::BulletShapeInstance>(new Resource::BulletShapeInstance(bulletShape)),
|
||||||
shape, objectTransform
|
shape, objectTransform
|
||||||
);
|
);
|
||||||
recastMeshManager.addObject(id, collisionShape, btTransform::getIdentity(), AreaType_ground);
|
recastMeshManager.addObject(id, collisionShape, btTransform::getIdentity(), AreaType_ground, [] (auto) {});
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DetourNavigatorAsyncNavMeshUpdaterTest : Test
|
struct DetourNavigatorAsyncNavMeshUpdaterTest : Test
|
||||||
|
|
|
@ -37,35 +37,35 @@ namespace
|
||||||
|
|
||||||
TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_in_single_tile_should_return_one_tile)
|
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)));
|
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_x_bounds_in_two_tiles_should_return_two_tiles)
|
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)));
|
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)
|
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)));
|
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(0, 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_works_only_for_x_and_y_coordinates)
|
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)));
|
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_should_work_with_negative_coordinates)
|
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(
|
EXPECT_THAT(mTilesPositions, ElementsAre(
|
||||||
TilePosition(-1, -1),
|
TilePosition(-1, -1),
|
||||||
|
@ -79,7 +79,7 @@ namespace
|
||||||
{
|
{
|
||||||
mSettings.mBorderSize = 1;
|
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(
|
EXPECT_THAT(mTilesPositions, ElementsAre(
|
||||||
TilePosition(-1, -1),
|
TilePosition(-1, -1),
|
||||||
|
@ -98,7 +98,7 @@ namespace
|
||||||
{
|
{
|
||||||
mSettings.mRecastScaleFactor = 0.5;
|
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)));
|
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1132,4 +1132,16 @@ namespace
|
||||||
Vec3fEq(306, 56.66666412353515625, -2.6667339801788330078125)
|
Vec3fEq(306, 56.66666412353515625, -2.6667339801788330078125)
|
||||||
)) << mPath;
|
)) << 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>
|
#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
|
namespace
|
||||||
{
|
{
|
||||||
template <class T>
|
template <class T>
|
||||||
|
|
|
@ -16,7 +16,8 @@ namespace
|
||||||
struct DetourNavigatorTileCachedRecastMeshManagerTest : Test
|
struct DetourNavigatorTileCachedRecastMeshManagerTest : Test
|
||||||
{
|
{
|
||||||
RecastSettings mSettings;
|
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 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::BulletShape> mShape = new Resource::BulletShape;
|
||||||
const osg::ref_ptr<const Resource::BulletShapeInstance> mInstance = new Resource::BulletShapeInstance(mShape);
|
const osg::ref_ptr<const Resource::BulletShapeInstance> mInstance = new Resource::BulletShapeInstance(mShape);
|
||||||
|
@ -29,9 +30,14 @@ namespace
|
||||||
mSettings.mTileSize = 64;
|
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);
|
TileCachedRecastMeshManager manager(mSettings);
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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)
|
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false)
|
||||||
|
@ -68,8 +74,8 @@ namespace
|
||||||
TileCachedRecastMeshManager manager(mSettings);
|
TileCachedRecastMeshManager manager(mSettings);
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
|
EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles)
|
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles)
|
||||||
|
@ -78,12 +84,26 @@ namespace
|
||||||
manager.setWorldspace("worldspace");
|
manager.setWorldspace("worldspace");
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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 x = -1; x < 1; ++x)
|
||||||
for (int y = -1; y < 1; ++y)
|
for (int y = -1; y < 1; ++y)
|
||||||
ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr);
|
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)
|
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles)
|
||||||
{
|
{
|
||||||
TileCachedRecastMeshManager manager(mSettings);
|
TileCachedRecastMeshManager manager(mSettings);
|
||||||
|
@ -94,14 +114,17 @@ namespace
|
||||||
bounds.mMin = osg::Vec2f(-1000, -1000);
|
bounds.mMin = osg::Vec2f(-1000, -1000);
|
||||||
bounds.mMax = osg::Vec2f(1000, 1000);
|
bounds.mMax = osg::Vec2f(1000, 1000);
|
||||||
manager.setBounds(bounds);
|
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,
|
EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground,
|
||||||
[&] (const auto& v) { onChangedTile(v); }));
|
[&] (const auto& ... v) { onChangedTile(v ...); }));
|
||||||
EXPECT_THAT(
|
EXPECT_THAT(mChangedTiles, ElementsAre(
|
||||||
mChangedTiles,
|
std::pair(TilePosition(-1, -1), ChangeType::add),
|
||||||
ElementsAre(TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(0, -1), TilePosition(0, 0),
|
std::pair(TilePosition(-1, 0), ChangeType::add),
|
||||||
TilePosition(1, -1), TilePosition(1, 0))
|
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)
|
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_not_changed_object_should_return_empty)
|
||||||
|
@ -109,10 +132,10 @@ namespace
|
||||||
TileCachedRecastMeshManager manager(mSettings);
|
TileCachedRecastMeshManager manager(mSettings);
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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,
|
EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground,
|
||||||
[&] (const auto& v) { onChangedTile(v); }));
|
[&] (const auto& ... v) { onChangedTile(v ...); }));
|
||||||
EXPECT_EQ(mChangedTiles, std::vector<TilePosition>());
|
EXPECT_THAT(mChangedTiles, IsEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile)
|
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile)
|
||||||
|
@ -121,7 +144,7 @@ namespace
|
||||||
manager.setWorldspace("worldspace");
|
manager.setWorldspace("worldspace");
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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, -1)), nullptr);
|
||||||
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), 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, -1)), nullptr);
|
||||||
|
@ -134,7 +157,7 @@ namespace
|
||||||
manager.setWorldspace("worldspace");
|
manager.setWorldspace("worldspace");
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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);
|
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 btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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, -1)), nullptr);
|
||||||
EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), 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, 0)), nullptr);
|
||||||
EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, -1)), 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, -1)), nullptr);
|
||||||
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), 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, -1)), nullptr);
|
||||||
|
@ -173,11 +196,11 @@ namespace
|
||||||
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
|
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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, -1)), nullptr);
|
||||||
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), 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, 0)), nullptr);
|
||||||
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr);
|
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr);
|
||||||
}
|
}
|
||||||
|
@ -188,7 +211,7 @@ namespace
|
||||||
manager.setWorldspace("worldspace");
|
manager.setWorldspace("worldspace");
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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));
|
manager.removeObject(ObjectId(&boxShape));
|
||||||
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
|
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
|
||||||
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
|
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
|
||||||
|
@ -203,13 +226,13 @@ namespace
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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, -1)), nullptr);
|
||||||
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), 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, -1)), nullptr);
|
||||||
EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), 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, -1)), nullptr);
|
||||||
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), 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, -1)), nullptr);
|
||||||
|
@ -222,7 +245,7 @@ namespace
|
||||||
const auto initialRevision = manager.getRevision();
|
const auto initialRevision = manager.getRevision();
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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);
|
EXPECT_EQ(manager.getRevision(), initialRevision + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,9 +254,9 @@ namespace
|
||||||
TileCachedRecastMeshManager manager(mSettings);
|
TileCachedRecastMeshManager manager(mSettings);
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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();
|
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);
|
EXPECT_EQ(manager.getRevision(), beforeAddRevision);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,9 +266,9 @@ namespace
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
|
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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();
|
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);
|
EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,9 +278,9 @@ namespace
|
||||||
manager.setWorldspace("worldspace");
|
manager.setWorldspace("worldspace");
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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();
|
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);
|
EXPECT_EQ(manager.getRevision(), beforeUpdateRevision);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,7 +289,7 @@ namespace
|
||||||
TileCachedRecastMeshManager manager(mSettings);
|
TileCachedRecastMeshManager manager(mSettings);
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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();
|
const auto beforeRemoveRevision = manager.getRevision();
|
||||||
manager.removeObject(ObjectId(&boxShape));
|
manager.removeObject(ObjectId(&boxShape));
|
||||||
EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1);
|
EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1);
|
||||||
|
@ -306,7 +329,7 @@ namespace
|
||||||
manager.setWorldspace("worldspace");
|
manager.setWorldspace("worldspace");
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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 osg::Vec2i cellPosition(0, 0);
|
||||||
const int cellSize = std::numeric_limits<int>::max();
|
const int cellSize = std::numeric_limits<int>::max();
|
||||||
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
|
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
|
||||||
|
@ -351,7 +374,7 @@ namespace
|
||||||
manager.setWorldspace("worldspace");
|
manager.setWorldspace("worldspace");
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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 osg::Vec2i cellPosition(0, 0);
|
||||||
const int cellSize = 8192;
|
const int cellSize = 8192;
|
||||||
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
|
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
|
||||||
|
@ -369,7 +392,7 @@ namespace
|
||||||
const int cellSize = 8192;
|
const int cellSize = 8192;
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
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.addWater(cellPosition, cellSize, 0.0f));
|
||||||
ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape)));
|
ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape)));
|
||||||
for (int x = -1; x < 12; ++x)
|
for (int x = -1; x < 12; ++x)
|
||||||
|
@ -383,10 +406,28 @@ namespace
|
||||||
manager.setWorldspace("worldspace");
|
manager.setWorldspace("worldspace");
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(nullptr, boxShape, mObjectTransform);
|
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");
|
manager.setWorldspace("other");
|
||||||
for (int x = -1; x < 1; ++x)
|
for (int x = -1; x < 1; ++x)
|
||||||
for (int y = -1; y < 1; ++y)
|
for (int y = -1; y < 1; ++y)
|
||||||
ASSERT_EQ(manager.getMesh("other", TilePosition(x, y)), nullptr);
|
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 <gtest/gtest.h>
|
||||||
#include "components/esm/esmcommon.hpp"
|
#include "components/esm/esmcommon.hpp"
|
||||||
|
#include "components/esm/defs.hpp"
|
||||||
|
|
||||||
TEST(EsmFixedString, operator__eq_ne)
|
TEST(EsmFixedString, operator__eq_ne)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
SCOPED_TRACE("asdc == asdc");
|
SCOPED_TRACE("asdc == asdc");
|
||||||
ESM::NAME name;
|
constexpr ESM::NAME name("asdc");
|
||||||
name.assign("asdc");
|
|
||||||
char s[4] = {'a', 's', 'd', 'c'};
|
char s[4] = {'a', 's', 'd', 'c'};
|
||||||
std::string ss(s, 4);
|
std::string ss(s, 4);
|
||||||
|
|
||||||
|
@ -16,8 +16,7 @@ TEST(EsmFixedString, operator__eq_ne)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
SCOPED_TRACE("asdc == asdcx");
|
SCOPED_TRACE("asdc == asdcx");
|
||||||
ESM::NAME name;
|
constexpr ESM::NAME name("asdc");
|
||||||
name.assign("asdc");
|
|
||||||
char s[5] = {'a', 's', 'd', 'c', 'x'};
|
char s[5] = {'a', 's', 'd', 'c', 'x'};
|
||||||
std::string ss(s, 5);
|
std::string ss(s, 5);
|
||||||
|
|
||||||
|
@ -27,8 +26,7 @@ TEST(EsmFixedString, operator__eq_ne)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
SCOPED_TRACE("asdc == asdc[NULL]");
|
SCOPED_TRACE("asdc == asdc[NULL]");
|
||||||
ESM::NAME name;
|
const ESM::NAME name("asdc");
|
||||||
name.assign("asdc");
|
|
||||||
char s[5] = {'a', 's', 'd', 'c', '\0'};
|
char s[5] = {'a', 's', 'd', 'c', '\0'};
|
||||||
std::string ss(s, 5);
|
std::string ss(s, 5);
|
||||||
|
|
||||||
|
@ -41,8 +39,7 @@ TEST(EsmFixedString, operator__eq_ne_const)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
SCOPED_TRACE("asdc == asdc (const)");
|
SCOPED_TRACE("asdc == asdc (const)");
|
||||||
ESM::NAME name;
|
constexpr ESM::NAME name("asdc");
|
||||||
name.assign("asdc");
|
|
||||||
const char s[4] = { 'a', 's', 'd', 'c' };
|
const char s[4] = { 'a', 's', 'd', 'c' };
|
||||||
std::string ss(s, 4);
|
std::string ss(s, 4);
|
||||||
|
|
||||||
|
@ -52,8 +49,7 @@ TEST(EsmFixedString, operator__eq_ne_const)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
SCOPED_TRACE("asdc == asdcx (const)");
|
SCOPED_TRACE("asdc == asdcx (const)");
|
||||||
ESM::NAME name;
|
constexpr ESM::NAME name("asdc");
|
||||||
name.assign("asdc");
|
|
||||||
const char s[5] = { 'a', 's', 'd', 'c', 'x' };
|
const char s[5] = { 'a', 's', 'd', 'c', 'x' };
|
||||||
std::string ss(s, 5);
|
std::string ss(s, 5);
|
||||||
|
|
||||||
|
@ -63,8 +59,7 @@ TEST(EsmFixedString, operator__eq_ne_const)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
SCOPED_TRACE("asdc == asdc[NULL] (const)");
|
SCOPED_TRACE("asdc == asdc[NULL] (const)");
|
||||||
ESM::NAME name;
|
constexpr ESM::NAME name("asdc");
|
||||||
name.assign("asdc");
|
|
||||||
const char s[5] = { 'a', 's', 'd', 'c', '\0' };
|
const char s[5] = { 'a', 's', 'd', 'c', '\0' };
|
||||||
std::string ss(s, 5);
|
std::string ss(s, 5);
|
||||||
|
|
||||||
|
@ -148,3 +143,15 @@ TEST(EsmFixedString, assignment_operator_is_supported_for_uint32)
|
||||||
value = static_cast<uint32_t>(0xFEDCBA98u);
|
value = static_cast<uint32_t>(0xFEDCBA98u);
|
||||||
EXPECT_EQ(value, static_cast<uint32_t>(0xFEDCBA98u)) << value.toInt();
|
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
|
-- should throw an error
|
||||||
incorrectRequire = function() require('counter') end,
|
incorrectRequire = function() require('counter') end,
|
||||||
modifySystemLib = function() math.sin = 5 end,
|
modifySystemLib = function() math.sin = 5 end,
|
||||||
|
modifySystemLib2 = function() math.__index.sin = 5 end,
|
||||||
rawsetSystemLib = function() rawset(math, 'sin', 5) end,
|
rawsetSystemLib = function() rawset(math, 'sin', 5) end,
|
||||||
callLoadstring = function() loadstring('print(1)') end,
|
callLoadstring = function() loadstring('print(1)') end,
|
||||||
setSqr = function() require('sqrlib').sqr = math.sin 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
|
// 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["rawsetSystemLib"]), "bad argument #1 to 'rawset' (table expected, got userdata)");
|
||||||
EXPECT_ERROR(LuaUtil::call(script["modifySystemLib"]), "a userdata value");
|
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);
|
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)"));
|
EXPECT_TRUE(get<bool>(lua, "v2 == util.vector2(3/5, 4/5)"));
|
||||||
lua.safe_script("_, len = util.vector2(0, 0):normalize()");
|
lua.safe_script("_, len = util.vector2(0, 0):normalize()");
|
||||||
EXPECT_FLOAT_EQ(get<float>(lua, "len"), 0);
|
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)
|
TEST(LuaUtilPackageTest, Vector3)
|
||||||
|
@ -68,6 +72,10 @@ namespace
|
||||||
EXPECT_TRUE(get<bool>(lua, "v2 == util.vector3(3/5, 4/5, 0)"));
|
EXPECT_TRUE(get<bool>(lua, "v2 == util.vector3(3/5, 4/5, 0)"));
|
||||||
lua.safe_script("_, len = util.vector3(0, 0, 0):normalize()");
|
lua.safe_script("_, len = util.vector3(0, 0, 0):normalize()");
|
||||||
EXPECT_FLOAT_EQ(get<float>(lua, "len"), 0);
|
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)
|
TEST(LuaUtilPackageTest, Vector4)
|
||||||
|
@ -95,6 +103,10 @@ namespace
|
||||||
EXPECT_TRUE(get<bool>(lua, "v2 == util.vector4(3/5, 0, 0, 4/5)"));
|
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()");
|
lua.safe_script("_, len = util.vector4(0, 0, 0, 0):normalize()");
|
||||||
EXPECT_FLOAT_EQ(get<float>(lua, "len"), 0);
|
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)
|
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>(), "")
|
("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")
|
->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(), ""));
|
("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);
|
boost::program_options::notify(variables);
|
||||||
|
|
||||||
|
|
159
apps/openmw_test_suite/toutf8/toutf8.cpp
Normal file
159
apps/openmw_test_suite/toutf8/toutf8.cpp
Normal file
|
@ -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
|
add_component_dir (lua_ui
|
||||||
properties widget element util layers content
|
registerscriptsettings scriptsettings
|
||||||
text textedit window image
|
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 {
|
try {
|
||||||
ESM::ESMReader fileReader;
|
ESM::ESMReader fileReader;
|
||||||
ToUTF8::Utf8Encoder encoder =
|
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString()));
|
||||||
ToUTF8::calculateEncoding(mEncoding.toStdString());
|
|
||||||
fileReader.setEncoder(&encoder);
|
fileReader.setEncoder(&encoder);
|
||||||
fileReader.open(std::string(dir.absoluteFilePath(path2).toUtf8().constData()));
|
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> 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()
|
std::ostream& getRawStdout()
|
||||||
{
|
{
|
||||||
return rawStdout ? *rawStdout : std::cout;
|
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) && 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
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[],
|
||||||
|
const std::string& appName, bool autoSetupLogging)
|
||||||
{
|
{
|
||||||
#if defined _WIN32
|
#if defined _WIN32
|
||||||
(void)Debug::attachParentConsole();
|
(void)Debug::attachParentConsole();
|
||||||
#endif
|
#endif
|
||||||
rawStdout = std::make_unique<std::ostream>(std::cout.rdbuf());
|
rawStdout = std::make_unique<std::ostream>(std::cout.rdbuf());
|
||||||
|
rawStderr = std::make_unique<std::ostream>(std::cerr.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;
|
|
||||||
#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);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log";
|
|
||||||
boost::filesystem::ofstream logfile;
|
|
||||||
|
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Files::ConfigurationManager cfgMgr;
|
Files::ConfigurationManager cfgMgr;
|
||||||
|
|
||||||
#if defined(_WIN32) && defined(_DEBUG)
|
if (autoSetupLogging)
|
||||||
// Redirect cout and cerr to VS debug output when running in debug mode
|
{
|
||||||
sb.open(Debug::DebugOutput());
|
std::ios_base::openmode mode = std::ios::out;
|
||||||
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 we are collecting a stack trace, append to existing log file
|
||||||
|
if (argc == 2 && strcmp(argv[1], crash_switch) == 0)
|
||||||
|
mode |= std::ios::app;
|
||||||
|
|
||||||
coutsb.open (Debug::Tee(logfile, oldcout));
|
setupLogging(cfgMgr.getLogPath().string(), appName, mode);
|
||||||
cerrsb.open (Debug::Tee(logfile, oldcerr));
|
}
|
||||||
|
|
||||||
std::cout.rdbuf (&coutsb);
|
|
||||||
std::cerr.rdbuf (&cerrsb);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.dmp";
|
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
|
// Restore cout and cerr
|
||||||
std::cout.rdbuf(cout_rdbuf);
|
std::cout.rdbuf(rawStdout->rdbuf());
|
||||||
std::cerr.rdbuf(cerr_rdbuf);
|
std::cerr.rdbuf(rawStderr->rdbuf());
|
||||||
Debug::CurrentDebugLevel = Debug::NoLevel;
|
Debug::CurrentDebugLevel = Debug::NoLevel;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -133,11 +133,16 @@ namespace Debug
|
||||||
std::map<Level, int> mColors;
|
std::map<Level, int> mColors;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can be used to print messages without timestamps
|
// Can be used to print messages without timestamps
|
||||||
std::ostream& getRawStdout();
|
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
|
#endif
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "navmeshtilescache.hpp"
|
#include "navmeshtilescache.hpp"
|
||||||
#include "waitconditiontype.hpp"
|
#include "waitconditiontype.hpp"
|
||||||
#include "navmeshdb.hpp"
|
#include "navmeshdb.hpp"
|
||||||
|
#include "changetype.hpp"
|
||||||
|
|
||||||
#include <osg/Vec3f>
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
|
@ -32,29 +33,6 @@ namespace Loading
|
||||||
|
|
||||||
namespace DetourNavigator
|
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
|
enum class JobState
|
||||||
{
|
{
|
||||||
Initial,
|
Initial,
|
||||||
|
|
33
components/detournavigator/changetype.hpp
Normal file
33
components/detournavigator/changetype.hpp
Normal file
|
@ -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
|
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)
|
const RecastSettings& settings)
|
||||||
{
|
{
|
||||||
osg::Vec3f min = toNavMeshCoordinates(settings, aabbMin);
|
osg::Vec2f min = toNavMeshCoordinates(settings, aabbMin);
|
||||||
osg::Vec3f max = toNavMeshCoordinates(settings, aabbMax);
|
osg::Vec2f max = toNavMeshCoordinates(settings, aabbMax);
|
||||||
|
|
||||||
const float border = getBorderSize(settings);
|
const float border = getBorderSize(settings);
|
||||||
min -= osg::Vec3f(border, border, border);
|
min -= osg::Vec2f(border, border);
|
||||||
max += osg::Vec3f(border, border, border);
|
max += osg::Vec2f(border, border);
|
||||||
|
|
||||||
TilePosition minTile = getTilePosition(settings, min);
|
TilePosition minTile = getTilePosition(settings, min);
|
||||||
TilePosition maxTile = getTilePosition(settings, max);
|
TilePosition maxTile = getTilePosition(settings, max);
|
||||||
|
@ -29,27 +29,27 @@ namespace DetourNavigator
|
||||||
if (minTile.y() > maxTile.y())
|
if (minTile.y() > maxTile.y())
|
||||||
std::swap(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,
|
TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform,
|
||||||
const TileBounds& bounds, const RecastSettings& settings)
|
const TileBounds& bounds, const RecastSettings& settings)
|
||||||
{
|
{
|
||||||
btVector3 aabbMin;
|
if (const auto intersection = getIntersection(bounds, makeObjectTileBounds(shape, transform)))
|
||||||
btVector3 aabbMax;
|
return makeTilesPositionsRange(intersection->mMin, intersection->mMax, settings);
|
||||||
shape.getAabb(transform, aabbMin, aabbMax);
|
return {};
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift,
|
TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift,
|
||||||
const RecastSettings& settings)
|
const RecastSettings& settings)
|
||||||
{
|
{
|
||||||
using Misc::Convert::toOsg;
|
|
||||||
|
|
||||||
const int halfCellSize = cellSize / 2;
|
const int halfCellSize = cellSize / 2;
|
||||||
const btTransform transform(btMatrix3x3::getIdentity(), shift);
|
const btTransform transform(btMatrix3x3::getIdentity(), shift);
|
||||||
btVector3 aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0));
|
btVector3 aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0));
|
||||||
|
@ -61,6 +61,19 @@ namespace DetourNavigator
|
||||||
aabbMax.setX(std::max(aabbMin.x(), aabbMax.x()));
|
aabbMax.setX(std::max(aabbMin.x(), aabbMax.x()));
|
||||||
aabbMax.setY(std::max(aabbMin.y(), aabbMax.y()));
|
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
|
namespace osg
|
||||||
{
|
{
|
||||||
class Vec3f;
|
class Vec2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace DetourNavigator
|
namespace DetourNavigator
|
||||||
|
@ -19,12 +19,15 @@ namespace DetourNavigator
|
||||||
|
|
||||||
struct TilesPositionsRange
|
struct TilesPositionsRange
|
||||||
{
|
{
|
||||||
TilePosition mMin;
|
TilePosition mBegin;
|
||||||
TilePosition mMax;
|
TilePosition mEnd;
|
||||||
};
|
};
|
||||||
|
|
||||||
TilesPositionsRange makeTilesPositionsRange(const osg::Vec3f& aabbMin,
|
TilesPositionsRange makeTilesPositionsRange(const osg::Vec2f& aabbMin,
|
||||||
const osg::Vec3f& aabbMax, const RecastSettings& settings);
|
const osg::Vec2f& aabbMax, const RecastSettings& settings);
|
||||||
|
|
||||||
|
TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape,
|
||||||
|
const btTransform& transform, const RecastSettings& settings);
|
||||||
|
|
||||||
TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape,
|
TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape,
|
||||||
const btTransform& transform, const TileBounds& bounds, const RecastSettings& settings);
|
const btTransform& transform, const TileBounds& bounds, const RecastSettings& settings);
|
||||||
|
@ -33,12 +36,25 @@ namespace DetourNavigator
|
||||||
const RecastSettings& settings);
|
const RecastSettings& settings);
|
||||||
|
|
||||||
template <class Callback>
|
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 tileX = range.mBegin.x(); tileX < range.mEnd.x(); ++tileX)
|
||||||
for (int tileY = range.mMin.y(); tileY <= range.mMax.y(); ++tileY)
|
for (int tileY = range.mBegin.y(); tileY < range.mEnd.y(); ++tileY)
|
||||||
callback(TilePosition {tileX, 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
|
#endif
|
||||||
|
|
|
@ -83,6 +83,12 @@ namespace DetourNavigator
|
||||||
*/
|
*/
|
||||||
virtual void setWorldspace(std::string_view worldspace) = 0;
|
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
|
* @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
|
* @param id is used to distinguish different objects
|
||||||
|
|
|
@ -38,6 +38,11 @@ namespace DetourNavigator
|
||||||
mNavMeshManager.setWorldspace(worldspace);
|
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)
|
bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform)
|
||||||
{
|
{
|
||||||
const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform);
|
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));
|
const TilePosition tilePosition = getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition));
|
||||||
if (mLastPlayerPosition.has_value() && *mLastPlayerPosition == tilePosition)
|
if (mLastPlayerPosition.has_value() && *mLastPlayerPosition == tilePosition)
|
||||||
return;
|
return;
|
||||||
|
mNavMeshManager.updateBounds(playerPosition);
|
||||||
update(playerPosition);
|
update(playerPosition);
|
||||||
mLastPlayerPosition = tilePosition;
|
mLastPlayerPosition = tilePosition;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ namespace DetourNavigator
|
||||||
|
|
||||||
void setWorldspace(std::string_view worldspace) override;
|
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 ObjectShapes& shapes, const btTransform& transform) override;
|
||||||
|
|
||||||
bool addObject(const ObjectId id, const DoorShapes& 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 update(const osg::Vec3f& /*playerPosition*/) override {}
|
||||||
|
|
||||||
|
void updateBounds(const osg::Vec3f& /*playerPosition*/) override {}
|
||||||
|
|
||||||
void updatePlayerPosition(const osg::Vec3f& /*playerPosition*/) override {};
|
void updatePlayerPosition(const osg::Vec3f& /*playerPosition*/) override {};
|
||||||
|
|
||||||
void setUpdatesEnabled(bool /*enabled*/) override {}
|
void setUpdatesEnabled(bool /*enabled*/) override {}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue