mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-18 13:12:50 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
6d98866be0
@ -8,6 +8,9 @@ stages:
|
||||
- docker
|
||||
- linux
|
||||
image: debian:bullseye
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "push"
|
||||
|
||||
|
||||
.Debian:
|
||||
extends: .Debian_Image
|
||||
@ -52,7 +55,7 @@ Coverity:
|
||||
extends: .Debian_Image
|
||||
stage: build
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
before_script:
|
||||
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic coverity
|
||||
- curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN
|
||||
@ -70,7 +73,6 @@ Coverity:
|
||||
variables:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
artifacts:
|
||||
|
||||
Debian_GCC:
|
||||
extends: .Debian
|
||||
@ -162,6 +164,7 @@ Debian_Clang_tests_Debug:
|
||||
only:
|
||||
variables:
|
||||
- $CI_PROJECT_ID == "7107382"
|
||||
- $CI_PIPELINE_SOURCE == "push"
|
||||
cache:
|
||||
paths:
|
||||
- ccache/
|
||||
@ -184,7 +187,6 @@ Debian_Clang_tests_Debug:
|
||||
macOS11_Xcode12:
|
||||
extends: .MacOS
|
||||
image: macos-11-xcode-12
|
||||
allow_failure: true
|
||||
cache:
|
||||
key: macOS11_Xcode12.v1
|
||||
variables:
|
||||
@ -193,6 +195,7 @@ macOS11_Xcode12:
|
||||
macOS10.15_Xcode11:
|
||||
extends: .MacOS
|
||||
image: macos-10.15-xcode-11
|
||||
allow_failure: true
|
||||
cache:
|
||||
key: macOS10.15_Xcode11.v1
|
||||
variables:
|
||||
@ -213,6 +216,8 @@ variables: &tests-targets
|
||||
.Windows_Ninja_Base:
|
||||
tags:
|
||||
- windows
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "push"
|
||||
before_script:
|
||||
- Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
|
||||
- choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1
|
||||
@ -329,6 +334,8 @@ Windows_Ninja_Tests_RelWithDebInfo:
|
||||
.Windows_MSBuild_Base:
|
||||
tags:
|
||||
- windows
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "push"
|
||||
before_script:
|
||||
- Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
|
||||
- choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1
|
||||
@ -389,6 +396,15 @@ Windows_Ninja_Tests_RelWithDebInfo:
|
||||
- MSVC2019_64/*/*/*/*/*/*/*.log
|
||||
- MSVC2019_64/*/*/*/*/*/*/*/*.log
|
||||
|
||||
Daily_Windows_MSBuild_Engine_Release:on-schedule:
|
||||
extends:
|
||||
- .Windows_MSBuild_Base
|
||||
variables:
|
||||
<<: *engine-targets
|
||||
config: "Release"
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
|
||||
Windows_MSBuild_Engine_Release:
|
||||
extends:
|
||||
- .Windows_MSBuild_Base
|
||||
@ -444,6 +460,8 @@ Debian_AndroidNDK_arm64-v8a:
|
||||
tags:
|
||||
- linux
|
||||
image: debian:bullseye
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "push"
|
||||
variables:
|
||||
CCACHE_SIZE: 3G
|
||||
cache:
|
||||
|
10
.readthedocs.yaml
Normal file
10
.readthedocs.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
|
||||
sphinx:
|
||||
configuration: docs/source/conf.py
|
||||
|
||||
python:
|
||||
version: 3.8
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
|
12
CHANGELOG.md
12
CHANGELOG.md
@ -5,12 +5,14 @@
|
||||
Bug #3246: ESSImporter: Most NPCs are dead on save load
|
||||
Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear
|
||||
Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions)
|
||||
Bug #3792: 1 frame late magicka recalc breaks early scripted magicka reactions to Intelligence change
|
||||
Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes
|
||||
Bug #3905: Great House Dagoth issues
|
||||
Bug #4203: Resurrecting an actor should close the loot GUI
|
||||
Bug #4602: Robert's Bodies: crash inside createInstance()
|
||||
Bug #4700: Editor: Incorrect command implementation
|
||||
Bug #4744: Invisible particles must still be processed
|
||||
Bug #5088: Sky abruptly changes direction during certain weather transitions
|
||||
Bug #5100: Persuasion doesn't always clamp the resulting disposition
|
||||
Bug #5120: Scripted object spawning updates physics system
|
||||
Bug #5207: Loose summons can be present in scene
|
||||
@ -25,6 +27,8 @@
|
||||
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 #5863: GetEffect should return true after the player has teleported
|
||||
Bug #5913: Failed assertion during Ritual of Trees quest
|
||||
Bug #5937: Lights always need to be rotated by 90 degrees
|
||||
Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher
|
||||
Bug #6051: NaN water height in ESM file is not handled gracefully
|
||||
Bug #6066: addtopic "return" does not work from within script. No errors thrown
|
||||
@ -40,10 +44,13 @@
|
||||
Bug #6133: Cannot reliably sneak or steal in the sight of the NPCs siding with player
|
||||
Bug #6143: Capturing a screenshot makes engine to be a temporary unresponsive
|
||||
Bug #6165: Paralyzed player character can pickup items when the inventory is open
|
||||
Bug #6168: Weather particles flicker for a frame at start of storms
|
||||
Bug #6172: Some creatures can't open doors
|
||||
Bug #6174: Spellmaking and Enchanting sliders differences from vanilla
|
||||
Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla
|
||||
Bug #6197: Infinite Casting Loop
|
||||
Bug #6253: Multiple instances of Reflect stack additively
|
||||
Bug #6255: Reflect is different from vanilla
|
||||
Bug #6258: Barter menu glitches out when modifying prices
|
||||
Bug #6273: Respawning NPCs rotation is inconsistent
|
||||
Bug #6282: Laura craft doesn't follow the player character
|
||||
@ -57,6 +64,9 @@
|
||||
Bug #6322: Total sold/cost should reset to 0 when there are no items offered
|
||||
Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house
|
||||
Bug #6326: Detect Enchantment/Key should detect items in unresolved containers
|
||||
Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures
|
||||
Bug #6363: Some scripts in Morrowland fail to work
|
||||
Bug #6376: Creatures should be able to use torches
|
||||
Feature #890: OpenMW-CS: Column filtering
|
||||
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
|
||||
@ -72,6 +82,7 @@
|
||||
Feature #6017: Separate persistent and temporary cell references when saving
|
||||
Feature #6032: Reverse-z depth buffer
|
||||
Feature #6078: First person should not clear depth buffer
|
||||
Feature #6161: Refactor Sky to use shaders and GLES/GL3 friendly
|
||||
Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly
|
||||
Feature #6199: Support FBO Rendering
|
||||
Feature #6249: Alpha testing support for Collada
|
||||
@ -80,7 +91,6 @@
|
||||
Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings
|
||||
Task #6264: Remove the old classes in animation.cpp
|
||||
|
||||
|
||||
0.47.0
|
||||
------
|
||||
|
||||
|
@ -199,6 +199,10 @@ if (WIN32)
|
||||
option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
add_compile_options("/utf-8")
|
||||
endif()
|
||||
|
||||
# Dependencies
|
||||
find_package(OpenGL REQUIRED)
|
||||
|
||||
@ -707,6 +711,8 @@ endif()
|
||||
if (BUILD_OPENMW AND APPLE)
|
||||
# Without these flags LuaJit crashes on startup on OSX
|
||||
set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000")
|
||||
target_compile_definitions(components PRIVATE GL_SILENCE_DEPRECATION=1)
|
||||
target_compile_definitions(openmw PRIVATE GL_SILENCE_DEPRECATION=1)
|
||||
endif()
|
||||
|
||||
# Apple bundling
|
||||
|
@ -32,7 +32,7 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config:
|
||||
{
|
||||
ui.setupUi (this);
|
||||
setObjectName ("DataFilesPage");
|
||||
mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget);
|
||||
mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget, /*showOMWScripts=*/true);
|
||||
const QString encoding = mGameSettings.value("encoding", "win1252");
|
||||
mSelector->setEncoding(encoding);
|
||||
|
||||
|
@ -10,6 +10,8 @@ QSet<QString> CellNameLoader::getCellNames(QStringList &contentPaths)
|
||||
|
||||
// Loop through all content files
|
||||
for (auto &contentPath : contentPaths) {
|
||||
if (contentPath.endsWith(".omwscripts", Qt::CaseInsensitive))
|
||||
continue;
|
||||
esmReader.open(contentPath.toStdString());
|
||||
|
||||
// Loop through all records
|
||||
|
@ -296,7 +296,7 @@ namespace CSMWorld
|
||||
const std::string& destination, const UniversalId::Type type)
|
||||
{
|
||||
int index = cloneRecordImp(origin, destination, type);
|
||||
mRecords.at(index)->get().mPlugin = 0;
|
||||
mRecords.at(index)->get().setPlugin(0);
|
||||
}
|
||||
|
||||
template<typename ESXRecordT, typename IdAccessorT>
|
||||
@ -311,7 +311,7 @@ namespace CSMWorld
|
||||
int index = touchRecordImp(id);
|
||||
if (index >= 0)
|
||||
{
|
||||
mRecords.at(index)->get().mPlugin = 0;
|
||||
mRecords.at(index)->get().setPlugin(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ namespace CSMWorld
|
||||
|
||||
QVariant LandPluginIndexColumn::get(const Record<Land>& record) const
|
||||
{
|
||||
return record.get().mPlugin;
|
||||
return record.get().getPlugin();
|
||||
}
|
||||
|
||||
bool LandPluginIndexColumn::isEditable() const
|
||||
|
@ -102,11 +102,6 @@ bool CSMWorld::ScriptContext::isId (const std::string& name) const
|
||||
return std::binary_search (mIds.begin(), mIds.end(), Misc::StringUtils::lowerCase (name));
|
||||
}
|
||||
|
||||
bool CSMWorld::ScriptContext::isJournalId (const std::string& name) const
|
||||
{
|
||||
return mData.getJournals().searchId (name)!=-1;
|
||||
}
|
||||
|
||||
void CSMWorld::ScriptContext::invalidateIds()
|
||||
{
|
||||
mIdsUpdated = false;
|
||||
|
@ -39,9 +39,6 @@ namespace CSMWorld
|
||||
bool isId (const std::string& name) const override;
|
||||
///< Does \a name match an ID, that can be referenced?
|
||||
|
||||
bool isJournalId (const std::string& name) const override;
|
||||
///< Does \a name match a journal ID?
|
||||
|
||||
void invalidateIds();
|
||||
|
||||
void clear();
|
||||
|
@ -24,7 +24,7 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) :
|
||||
resize(400, 400);
|
||||
|
||||
setObjectName ("FileDialog");
|
||||
mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget);
|
||||
mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget, /*showOMWScripts=*/false);
|
||||
mAdjusterWidget = new AdjusterWidget (this);
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,7 @@ namespace CSVRender
|
||||
if (!mesh.empty() && node != mNodeMap.end())
|
||||
{
|
||||
auto instance = sceneMgr->getInstance(mesh);
|
||||
SceneUtil::attach(instance, mSkeleton, boneName, node->second);
|
||||
SceneUtil::attach(instance, mSkeleton, boneName, node->second, sceneMgr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,7 +308,7 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
|
||||
const float OuterRadius = InnerRadius + MarkerShaftWidth;
|
||||
|
||||
const float SegmentDistance = 100.f;
|
||||
const size_t SegmentCount = std::min(64, std::max(24, (int)(OuterRadius * 2 * osg::PI / SegmentDistance)));
|
||||
const size_t SegmentCount = std::clamp<int>(OuterRadius * 2 * osg::PI / SegmentDistance, 24, 64);
|
||||
const size_t VerticesPerSegment = 4;
|
||||
const size_t IndicesPerSegment = 24;
|
||||
|
||||
|
@ -19,7 +19,7 @@ set(GAME_HEADER
|
||||
source_group(game FILES ${GAME} ${GAME_HEADER})
|
||||
|
||||
add_openmw_dir (mwrender
|
||||
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
|
||||
actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation vismask
|
||||
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager
|
||||
bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation
|
||||
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor
|
||||
@ -72,7 +72,7 @@ add_openmw_dir (mwworld
|
||||
containerstore actiontalk actiontake manualref player cellvisitors failedaction
|
||||
cells localscripts customdata inventorystore ptr actionopen actionread actionharvest
|
||||
actionequip timestamp actionalchemy cellstore actionapply actioneat
|
||||
store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor
|
||||
store esmstore fallback actionrepair actionsoulgem livecellref actiondoor
|
||||
contentloader esmloader actiontrap cellreflist cellref weather projectilemanager
|
||||
cellpreloader datetimemanager
|
||||
)
|
||||
@ -94,7 +94,7 @@ add_openmw_dir (mwmechanics
|
||||
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance
|
||||
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
|
||||
character actors objects aistate trading weaponpriority spellpriority weapontype spellutil
|
||||
spellabsorption spelleffects
|
||||
spelleffects
|
||||
)
|
||||
|
||||
add_openmw_dir (mwstate
|
||||
|
@ -495,11 +495,6 @@ void OMW::Engine::addGroundcoverFile(const std::string& file)
|
||||
mGroundcoverFiles.emplace_back(file);
|
||||
}
|
||||
|
||||
void OMW::Engine::addLuaScriptListFile(const std::string& file)
|
||||
{
|
||||
mLuaScriptListFiles.push_back(file);
|
||||
}
|
||||
|
||||
void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame)
|
||||
{
|
||||
mSkipMenu = skipMenu;
|
||||
@ -674,7 +669,7 @@ void OMW::Engine::setWindowIcon()
|
||||
void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||
{
|
||||
mEnvironment.setStateManager (
|
||||
new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0)));
|
||||
new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles));
|
||||
|
||||
createWindow(settings);
|
||||
|
||||
@ -714,7 +709,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||
|
||||
mViewer->addEventHandler(mScreenCaptureHandler);
|
||||
|
||||
mLuaManager = new MWLua::LuaManager(mVFS.get(), mLuaScriptListFiles);
|
||||
mLuaManager = new MWLua::LuaManager(mVFS.get());
|
||||
mEnvironment.setLuaManager(mLuaManager);
|
||||
|
||||
// Create input and UI first to set up a bootstrapping environment for
|
||||
|
@ -72,7 +72,6 @@ namespace OMW
|
||||
std::string mCellName;
|
||||
std::vector<std::string> mContentFiles;
|
||||
std::vector<std::string> mGroundcoverFiles;
|
||||
std::vector<std::string> mLuaScriptListFiles;
|
||||
bool mSkipMenu;
|
||||
bool mUseSound;
|
||||
bool mCompileAll;
|
||||
@ -146,7 +145,6 @@ namespace OMW
|
||||
*/
|
||||
void addContentFile(const std::string& file);
|
||||
void addGroundcoverFile(const std::string& file);
|
||||
void addLuaScriptListFile(const std::string& file);
|
||||
|
||||
/// Disable or enable all sounds
|
||||
void setSoundUsage(bool soundUsage);
|
||||
|
@ -124,9 +124,11 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
||||
engine.addGroundcoverFile(file);
|
||||
}
|
||||
|
||||
StringsVector luaScriptLists = variables["lua-scripts"].as<Files::EscapeStringVector>().toStdStringVector();
|
||||
for (const auto& file : luaScriptLists)
|
||||
engine.addLuaScriptListFile(file);
|
||||
if (variables.count("lua-scripts"))
|
||||
{
|
||||
Log(Debug::Warning) << "Lua scripts have been specified via the old lua-scripts option and will not be loaded. "
|
||||
"Please update them to a version which uses the new omwscripts format.";
|
||||
}
|
||||
|
||||
// startup-settings
|
||||
engine.setCell(variables["start"].as<Files::EscapeHashString>().toStdString());
|
||||
|
@ -30,6 +30,7 @@ namespace MWBase
|
||||
virtual ~LuaManager() = default;
|
||||
|
||||
virtual void newGameStarted() = 0;
|
||||
virtual void gameLoaded() = 0;
|
||||
virtual void registerObject(const MWWorld::Ptr& ptr) = 0;
|
||||
virtual void deregisterObject(const MWWorld::Ptr& ptr) = 0;
|
||||
virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0;
|
||||
|
@ -289,7 +289,7 @@ namespace MWBase
|
||||
virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true, bool keepActive=false) = 0;
|
||||
///< @return an updated Ptr
|
||||
|
||||
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, const osg::Vec3f& vec, bool moveToActive, bool ignoreCollisions) = 0;
|
||||
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, const osg::Vec3f& vec) = 0;
|
||||
///< @return an updated Ptr
|
||||
|
||||
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
|
||||
|
@ -82,7 +82,7 @@ namespace MWClass
|
||||
|
||||
const Creature::GMST& Creature::getGmst()
|
||||
{
|
||||
static const GMST gmst = []
|
||||
static const GMST staticGmst = []
|
||||
{
|
||||
GMST gmst;
|
||||
|
||||
@ -105,14 +105,17 @@ namespace MWClass
|
||||
|
||||
return gmst;
|
||||
} ();
|
||||
return gmst;
|
||||
return staticGmst;
|
||||
}
|
||||
|
||||
void Creature::ensureCustomData (const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
std::unique_ptr<CreatureCustomData> data (new CreatureCustomData);
|
||||
auto tempData = std::make_unique<CreatureCustomData>();
|
||||
CreatureCustomData* data = tempData.get();
|
||||
MWMechanics::CreatureCustomDataResetter resetter(ptr);
|
||||
ptr.getRefData().setCustomData(std::move(tempData));
|
||||
|
||||
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
||||
|
||||
@ -156,10 +159,7 @@ namespace MWClass
|
||||
|
||||
data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold);
|
||||
|
||||
data->mCreatureStats.setNeedRecalcDynamicStats(false);
|
||||
|
||||
// store
|
||||
ptr.getRefData().setCustomData(std::move(data));
|
||||
resetter.mPtr = {};
|
||||
|
||||
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "../mwmechanics/levelledlist.hpp"
|
||||
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/customdata.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
|
||||
@ -27,6 +28,24 @@ namespace MWClass
|
||||
}
|
||||
};
|
||||
|
||||
MWWorld::Ptr CreatureLevList::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const
|
||||
{
|
||||
const MWWorld::LiveCellRef<ESM::CreatureLevList> *ref = ptr.get<ESM::CreatureLevList>();
|
||||
|
||||
return MWWorld::Ptr(cell.insert(ref), &cell);
|
||||
}
|
||||
|
||||
void CreatureLevList::adjustPosition(const MWWorld::Ptr& ptr, bool force) const
|
||||
{
|
||||
if (ptr.getRefData().getCustomData() == nullptr)
|
||||
return;
|
||||
|
||||
CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData();
|
||||
MWWorld::Ptr creature = (customData.mSpawnActorId == -1) ? MWWorld::Ptr() : MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId);
|
||||
if (!creature.isEmpty())
|
||||
MWBase::Environment::get().getWorld()->adjustPosition(creature, force);
|
||||
}
|
||||
|
||||
std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const
|
||||
{
|
||||
return "";
|
||||
|
@ -32,6 +32,10 @@ namespace MWClass
|
||||
///< Write additional state from \a ptr into \a state.
|
||||
|
||||
void respawn (const MWWorld::Ptr& ptr) const override;
|
||||
|
||||
MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override;
|
||||
|
||||
void adjustPosition(const MWWorld::Ptr& ptr, bool force) const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -266,7 +266,7 @@ namespace MWClass
|
||||
|
||||
const Npc::GMST& Npc::getGmst()
|
||||
{
|
||||
static const GMST gmst = []
|
||||
static const GMST staticGmst = []
|
||||
{
|
||||
GMST gmst;
|
||||
|
||||
@ -296,14 +296,18 @@ namespace MWClass
|
||||
|
||||
return gmst;
|
||||
} ();
|
||||
return gmst;
|
||||
return staticGmst;
|
||||
}
|
||||
|
||||
void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
std::unique_ptr<NpcCustomData> data(new NpcCustomData);
|
||||
bool recalculate = false;
|
||||
auto tempData = std::make_unique<NpcCustomData>();
|
||||
NpcCustomData* data = tempData.get();
|
||||
MWMechanics::CreatureCustomDataResetter resetter(ptr);
|
||||
ptr.getRefData().setCustomData(std::move(tempData));
|
||||
|
||||
MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
|
||||
|
||||
@ -334,8 +338,6 @@ namespace MWClass
|
||||
data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel);
|
||||
data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition);
|
||||
data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation);
|
||||
|
||||
data->mNpcStats.setNeedRecalcDynamicStats(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -351,7 +353,7 @@ namespace MWClass
|
||||
autoCalculateAttributes(ref->mBase, data->mNpcStats);
|
||||
autoCalculateSkills(ref->mBase, data->mNpcStats, ptr, spellsInitialised);
|
||||
|
||||
data->mNpcStats.setNeedRecalcDynamicStats(true);
|
||||
recalculate = true;
|
||||
}
|
||||
|
||||
// Persistent actors with 0 health do not play death animation
|
||||
@ -387,7 +389,9 @@ namespace MWClass
|
||||
data->mNpcStats.setGoldPool(gold);
|
||||
|
||||
// store
|
||||
ptr.getRefData().setCustomData(std::move(data));
|
||||
resetter.mPtr = {};
|
||||
if(recalculate)
|
||||
data->mNpcStats.recalculateMagicka();
|
||||
|
||||
// inventory
|
||||
// setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items
|
||||
|
@ -421,7 +421,7 @@ namespace MWDialogue
|
||||
// Clamp permanent disposition change so that final disposition doesn't go below 0 (could happen with intimidate)
|
||||
npcStats.setBaseDisposition(0);
|
||||
int zero = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false);
|
||||
int disposition = std::min(100 - zero, std::max(mOriginalDisposition + mPermanentDispositionChange, -zero));
|
||||
int disposition = std::clamp(mOriginalDisposition + mPermanentDispositionChange, -zero, 100 - zero);
|
||||
|
||||
npcStats.setBaseDisposition(disposition);
|
||||
}
|
||||
|
@ -347,8 +347,7 @@ namespace MWGui
|
||||
{
|
||||
if (!mScrollBar->getVisible())
|
||||
return;
|
||||
mScrollBar->setScrollPosition(std::min(static_cast<int>(mScrollBar->getScrollRange()-1),
|
||||
std::max(0, static_cast<int>(mScrollBar->getScrollPosition() - _rel*0.3))));
|
||||
mScrollBar->setScrollPosition(std::clamp<int>(mScrollBar->getScrollPosition() - _rel*0.3, 0, mScrollBar->getScrollRange() - 1));
|
||||
onScrollbarMoved(mScrollBar, mScrollBar->getScrollPosition());
|
||||
}
|
||||
|
||||
|
@ -109,7 +109,7 @@ namespace MWGui
|
||||
{
|
||||
mEnchantmentPoints->setCaption(std::to_string(static_cast<int>(mEnchanting.getEnchantPoints(false))) + " / " + std::to_string(mEnchanting.getMaxEnchantValue()));
|
||||
mCharge->setCaption(std::to_string(mEnchanting.getGemCharge()));
|
||||
mSuccessChance->setCaption(std::to_string(std::max(0, std::min(100, mEnchanting.getEnchantChance()))));
|
||||
mSuccessChance->setCaption(std::to_string(std::clamp(mEnchanting.getEnchantChance(), 0, 100)));
|
||||
mCastCost->setCaption(std::to_string(mEnchanting.getEffectiveCastCost()));
|
||||
mPrice->setCaption(std::to_string(mEnchanting.getEnchantPrice()));
|
||||
|
||||
|
@ -610,7 +610,7 @@ namespace MWGui
|
||||
|
||||
static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fNPCHealthBarFade")->mValue.getFloat();
|
||||
if (fNPCHealthBarFade > 0.f)
|
||||
mEnemyHealth->setAlpha(std::max(0.f, std::min(1.f, mEnemyHealthTimer/fNPCHealthBarFade)));
|
||||
mEnemyHealth->setAlpha(std::clamp(mEnemyHealthTimer / fNPCHealthBarFade, 0.f, 1.f));
|
||||
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ namespace MWGui
|
||||
, mTrading(false)
|
||||
, mUpdateTimer(0.f)
|
||||
{
|
||||
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture()));
|
||||
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture(), mPreview->getTextureStateSet()));
|
||||
mPreview->rebuild();
|
||||
|
||||
mMainWidget->castType<MyGUI::Window>()->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize);
|
||||
|
@ -313,9 +313,9 @@ struct JournalViewModelImpl : JournalViewModel
|
||||
for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i)
|
||||
{
|
||||
Utf8Stream stream (i->first.c_str());
|
||||
Utf8Stream::UnicodeChar first = Misc::StringUtils::toLowerUtf8(stream.peek());
|
||||
Utf8Stream::UnicodeChar first = Utf8Stream::toLowerUtf8(stream.peek());
|
||||
|
||||
if (first != Misc::StringUtils::toLowerUtf8(character))
|
||||
if (first != Utf8Stream::toLowerUtf8(character))
|
||||
continue;
|
||||
|
||||
visitor (i->second.getName());
|
||||
|
@ -273,7 +273,7 @@ bool KeyboardNavigation::switchFocus(int direction, bool wrap)
|
||||
if (wrap)
|
||||
index = (index + keyFocusList.size())%keyFocusList.size();
|
||||
else
|
||||
index = std::min(std::max(0, index), static_cast<int>(keyFocusList.size())-1);
|
||||
index = std::clamp<int>(index, 0, keyFocusList.size() - 1);
|
||||
|
||||
MyGUI::Widget* next = keyFocusList[index];
|
||||
int vertdiff = next->getTop() - focus->getTop();
|
||||
|
@ -138,7 +138,7 @@ namespace MWGui
|
||||
mPreview->rebuild();
|
||||
mPreview->setAngle (mCurrentAngle);
|
||||
|
||||
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture()));
|
||||
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture(), mPreview->getTextureStateSet()));
|
||||
mPreviewImage->setRenderItemTexture(mPreviewTexture.get());
|
||||
mPreviewImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f));
|
||||
|
||||
|
@ -171,7 +171,7 @@ namespace MWGui
|
||||
else
|
||||
valueStr = MyGUI::utility::toString(int(value));
|
||||
|
||||
value = std::max(min, std::min(value, max));
|
||||
value = std::clamp(value, min, max);
|
||||
value = (value-min)/(max-min);
|
||||
|
||||
scroll->setScrollPosition(static_cast<size_t>(value * (scroll->getScrollRange() - 1)));
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "sortfilteritemmodel.hpp"
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include <components/misc/utf8stream.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/esm/loadalch.hpp>
|
||||
#include <components/esm/loadappa.hpp>
|
||||
@ -69,8 +70,8 @@ namespace
|
||||
return compareType(leftType, rightType);
|
||||
|
||||
// compare items by name
|
||||
std::string leftName = Misc::StringUtils::lowerCaseUtf8(left.mBase.getClass().getName(left.mBase));
|
||||
std::string rightName = Misc::StringUtils::lowerCaseUtf8(right.mBase.getClass().getName(right.mBase));
|
||||
std::string leftName = Utf8Stream::lowerCaseUtf8(left.mBase.getClass().getName(left.mBase));
|
||||
std::string rightName = Utf8Stream::lowerCaseUtf8(right.mBase.getClass().getName(right.mBase));
|
||||
|
||||
result = leftName.compare(rightName);
|
||||
if (result != 0)
|
||||
@ -213,7 +214,7 @@ namespace MWGui
|
||||
|
||||
if (!mNameFilter.empty())
|
||||
{
|
||||
const auto itemName = Misc::StringUtils::lowerCaseUtf8(base.getClass().getName(base));
|
||||
const auto itemName = Utf8Stream::lowerCaseUtf8(base.getClass().getName(base));
|
||||
return itemName.find(mNameFilter) != std::string::npos;
|
||||
}
|
||||
|
||||
@ -226,7 +227,7 @@ namespace MWGui
|
||||
|
||||
for (const auto& effect : effects)
|
||||
{
|
||||
const auto ciEffect = Misc::StringUtils::lowerCaseUtf8(effect);
|
||||
const auto ciEffect = Utf8Stream::lowerCaseUtf8(effect);
|
||||
|
||||
if (ciEffect.find(mEffectFilter) != std::string::npos)
|
||||
return true;
|
||||
@ -285,7 +286,7 @@ namespace MWGui
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string compare = Misc::StringUtils::lowerCaseUtf8(item.mBase.getClass().getName(item.mBase));
|
||||
std::string compare = Utf8Stream::lowerCaseUtf8(item.mBase.getClass().getName(item.mBase));
|
||||
if(compare.find(mNameFilter) == std::string::npos)
|
||||
return false;
|
||||
|
||||
@ -318,12 +319,12 @@ namespace MWGui
|
||||
|
||||
void SortFilterItemModel::setNameFilter (const std::string& filter)
|
||||
{
|
||||
mNameFilter = Misc::StringUtils::lowerCaseUtf8(filter);
|
||||
mNameFilter = Utf8Stream::lowerCaseUtf8(filter);
|
||||
}
|
||||
|
||||
void SortFilterItemModel::setEffectFilter (const std::string& filter)
|
||||
{
|
||||
mEffectFilter = Misc::StringUtils::lowerCaseUtf8(filter);
|
||||
mEffectFilter = Utf8Stream::lowerCaseUtf8(filter);
|
||||
}
|
||||
|
||||
void SortFilterItemModel::update()
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "spellmodel.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/misc/utf8stream.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
@ -69,7 +70,7 @@ namespace MWGui
|
||||
fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effect.mAttribute], "");
|
||||
}
|
||||
|
||||
std::string convert = Misc::StringUtils::lowerCaseUtf8(fullEffectName);
|
||||
std::string convert = Utf8Stream::lowerCaseUtf8(fullEffectName);
|
||||
if (convert.find(filter) != std::string::npos)
|
||||
{
|
||||
return true;
|
||||
@ -90,14 +91,14 @@ namespace MWGui
|
||||
const MWWorld::ESMStore &esmStore =
|
||||
MWBase::Environment::get().getWorld()->getStore();
|
||||
|
||||
std::string filter = Misc::StringUtils::lowerCaseUtf8(mFilter);
|
||||
std::string filter = Utf8Stream::lowerCaseUtf8(mFilter);
|
||||
|
||||
for (const ESM::Spell* spell : spells)
|
||||
{
|
||||
if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell)
|
||||
continue;
|
||||
|
||||
std::string name = Misc::StringUtils::lowerCaseUtf8(spell->mName);
|
||||
std::string name = Utf8Stream::lowerCaseUtf8(spell->mName);
|
||||
|
||||
if (name.find(filter) == std::string::npos
|
||||
&& !matchingEffectExists(filter, spell->mEffects))
|
||||
@ -139,7 +140,7 @@ namespace MWGui
|
||||
if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce)
|
||||
continue;
|
||||
|
||||
std::string name = Misc::StringUtils::lowerCaseUtf8(item.getClass().getName(item));
|
||||
std::string name = Utf8Stream::lowerCaseUtf8(item.getClass().getName(item));
|
||||
|
||||
if (name.find(filter) == std::string::npos
|
||||
&& !matchingEffectExists(filter, enchant->mEffects))
|
||||
|
@ -599,8 +599,7 @@ namespace MWGui
|
||||
text += "\n#{fontcolourhtml=normal}#{sExpelled}";
|
||||
else
|
||||
{
|
||||
int rank = factionPair.second;
|
||||
rank = std::max(0, std::min(9, rank));
|
||||
const int rank = std::clamp(factionPair.second, 0, 9);
|
||||
text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank];
|
||||
|
||||
if (rank < 9)
|
||||
|
@ -70,7 +70,7 @@ namespace MWInput
|
||||
}
|
||||
|
||||
float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input");
|
||||
deadZoneRadius = std::min(std::max(deadZoneRadius, 0.0f), 0.5f);
|
||||
deadZoneRadius = std::clamp(deadZoneRadius, 0.f, 0.5f);
|
||||
mBindingsManager->setJoystickDeadZone(deadZoneRadius);
|
||||
}
|
||||
|
||||
|
@ -245,8 +245,8 @@ namespace MWInput
|
||||
mMouseWheel += mouseWheelMove;
|
||||
|
||||
const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
||||
mGuiCursorX = std::max(0.f, std::min(mGuiCursorX, float(viewSize.width - 1)));
|
||||
mGuiCursorY = std::max(0.f, std::min(mGuiCursorY, float(viewSize.height - 1)));
|
||||
mGuiCursorX = std::clamp<float>(mGuiCursorX, 0.f, viewSize.width - 1);
|
||||
mGuiCursorY = std::clamp<float>(mGuiCursorY, 0.f, viewSize.height - 1);
|
||||
|
||||
MyGUI::InputManager::getInstance().injectMouseMove(static_cast<int>(mGuiCursorX), static_cast<int>(mGuiCursorY), static_cast<int>(mMouseWheel));
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include "actions.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
@ -23,7 +23,7 @@ namespace MWLua
|
||||
sol::usertype<AsyncPackageId> api = context.mLua->sol().new_usertype<AsyncPackageId>("AsyncPackage");
|
||||
api["registerTimerCallback"] = [](const AsyncPackageId& asyncId, std::string_view name, sol::function callback)
|
||||
{
|
||||
asyncId.mContainer->registerTimerCallback(asyncId.mScript, name, std::move(callback));
|
||||
asyncId.mContainer->registerTimerCallback(asyncId.mScriptId, name, std::move(callback));
|
||||
return TimerCallback{asyncId, std::string(name)};
|
||||
};
|
||||
api["newTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
|
||||
@ -31,35 +31,34 @@ namespace MWLua
|
||||
{
|
||||
callback.mAsyncId.mContainer->setupSerializableTimer(
|
||||
TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay,
|
||||
callback.mAsyncId.mScript, callback.mName, std::move(callbackArg));
|
||||
callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg));
|
||||
};
|
||||
api["newTimerInHours"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
|
||||
const TimerCallback& callback, sol::object callbackArg)
|
||||
{
|
||||
callback.mAsyncId.mContainer->setupSerializableTimer(
|
||||
TimeUnit::HOURS, world->getGameTimeInHours() + delay,
|
||||
callback.mAsyncId.mScript, callback.mName, std::move(callbackArg));
|
||||
callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg));
|
||||
};
|
||||
api["newUnsavableTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
|
||||
{
|
||||
asyncId.mContainer->setupUnsavableTimer(
|
||||
TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay, asyncId.mScript, std::move(callback));
|
||||
TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay, asyncId.mScriptId, std::move(callback));
|
||||
};
|
||||
api["newUnsavableTimerInHours"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
|
||||
{
|
||||
asyncId.mContainer->setupUnsavableTimer(
|
||||
TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScript, std::move(callback));
|
||||
TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScriptId, std::move(callback));
|
||||
};
|
||||
api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn)
|
||||
{
|
||||
return Callback{std::move(fn), asyncId.mHiddenData};
|
||||
return LuaUtil::Callback{std::move(fn), asyncId.mHiddenData};
|
||||
};
|
||||
|
||||
auto initializer = [](sol::table hiddenData)
|
||||
{
|
||||
LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY];
|
||||
hiddenData[Callback::SCRIPT_NAME_KEY] = id.toString();
|
||||
return AsyncPackageId{id.mContainer, id.mPath, hiddenData};
|
||||
LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::sScriptIdKey];
|
||||
return AsyncPackageId{id.mContainer, id.mIndex, hiddenData};
|
||||
};
|
||||
return sol::make_object(context.mLua->sol(), initializer);
|
||||
}
|
||||
|
@ -16,7 +16,8 @@ namespace MWLua
|
||||
class GlobalScripts : public LuaUtil::ScriptsContainer
|
||||
{
|
||||
public:
|
||||
GlobalScripts(LuaUtil::LuaState* lua) : LuaUtil::ScriptsContainer(lua, "Global")
|
||||
GlobalScripts(LuaUtil::LuaState* lua) :
|
||||
LuaUtil::ScriptsContainer(lua, "Global", ESM::LuaScriptCfg::sGlobal)
|
||||
{
|
||||
registerEngineHandlers({&mActorActiveHandlers, &mNewGameHandlers, &mPlayerAddedHandlers});
|
||||
}
|
||||
|
@ -82,14 +82,14 @@ namespace MWLua
|
||||
};
|
||||
}
|
||||
|
||||
LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj)
|
||||
: LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id())), mData(obj)
|
||||
LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj, ESM::LuaScriptCfg::Flags autoStartMode)
|
||||
: LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id()), autoStartMode), mData(obj)
|
||||
{
|
||||
this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData));
|
||||
registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers});
|
||||
}
|
||||
|
||||
void LocalScripts::receiveEngineEvent(const EngineEvent& event, ObjectRegistry*)
|
||||
void LocalScripts::receiveEngineEvent(const EngineEvent& event)
|
||||
{
|
||||
std::visit([this](auto&& arg)
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ namespace MWLua
|
||||
{
|
||||
public:
|
||||
static void initializeSelfPackage(const Context&);
|
||||
LocalScripts(LuaUtil::LuaState* lua, const LObject& obj);
|
||||
LocalScripts(LuaUtil::LuaState* lua, const LObject& obj, ESM::LuaScriptCfg::Flags autoStartMode);
|
||||
|
||||
MWBase::LuaManager::ActorControls* getActorControls() { return &mData.mControls; }
|
||||
|
||||
@ -39,7 +39,7 @@ namespace MWLua
|
||||
};
|
||||
using EngineEvent = std::variant<OnActive, OnInactive, OnConsume>;
|
||||
|
||||
void receiveEngineEvent(const EngineEvent&, ObjectRegistry*);
|
||||
void receiveEngineEvent(const EngineEvent&);
|
||||
|
||||
protected:
|
||||
SelfObject mData;
|
||||
|
@ -25,7 +25,7 @@ namespace MWLua
|
||||
{
|
||||
auto* lua = context.mLua;
|
||||
sol::table api(lua->sol(), sol::create);
|
||||
api["API_REVISION"] = 7;
|
||||
api["API_REVISION"] = 8;
|
||||
api["quit"] = [lua]()
|
||||
{
|
||||
std::string traceback = lua->sol()["debug"]["traceback"]().get<std::string>();
|
||||
|
@ -48,7 +48,7 @@ namespace MWLua
|
||||
struct AsyncPackageId
|
||||
{
|
||||
LuaUtil::ScriptsContainer* mContainer;
|
||||
std::string mScript;
|
||||
int mScriptId;
|
||||
sol::table mHiddenData;
|
||||
};
|
||||
sol::function getAsyncPackageInitializer(const Context&);
|
||||
|
@ -7,11 +7,11 @@
|
||||
#include <components/esm/luascripts.hpp>
|
||||
|
||||
#include <components/lua/utilpackage.hpp>
|
||||
#include <components/lua/omwscriptsparser.hpp>
|
||||
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
#include "luabindings.hpp"
|
||||
@ -20,10 +20,9 @@
|
||||
namespace MWLua
|
||||
{
|
||||
|
||||
LuaManager::LuaManager(const VFS::Manager* vfs, const std::vector<std::string>& scriptLists) : mLua(vfs)
|
||||
LuaManager::LuaManager(const VFS::Manager* vfs) : mLua(vfs, &mConfiguration)
|
||||
{
|
||||
Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion();
|
||||
mGlobalScriptList = LuaUtil::parseOMWScriptsFiles(vfs, scriptLists);
|
||||
|
||||
mGlobalSerializer = createUserdataSerializer(false, mWorldView.getObjectRegistry());
|
||||
mLocalSerializer = createUserdataSerializer(true, mWorldView.getObjectRegistry());
|
||||
@ -33,6 +32,14 @@ namespace MWLua
|
||||
mGlobalScripts.setSerializer(mGlobalSerializer.get());
|
||||
}
|
||||
|
||||
void LuaManager::initConfiguration()
|
||||
{
|
||||
mConfiguration.init(MWBase::Environment::get().getWorld()->getStore().getLuaScriptsCfg());
|
||||
Log(Debug::Verbose) << "Lua scripts configuration (" << mConfiguration.size() << " scripts):";
|
||||
for (size_t i = 0; i < mConfiguration.size(); ++i)
|
||||
Log(Debug::Verbose) << "#" << i << " " << LuaUtil::scriptCfgToString(mConfiguration[i]);
|
||||
}
|
||||
|
||||
void LuaManager::init()
|
||||
{
|
||||
Context context;
|
||||
@ -67,23 +74,10 @@ namespace MWLua
|
||||
mLocalSettingsPackage = initLocalSettingsPackage(localContext);
|
||||
mPlayerSettingsPackage = initPlayerSettingsPackage(localContext);
|
||||
|
||||
mInputEvents.clear();
|
||||
for (const std::string& path : mGlobalScriptList)
|
||||
if (mGlobalScripts.addNewScript(path))
|
||||
Log(Debug::Info) << "Global script started: " << path;
|
||||
initConfiguration();
|
||||
mInitialized = true;
|
||||
}
|
||||
|
||||
void Callback::operator()(sol::object arg) const
|
||||
{
|
||||
if (mHiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY] != sol::nil)
|
||||
LuaUtil::call(mFunc, std::move(arg));
|
||||
else
|
||||
{
|
||||
Log(Debug::Debug) << "Ignored callback to removed script " << mHiddenData.get<std::string>(SCRIPT_NAME_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
void LuaManager::update(bool paused, float dt)
|
||||
{
|
||||
ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry();
|
||||
@ -160,7 +154,7 @@ namespace MWLua
|
||||
}
|
||||
LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts();
|
||||
if (scripts)
|
||||
scripts->receiveEngineEvent(e.mEvent, objectRegistry);
|
||||
scripts->receiveEngineEvent(e.mEvent);
|
||||
}
|
||||
mLocalEngineEvents.clear();
|
||||
|
||||
@ -173,6 +167,11 @@ namespace MWLua
|
||||
mPlayerChanged = false;
|
||||
mGlobalScripts.playerAdded(GObject(getId(mPlayer), objectRegistry));
|
||||
}
|
||||
if (mNewGameStarted)
|
||||
{
|
||||
mNewGameStarted = false;
|
||||
mGlobalScripts.newGameStarted();
|
||||
}
|
||||
|
||||
for (ObjectId id : mActorAddedEvents)
|
||||
mGlobalScripts.actorActive(GObject(id, objectRegistry));
|
||||
@ -205,8 +204,11 @@ namespace MWLua
|
||||
mInputEvents.clear();
|
||||
mActorAddedEvents.clear();
|
||||
mLocalEngineEvents.clear();
|
||||
mNewGameStarted = false;
|
||||
mPlayerChanged = false;
|
||||
mWorldView.clear();
|
||||
mGlobalScripts.removeAllScripts();
|
||||
mGlobalScriptsStarted = false;
|
||||
if (!mPlayer.isEmpty())
|
||||
{
|
||||
mPlayer.getCellRef().unsetRefNum();
|
||||
@ -225,17 +227,38 @@ namespace MWLua
|
||||
mPlayer = ptr;
|
||||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||
if (!localScripts)
|
||||
localScripts = createLocalScripts(ptr);
|
||||
localScripts = createLocalScripts(ptr, ESM::LuaScriptCfg::sPlayer);
|
||||
mActiveLocalScripts.insert(localScripts);
|
||||
mLocalEngineEvents.push_back({getId(ptr), LocalScripts::OnActive{}});
|
||||
mPlayerChanged = true;
|
||||
}
|
||||
|
||||
void LuaManager::newGameStarted()
|
||||
{
|
||||
mNewGameStarted = true;
|
||||
mInputEvents.clear();
|
||||
mGlobalScripts.addAutoStartedScripts();
|
||||
mGlobalScriptsStarted = true;
|
||||
}
|
||||
|
||||
void LuaManager::gameLoaded()
|
||||
{
|
||||
if (!mGlobalScriptsStarted)
|
||||
mGlobalScripts.addAutoStartedScripts();
|
||||
mGlobalScriptsStarted = true;
|
||||
}
|
||||
|
||||
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
mWorldView.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.
|
||||
|
||||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||
if (!localScripts)
|
||||
{
|
||||
ESM::LuaScriptCfg::Flags flag = getLuaScriptFlag(ptr);
|
||||
if (!mConfiguration.getListByFlag(flag).empty())
|
||||
localScripts = createLocalScripts(ptr, flag); // TODO: put to a queue and apply on next `update()`
|
||||
}
|
||||
if (localScripts)
|
||||
{
|
||||
mActiveLocalScripts.insert(localScripts);
|
||||
@ -281,26 +304,26 @@ namespace MWLua
|
||||
return localScripts->getActorControls();
|
||||
}
|
||||
|
||||
void LuaManager::addLocalScript(const MWWorld::Ptr& ptr, const std::string& scriptPath)
|
||||
void LuaManager::addCustomLocalScript(const MWWorld::Ptr& ptr, int scriptId)
|
||||
{
|
||||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||
if (!localScripts)
|
||||
{
|
||||
localScripts = createLocalScripts(ptr);
|
||||
localScripts = createLocalScripts(ptr, getLuaScriptFlag(ptr));
|
||||
if (ptr.isInCell() && MWBase::Environment::get().getWorld()->isCellActive(ptr.getCell()))
|
||||
mActiveLocalScripts.insert(localScripts);
|
||||
}
|
||||
localScripts->addNewScript(scriptPath);
|
||||
localScripts->addCustomScript(scriptId);
|
||||
}
|
||||
|
||||
LocalScripts* LuaManager::createLocalScripts(const MWWorld::Ptr& ptr)
|
||||
LocalScripts* LuaManager::createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags flag)
|
||||
{
|
||||
assert(mInitialized);
|
||||
assert(flag != ESM::LuaScriptCfg::sGlobal);
|
||||
std::shared_ptr<LocalScripts> scripts;
|
||||
// When loading a game, it can be called before LuaManager::setPlayer,
|
||||
// so we can't just check ptr == mPlayer here.
|
||||
if (ptr.getCellRef().getRefIdRef() == "player")
|
||||
if (flag == ESM::LuaScriptCfg::sPlayer)
|
||||
{
|
||||
assert(ptr.getCellRef().getRefIdRef() == "player");
|
||||
scripts = std::make_shared<PlayerScripts>(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()));
|
||||
scripts->addPackage("openmw.ui", mUserInterfacePackage);
|
||||
scripts->addPackage("openmw.camera", mCameraPackage);
|
||||
@ -309,11 +332,12 @@ namespace MWLua
|
||||
}
|
||||
else
|
||||
{
|
||||
scripts = std::make_shared<LocalScripts>(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()));
|
||||
scripts = std::make_shared<LocalScripts>(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()), flag);
|
||||
scripts->addPackage("openmw.settings", mLocalSettingsPackage);
|
||||
}
|
||||
scripts->addPackage("openmw.nearby", mNearbyPackage);
|
||||
scripts->setSerializer(mLocalSerializer.get());
|
||||
scripts->addAutoStartedScripts();
|
||||
|
||||
MWWorld::RefData& refData = ptr.getRefData();
|
||||
refData.setLuaScripts(std::move(scripts));
|
||||
@ -344,8 +368,9 @@ namespace MWLua
|
||||
loadEvents(mLua.sol(), reader, mGlobalEvents, mLocalEvents, mContentFileMapping, mGlobalLoader.get());
|
||||
|
||||
mGlobalScripts.setSerializer(mGlobalLoader.get());
|
||||
mGlobalScripts.load(globalScripts, false);
|
||||
mGlobalScripts.load(globalScripts);
|
||||
mGlobalScripts.setSerializer(mGlobalSerializer.get());
|
||||
mGlobalScriptsStarted = true;
|
||||
}
|
||||
|
||||
void LuaManager::saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data)
|
||||
@ -366,10 +391,10 @@ namespace MWLua
|
||||
}
|
||||
|
||||
mWorldView.getObjectRegistry()->registerPtr(ptr);
|
||||
LocalScripts* scripts = createLocalScripts(ptr);
|
||||
LocalScripts* scripts = createLocalScripts(ptr, getLuaScriptFlag(ptr));
|
||||
|
||||
scripts->setSerializer(mLocalLoader.get());
|
||||
scripts->load(data, true);
|
||||
scripts->load(data);
|
||||
scripts->setSerializer(mLocalSerializer.get());
|
||||
|
||||
// LiveCellRef is usually copied after loading, so this Ptr will become invalid and should be deregistered.
|
||||
@ -380,15 +405,12 @@ namespace MWLua
|
||||
{
|
||||
Log(Debug::Info) << "Reload Lua";
|
||||
mLua.dropScriptCache();
|
||||
initConfiguration();
|
||||
|
||||
{ // Reload global scripts
|
||||
ESM::LuaScripts data;
|
||||
mGlobalScripts.save(data);
|
||||
mGlobalScripts.removeAllScripts();
|
||||
for (const std::string& path : mGlobalScriptList)
|
||||
if (mGlobalScripts.addNewScript(path))
|
||||
Log(Debug::Info) << "Global script restarted: " << path;
|
||||
mGlobalScripts.load(data, false);
|
||||
mGlobalScripts.load(data);
|
||||
}
|
||||
|
||||
for (const auto& [id, ptr] : mWorldView.getObjectRegistry()->mObjectMapping)
|
||||
@ -398,8 +420,10 @@ namespace MWLua
|
||||
continue;
|
||||
ESM::LuaScripts data;
|
||||
scripts->save(data);
|
||||
scripts->load(data, true);
|
||||
scripts->load(data);
|
||||
}
|
||||
for (LocalScripts* scripts : mActiveLocalScripts)
|
||||
scripts->receiveEngineEvent(LocalScripts::OnActive());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,25 +19,12 @@
|
||||
namespace MWLua
|
||||
{
|
||||
|
||||
// Wrapper for a single-argument Lua function.
|
||||
// Holds information about the script the function belongs to.
|
||||
// Needed to prevent callback calls if the script was removed.
|
||||
struct Callback
|
||||
{
|
||||
static constexpr std::string_view SCRIPT_NAME_KEY = "name";
|
||||
|
||||
sol::function mFunc;
|
||||
sol::table mHiddenData;
|
||||
|
||||
void operator()(sol::object arg) const;
|
||||
};
|
||||
|
||||
class LuaManager : public MWBase::LuaManager
|
||||
{
|
||||
public:
|
||||
LuaManager(const VFS::Manager* vfs, const std::vector<std::string>& globalScriptLists);
|
||||
LuaManager(const VFS::Manager* vfs);
|
||||
|
||||
// Called by engine.cpp when environment is fully initialized.
|
||||
// Called by engine.cpp when the environment is fully initialized.
|
||||
void init();
|
||||
|
||||
// Called by engine.cpp every frame. For performance reasons it works in a separate
|
||||
@ -49,7 +36,8 @@ namespace MWLua
|
||||
|
||||
// Available everywhere through the MWBase::LuaManager interface.
|
||||
// LuaManager queues these events and propagates to scripts on the next `update` call.
|
||||
void newGameStarted() override { mGlobalScripts.newGameStarted(); }
|
||||
void newGameStarted() override;
|
||||
void gameLoaded() override;
|
||||
void objectAddedToScene(const MWWorld::Ptr& ptr) override;
|
||||
void objectRemovedFromScene(const MWWorld::Ptr& ptr) override;
|
||||
void registerObject(const MWWorld::Ptr& ptr) override;
|
||||
@ -62,8 +50,8 @@ namespace MWLua
|
||||
void clear() override; // should be called before loading game or starting a new game to reset internal state.
|
||||
void setupPlayer(const MWWorld::Ptr& ptr) override; // Should be called once after each "clear".
|
||||
|
||||
// Used only in luabindings
|
||||
void addLocalScript(const MWWorld::Ptr&, const std::string& scriptPath);
|
||||
// Used only in Lua bindings
|
||||
void addCustomLocalScript(const MWWorld::Ptr&, int scriptId);
|
||||
void addAction(std::unique_ptr<Action>&& action) { mActionQueue.push_back(std::move(action)); }
|
||||
void addTeleportPlayerAction(std::unique_ptr<TeleportAction>&& action) { mTeleportPlayerAction = std::move(action); }
|
||||
void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); }
|
||||
@ -81,21 +69,27 @@ namespace MWLua
|
||||
void reloadAllScripts() override;
|
||||
|
||||
// Used to call Lua callbacks from C++
|
||||
void queueCallback(Callback callback, sol::object arg) { mQueuedCallbacks.push_back({std::move(callback), std::move(arg)}); }
|
||||
void queueCallback(LuaUtil::Callback callback, sol::object arg)
|
||||
{
|
||||
mQueuedCallbacks.push_back({std::move(callback), std::move(arg)});
|
||||
}
|
||||
|
||||
// Wraps Lua callback into an std::function.
|
||||
// NOTE: Resulted function is not thread safe. Can not be used while LuaManager::update() or
|
||||
// any other Lua-related function is running.
|
||||
template <class Arg>
|
||||
std::function<void(Arg)> wrapLuaCallback(const Callback& c)
|
||||
std::function<void(Arg)> wrapLuaCallback(const LuaUtil::Callback& c)
|
||||
{
|
||||
return [this, c](Arg arg) { this->queueCallback(c, sol::make_object(c.mFunc.lua_state(), arg)); };
|
||||
}
|
||||
|
||||
private:
|
||||
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr);
|
||||
void initConfiguration();
|
||||
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags);
|
||||
|
||||
bool mInitialized = false;
|
||||
bool mGlobalScriptsStarted = false;
|
||||
LuaUtil::ScriptsConfiguration mConfiguration;
|
||||
LuaUtil::LuaState mLua;
|
||||
sol::table mNearbyPackage;
|
||||
sol::table mUserInterfacePackage;
|
||||
@ -104,12 +98,12 @@ namespace MWLua
|
||||
sol::table mLocalSettingsPackage;
|
||||
sol::table mPlayerSettingsPackage;
|
||||
|
||||
std::vector<std::string> mGlobalScriptList;
|
||||
GlobalScripts mGlobalScripts{&mLua};
|
||||
std::set<LocalScripts*> mActiveLocalScripts;
|
||||
WorldView mWorldView;
|
||||
|
||||
bool mPlayerChanged = false;
|
||||
bool mNewGameStarted = false;
|
||||
MWWorld::Ptr mPlayer;
|
||||
|
||||
GlobalEventQueue mGlobalEvents;
|
||||
@ -127,7 +121,7 @@ namespace MWLua
|
||||
|
||||
struct CallbackWithData
|
||||
{
|
||||
Callback mCallback;
|
||||
LuaUtil::Callback mCallback;
|
||||
sol::object mArg;
|
||||
};
|
||||
std::vector<CallbackWithData> mQueuedCallbacks;
|
||||
|
@ -1,19 +1,6 @@
|
||||
#include "object.hpp"
|
||||
|
||||
#include "../mwclass/activator.hpp"
|
||||
#include "../mwclass/armor.hpp"
|
||||
#include "../mwclass/book.hpp"
|
||||
#include "../mwclass/clothing.hpp"
|
||||
#include "../mwclass/container.hpp"
|
||||
#include "../mwclass/creature.hpp"
|
||||
#include "../mwclass/door.hpp"
|
||||
#include "../mwclass/ingredient.hpp"
|
||||
#include "../mwclass/light.hpp"
|
||||
#include "../mwclass/misc.hpp"
|
||||
#include "../mwclass/npc.hpp"
|
||||
#include "../mwclass/potion.hpp"
|
||||
#include "../mwclass/static.hpp"
|
||||
#include "../mwclass/weapon.hpp"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
@ -23,28 +10,34 @@ namespace MWLua
|
||||
return std::to_string(id.mIndex) + "_" + std::to_string(id.mContentFile);
|
||||
}
|
||||
|
||||
const static std::map<std::type_index, std::string_view> classNames = {
|
||||
{typeid(MWClass::Activator), "Activator"},
|
||||
{typeid(MWClass::Armor), "Armor"},
|
||||
{typeid(MWClass::Book), "Book"},
|
||||
{typeid(MWClass::Clothing), "Clothing"},
|
||||
{typeid(MWClass::Container), "Container"},
|
||||
{typeid(MWClass::Creature), "Creature"},
|
||||
{typeid(MWClass::Door), "Door"},
|
||||
{typeid(MWClass::Ingredient), "Ingredient"},
|
||||
{typeid(MWClass::Light), "Light"},
|
||||
{typeid(MWClass::Miscellaneous), "Miscellaneous"},
|
||||
{typeid(MWClass::Npc), "NPC"},
|
||||
{typeid(MWClass::Potion), "Potion"},
|
||||
{typeid(MWClass::Static), "Static"},
|
||||
{typeid(MWClass::Weapon), "Weapon"},
|
||||
struct LuaObjectTypeInfo
|
||||
{
|
||||
std::string_view mName;
|
||||
ESM::LuaScriptCfg::Flags mFlag = 0;
|
||||
};
|
||||
|
||||
std::string_view getMWClassName(const std::type_index& cls_type, std::string_view fallback)
|
||||
const static std::unordered_map<ESM::RecNameInts, LuaObjectTypeInfo> luaObjectTypeInfo = {
|
||||
{ESM::REC_ACTI, {"Activator", ESM::LuaScriptCfg::sActivator}},
|
||||
{ESM::REC_ARMO, {"Armor", ESM::LuaScriptCfg::sArmor}},
|
||||
{ESM::REC_BOOK, {"Book", ESM::LuaScriptCfg::sBook}},
|
||||
{ESM::REC_CLOT, {"Clothing", ESM::LuaScriptCfg::sClothing}},
|
||||
{ESM::REC_CONT, {"Container", ESM::LuaScriptCfg::sContainer}},
|
||||
{ESM::REC_CREA, {"Creature", ESM::LuaScriptCfg::sCreature}},
|
||||
{ESM::REC_DOOR, {"Door", ESM::LuaScriptCfg::sDoor}},
|
||||
{ESM::REC_INGR, {"Ingredient", ESM::LuaScriptCfg::sIngredient}},
|
||||
{ESM::REC_LIGH, {"Light", ESM::LuaScriptCfg::sLight}},
|
||||
{ESM::REC_MISC, {"Miscellaneous", ESM::LuaScriptCfg::sMiscItem}},
|
||||
{ESM::REC_NPC_, {"NPC", ESM::LuaScriptCfg::sNPC}},
|
||||
{ESM::REC_ALCH, {"Potion", ESM::LuaScriptCfg::sPotion}},
|
||||
{ESM::REC_STAT, {"Static"}},
|
||||
{ESM::REC_WEAP, {"Weapon", ESM::LuaScriptCfg::sWeapon}},
|
||||
};
|
||||
|
||||
std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback)
|
||||
{
|
||||
auto it = classNames.find(cls_type);
|
||||
if (it != classNames.end())
|
||||
return it->second;
|
||||
auto it = luaObjectTypeInfo.find(type);
|
||||
if (it != luaObjectTypeInfo.end())
|
||||
return it->second.mName;
|
||||
else
|
||||
return fallback;
|
||||
}
|
||||
@ -55,13 +48,31 @@ namespace MWLua
|
||||
return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker";
|
||||
}
|
||||
|
||||
std::string_view getMWClassName(const MWWorld::Ptr& ptr)
|
||||
std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
// Behaviour of this function is a part of OpenMW Lua API. We can not just return
|
||||
// `ptr.getTypeDescription()` because its implementation is distributed over many files
|
||||
// and can be accidentally changed. We use `ptr.getTypeDescription()` only as a fallback
|
||||
// for types that are not present in `luaObjectTypeInfo` (for such types result stability
|
||||
// is not necessary because they are not listed in OpenMW Lua documentation).
|
||||
if (ptr.getCellRef().getRefIdRef() == "player")
|
||||
return "Player";
|
||||
if (isMarker(ptr))
|
||||
return "Marker";
|
||||
return getMWClassName(typeid(ptr.getClass()));
|
||||
return getLuaObjectTypeName(static_cast<ESM::RecNameInts>(ptr.getType()), /*fallback=*/ptr.getTypeDescription());
|
||||
}
|
||||
|
||||
ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
if (ptr.getCellRef().getRefIdRef() == "player")
|
||||
return ESM::LuaScriptCfg::sPlayer;
|
||||
if (isMarker(ptr))
|
||||
return 0;
|
||||
auto it = luaObjectTypeInfo.find(static_cast<ESM::RecNameInts>(ptr.getType()));
|
||||
if (it != luaObjectTypeInfo.end())
|
||||
return it->second.mFlag;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string ptrToString(const MWWorld::Ptr& ptr)
|
||||
@ -69,7 +80,7 @@ namespace MWLua
|
||||
std::string res = "object";
|
||||
res.append(idToString(getId(ptr)));
|
||||
res.append(" (");
|
||||
res.append(getMWClassName(ptr));
|
||||
res.append(getLuaObjectTypeName(ptr));
|
||||
res.append(", ");
|
||||
res.append(ptr.getCellRef().getRefIdRef());
|
||||
res.append(")");
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include <typeindex>
|
||||
|
||||
#include <components/esm/cellref.hpp>
|
||||
#include <components/esm/defs.hpp>
|
||||
#include <components/esm/luascripts.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
@ -19,8 +21,12 @@ namespace MWLua
|
||||
std::string idToString(const ObjectId& id);
|
||||
std::string ptrToString(const MWWorld::Ptr& ptr);
|
||||
bool isMarker(const MWWorld::Ptr& ptr);
|
||||
std::string_view getMWClassName(const std::type_index& cls_type, std::string_view fallback = "Unknown");
|
||||
std::string_view getMWClassName(const MWWorld::Ptr& ptr);
|
||||
std::string_view getLuaObjectTypeName(ESM::RecNameInts recordType, std::string_view fallback = "Unknown");
|
||||
std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr);
|
||||
|
||||
// Each script has a set of flags that controls to which objects the script should be
|
||||
// automatically attached. This function maps each object types to one of the flags.
|
||||
ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr);
|
||||
|
||||
// Holds a mapping ObjectId -> MWWord::Ptr.
|
||||
class ObjectRegistry
|
||||
@ -64,7 +70,7 @@ namespace MWLua
|
||||
ObjectId id() const { return mId; }
|
||||
|
||||
std::string toString() const;
|
||||
std::string_view type() const { return getMWClassName(ptr()); }
|
||||
std::string_view type() const { return getLuaObjectTypeName(ptr()); }
|
||||
|
||||
// Updates and returns the underlying Ptr. Throws an exception if object is not available.
|
||||
const MWWorld::Ptr& ptr() const;
|
||||
|
@ -42,13 +42,12 @@ namespace MWLua
|
||||
template <typename ObjT>
|
||||
using Cell = std::conditional_t<std::is_same_v<ObjT, LObject>, LCell, GCell>;
|
||||
|
||||
template <class Class>
|
||||
static const MWWorld::Ptr& requireClass(const MWWorld::Ptr& ptr)
|
||||
static const MWWorld::Ptr& requireRecord(ESM::RecNameInts recordType, const MWWorld::Ptr& ptr)
|
||||
{
|
||||
if (typeid(Class) != typeid(ptr.getClass()))
|
||||
if (ptr.getType() != recordType)
|
||||
{
|
||||
std::string msg = "Requires type '";
|
||||
msg.append(getMWClassName(typeid(Class)));
|
||||
msg.append(getLuaObjectTypeName(recordType));
|
||||
msg.append("', but applied to ");
|
||||
msg.append(ptrToString(ptr));
|
||||
throw std::runtime_error(msg);
|
||||
@ -141,9 +140,43 @@ namespace MWLua
|
||||
|
||||
if constexpr (std::is_same_v<ObjectT, GObject>)
|
||||
{ // Only for global scripts
|
||||
objectT["addScript"] = [luaManager=context.mLuaManager](const GObject& object, const std::string& path)
|
||||
objectT["addScript"] = [lua=context.mLua, luaManager=context.mLuaManager](const GObject& object, std::string_view path)
|
||||
{
|
||||
luaManager->addLocalScript(object.ptr(), path);
|
||||
const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration();
|
||||
std::optional<int> scriptId = cfg.findId(path);
|
||||
if (!scriptId)
|
||||
throw std::runtime_error("Unknown script: " + std::string(path));
|
||||
if (!(cfg[*scriptId].mFlags & ESM::LuaScriptCfg::sCustom))
|
||||
throw std::runtime_error("Script without CUSTOM tag can not be added dynamically: " + std::string(path));
|
||||
luaManager->addCustomLocalScript(object.ptr(), *scriptId);
|
||||
};
|
||||
objectT["hasScript"] = [lua=context.mLua](const GObject& object, std::string_view path)
|
||||
{
|
||||
const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration();
|
||||
std::optional<int> scriptId = cfg.findId(path);
|
||||
if (!scriptId)
|
||||
return false;
|
||||
MWWorld::Ptr ptr = object.ptr();
|
||||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||
if (localScripts)
|
||||
return localScripts->hasScript(*scriptId);
|
||||
else
|
||||
return false;
|
||||
};
|
||||
objectT["removeScript"] = [lua=context.mLua](const GObject& object, std::string_view path)
|
||||
{
|
||||
const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration();
|
||||
std::optional<int> scriptId = cfg.findId(path);
|
||||
if (!scriptId)
|
||||
throw std::runtime_error("Unknown script: " + std::string(path));
|
||||
MWWorld::Ptr ptr = object.ptr();
|
||||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||
if (!localScripts || !localScripts->hasScript(*scriptId))
|
||||
throw std::runtime_error("There is no script " + std::string(path) + " on " + ptrToString(ptr));
|
||||
ESM::LuaScriptCfg::Flags flags = cfg[*scriptId].mFlags;
|
||||
if ((flags & (localScripts->getAutoStartMode() | ESM::LuaScriptCfg::sCustom)) != ESM::LuaScriptCfg::sCustom)
|
||||
throw std::runtime_error("Autostarted script can not be removed: " + std::string(path));
|
||||
localScripts->removeScript(*scriptId);
|
||||
};
|
||||
|
||||
objectT["teleport"] = [luaManager=context.mLuaManager](const GObject& object, std::string_view cell,
|
||||
@ -189,7 +222,7 @@ namespace MWLua
|
||||
template <class ObjectT>
|
||||
static void addDoorBindings(sol::usertype<ObjectT>& objectT, const Context& context)
|
||||
{
|
||||
auto ptr = [](const ObjectT& o) -> const MWWorld::Ptr& { return requireClass<MWClass::Door>(o.ptr()); };
|
||||
auto ptr = [](const ObjectT& o) -> const MWWorld::Ptr& { return requireRecord(ESM::REC_DOOR, o.ptr()); };
|
||||
|
||||
objectT["isTeleport"] = sol::readonly_property([ptr](const ObjectT& o)
|
||||
{
|
||||
|
@ -13,7 +13,7 @@ namespace MWLua
|
||||
class PlayerScripts : public LocalScripts
|
||||
{
|
||||
public:
|
||||
PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj)
|
||||
PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj, ESM::LuaScriptCfg::sPlayer)
|
||||
{
|
||||
registerEngineHandlers({&mKeyPressHandlers, &mKeyReleaseHandlers,
|
||||
&mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers,
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include "activespells.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/misc/rng.hpp>
|
||||
@ -14,6 +16,8 @@
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
@ -96,6 +100,11 @@ namespace MWMechanics
|
||||
, mType(params.mType), mWorsenings(params.mWorsenings), mNextWorsening({params.mNextWorsening})
|
||||
{}
|
||||
|
||||
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor)
|
||||
: mId(params.mId), mDisplayName(params.mDisplayName), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
|
||||
, mSlot(params.mSlot), mType(params.mType), mWorsenings(-1)
|
||||
{}
|
||||
|
||||
ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const
|
||||
{
|
||||
ESM::ActiveSpells::ActiveSpellParams params;
|
||||
@ -220,10 +229,19 @@ namespace MWMechanics
|
||||
{
|
||||
const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid?
|
||||
bool removedSpell = false;
|
||||
std::optional<ActiveSpellParams> reflected;
|
||||
for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
|
||||
{
|
||||
bool remove = applyMagicEffect(ptr, caster, *spellIt, *it, duration);
|
||||
if(remove)
|
||||
auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration);
|
||||
if(result == MagicApplicationResult::REFLECTED)
|
||||
{
|
||||
if(!reflected)
|
||||
reflected = {*spellIt, ptr};
|
||||
auto& reflectedEffect = reflected->mEffects.emplace_back(*it);
|
||||
reflectedEffect.mFlags = ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption;
|
||||
it = spellIt->mEffects.erase(it);
|
||||
}
|
||||
else if(result == MagicApplicationResult::REMOVED)
|
||||
it = spellIt->mEffects.erase(it);
|
||||
else
|
||||
++it;
|
||||
@ -231,6 +249,14 @@ namespace MWMechanics
|
||||
if(removedSpell)
|
||||
break;
|
||||
}
|
||||
if(reflected)
|
||||
{
|
||||
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find("VFX_Reflect");
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
|
||||
if(animation && !reflectStatic->mModel.empty())
|
||||
animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string());
|
||||
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected);
|
||||
}
|
||||
if(removedSpell)
|
||||
continue;
|
||||
|
||||
|
@ -50,6 +50,8 @@ namespace MWMechanics
|
||||
|
||||
ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor);
|
||||
|
||||
ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor);
|
||||
|
||||
ESM::ActiveSpells::ActiveSpellParams toEsm() const;
|
||||
|
||||
friend class ActiveSpells;
|
||||
|
@ -240,10 +240,7 @@ namespace MWMechanics
|
||||
{
|
||||
// magic effects
|
||||
adjustMagicEffects (ptr, duration);
|
||||
if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats())
|
||||
calculateDynamicStats (ptr);
|
||||
|
||||
calculateCreatureStatModifiers (ptr, duration);
|
||||
// fatigue restoration
|
||||
calculateRestoration(ptr, duration);
|
||||
}
|
||||
@ -654,29 +651,6 @@ namespace MWMechanics
|
||||
updateSummons(creature, mTimerDisposeSummonsCorpses == 0.f);
|
||||
}
|
||||
|
||||
void Actors::calculateDynamicStats (const MWWorld::Ptr& ptr)
|
||||
{
|
||||
CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr);
|
||||
|
||||
float intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified();
|
||||
|
||||
float base = 1.f;
|
||||
if (ptr == getPlayer())
|
||||
base = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fPCbaseMagickaMult")->mValue.getFloat();
|
||||
else
|
||||
base = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fNPCbaseMagickaMult")->mValue.getFloat();
|
||||
|
||||
double magickaFactor = base +
|
||||
creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1;
|
||||
|
||||
DynamicStat<float> magicka = creatureStats.getMagicka();
|
||||
float diff = (static_cast<int>(magickaFactor*intelligence)) - magicka.getBase();
|
||||
float currentToBaseRatio = magicka.getBase() > 0 ? magicka.getCurrent() / magicka.getBase() : 0;
|
||||
magicka.setModified(magicka.getModified() + diff, 0);
|
||||
magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true);
|
||||
creatureStats.setMagicka(magicka);
|
||||
}
|
||||
|
||||
void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, double hours, bool sleep)
|
||||
{
|
||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
|
||||
@ -771,14 +745,6 @@ namespace MWMechanics
|
||||
stats.setFatigue (fatigue);
|
||||
}
|
||||
|
||||
void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration)
|
||||
{
|
||||
CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr);
|
||||
|
||||
if (creatureStats.needToRecalcDynamicStats())
|
||||
calculateDynamicStats(ptr);
|
||||
}
|
||||
|
||||
bool Actors::isAttackPreparing(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
PtrActorMap::iterator it = mActors.find(ptr);
|
||||
@ -1047,13 +1013,10 @@ namespace MWMechanics
|
||||
void Actors::updateProcessingRange()
|
||||
{
|
||||
// We have to cap it since using high values (larger than 7168) will make some quests harder or impossible to complete (bug #1876)
|
||||
static const float maxProcessingRange = 7168.f;
|
||||
static const float minProcessingRange = maxProcessingRange / 2.f;
|
||||
static const float maxRange = 7168.f;
|
||||
static const float minRange = maxRange / 2.f;
|
||||
|
||||
float actorsProcessingRange = Settings::Manager::getFloat("actors processing range", "Game");
|
||||
actorsProcessingRange = std::min(actorsProcessingRange, maxProcessingRange);
|
||||
actorsProcessingRange = std::max(actorsProcessingRange, minProcessingRange);
|
||||
mActorsProcessingRange = actorsProcessingRange;
|
||||
mActorsProcessingRange = std::clamp(Settings::Manager::getFloat("actors processing range", "Game"), minRange, maxRange);
|
||||
}
|
||||
|
||||
void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately)
|
||||
@ -1349,7 +1312,7 @@ namespace MWMechanics
|
||||
angleToApproachingActor = std::atan2(deltaPos.x(), deltaPos.y());
|
||||
osg::Vec2f posAtT = relPos + relSpeed * t;
|
||||
float coef = (posAtT.x() * relSpeed.x() + posAtT.y() * relSpeed.y()) / (collisionDist * collisionDist * maxSpeed);
|
||||
coef *= osg::clampBetween((maxDistForPartialAvoiding - dist) / (maxDistForPartialAvoiding - maxDistForStrictAvoiding), 0.f, 1.f);
|
||||
coef *= std::clamp((maxDistForPartialAvoiding - dist) / (maxDistForPartialAvoiding - maxDistForStrictAvoiding), 0.f, 1.f);
|
||||
movementCorrection = posAtT * coef;
|
||||
if (otherPtr.getClass().getCreatureStats(otherPtr).isDead())
|
||||
// In case of dead body still try to go around (it looks natural), but reduce the correction twice.
|
||||
@ -1530,15 +1493,13 @@ namespace MWMechanics
|
||||
stats.getAiSequence().execute(iter->first, *ctrl, duration, /*outOfRange*/true);
|
||||
}
|
||||
|
||||
if(iter->first.getClass().isNpc())
|
||||
if(inProcessingRange && iter->first.getClass().isNpc())
|
||||
{
|
||||
// We can not update drowning state for actors outside of AI distance - they can not resurface to breathe
|
||||
if (inProcessingRange)
|
||||
updateDrowning(iter->first, duration, ctrl->isKnockedOut(), isPlayer);
|
||||
|
||||
if (timerUpdateEquippedLight == 0)
|
||||
updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches);
|
||||
updateDrowning(iter->first, duration, ctrl->isKnockedOut(), isPlayer);
|
||||
}
|
||||
if(timerUpdateEquippedLight == 0 && iter->first.getClass().hasInventoryStore(iter->first))
|
||||
updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches);
|
||||
|
||||
if (luaControls && isConscious(iter->first))
|
||||
{
|
||||
@ -1711,10 +1672,6 @@ namespace MWMechanics
|
||||
if (iter->first.getType() == ESM::Creature::sRecordId)
|
||||
soulTrap(iter->first);
|
||||
|
||||
// Magic effects will be reset later, and the magic effect that could kill the actor
|
||||
// needs to be determined now
|
||||
calculateCreatureStatModifiers(iter->first, 0);
|
||||
|
||||
if (cls.isEssential(iter->first))
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}");
|
||||
}
|
||||
@ -1730,8 +1687,6 @@ namespace MWMechanics
|
||||
// Make sure spell effects are removed
|
||||
purgeSpellEffects(stats.getActorId());
|
||||
|
||||
// Reset dynamic stats, attributes and skills
|
||||
calculateCreatureStatModifiers(iter->first, 0);
|
||||
stats.getMagicEffects().add(ESM::MagicEffect::Vampirism, vampirism);
|
||||
|
||||
if (isPlayer)
|
||||
@ -1816,10 +1771,6 @@ namespace MWMechanics
|
||||
continue;
|
||||
|
||||
adjustMagicEffects (iter->first, duration);
|
||||
if (iter->first.getClass().getCreatureStats(iter->first).needToRecalcDynamicStats())
|
||||
calculateDynamicStats (iter->first);
|
||||
|
||||
calculateCreatureStatModifiers (iter->first, duration);
|
||||
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(iter->first);
|
||||
if (animation)
|
||||
@ -2209,7 +2160,6 @@ namespace MWMechanics
|
||||
void Actors::updateMagicEffects(const MWWorld::Ptr &ptr)
|
||||
{
|
||||
adjustMagicEffects(ptr, 0.f);
|
||||
calculateCreatureStatModifiers(ptr, 0.f);
|
||||
}
|
||||
|
||||
bool Actors::isReadyToBlock(const MWWorld::Ptr &ptr) const
|
||||
|
@ -43,10 +43,6 @@ namespace MWMechanics
|
||||
|
||||
void adjustMagicEffects (const MWWorld::Ptr& creature, float duration);
|
||||
|
||||
void calculateDynamicStats (const MWWorld::Ptr& ptr);
|
||||
|
||||
void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration);
|
||||
|
||||
void calculateRestoration (const MWWorld::Ptr& ptr, float duration);
|
||||
|
||||
void updateDrowning (const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer);
|
||||
|
@ -29,4 +29,12 @@ namespace MWMechanics
|
||||
const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects();
|
||||
return effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0;
|
||||
}
|
||||
|
||||
CreatureCustomDataResetter::CreatureCustomDataResetter(const MWWorld::Ptr& ptr) : mPtr(ptr) {}
|
||||
|
||||
CreatureCustomDataResetter::~CreatureCustomDataResetter()
|
||||
{
|
||||
if(!mPtr.isEmpty())
|
||||
mPtr.getRefData().setCustomData({});
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +86,14 @@ namespace MWMechanics
|
||||
template void modifyBaseInventory<ESM::Creature>(const std::string& actorId, const std::string& itemId, int amount);
|
||||
template void modifyBaseInventory<ESM::NPC>(const std::string& actorId, const std::string& itemId, int amount);
|
||||
template void modifyBaseInventory<ESM::Container>(const std::string& containerId, const std::string& itemId, int amount);
|
||||
|
||||
struct CreatureCustomDataResetter
|
||||
{
|
||||
MWWorld::Ptr mPtr;
|
||||
|
||||
CreatureCustomDataResetter(const MWWorld::Ptr& ptr);
|
||||
~CreatureCustomDataResetter();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -2044,7 +2044,7 @@ void CharacterController::update(float duration)
|
||||
mIsMovingBackward = vec.y() < 0;
|
||||
|
||||
float maxDelta = osg::PI * duration * (2.5f - cosDelta);
|
||||
delta = osg::clampBetween(delta, -maxDelta, maxDelta);
|
||||
delta = std::clamp(delta, -maxDelta, maxDelta);
|
||||
stats.setSideMovementAngle(stats.getSideMovementAngle() + delta);
|
||||
effectiveRotation += delta;
|
||||
}
|
||||
@ -2286,7 +2286,7 @@ void CharacterController::update(float duration)
|
||||
float swimmingPitch = mAnimation->getBodyPitchRadians();
|
||||
float targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0];
|
||||
float maxSwimPitchDelta = 3.0f * duration;
|
||||
swimmingPitch += osg::clampBetween(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta);
|
||||
swimmingPitch += std::clamp(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta);
|
||||
mAnimation->setBodyPitchRadians(swimmingPitch);
|
||||
}
|
||||
else
|
||||
@ -2522,7 +2522,7 @@ void CharacterController::unpersistAnimationState()
|
||||
{
|
||||
float start = mAnimation->getTextKeyTime(anim.mGroup+": start");
|
||||
float stop = mAnimation->getTextKeyTime(anim.mGroup+": stop");
|
||||
float time = std::max(start, std::min(stop, anim.mTime));
|
||||
float time = std::clamp(anim.mTime, start, stop);
|
||||
complete = (time - start) / (stop - start);
|
||||
}
|
||||
|
||||
@ -2746,7 +2746,7 @@ void CharacterController::setVisibility(float visibility)
|
||||
float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude();
|
||||
if (chameleon)
|
||||
{
|
||||
alpha *= std::min(0.75f, std::max(0.25f, (100.f - chameleon)/100.f));
|
||||
alpha *= std::clamp(1.f - chameleon / 100.f, 0.25f, 0.75f);
|
||||
}
|
||||
|
||||
visibility = std::min(visibility, alpha);
|
||||
@ -2965,8 +2965,8 @@ void CharacterController::updateHeadTracking(float duration)
|
||||
const double xLimit = osg::DegreesToRadians(40.0);
|
||||
const double zLimit = osg::DegreesToRadians(30.0);
|
||||
double zLimitOffset = mAnimation->getUpperBodyYawRadians();
|
||||
xAngleRadians = osg::clampBetween(xAngleRadians, -xLimit, xLimit);
|
||||
zAngleRadians = osg::clampBetween(zAngleRadians, -zLimit + zLimitOffset, zLimit + zLimitOffset);
|
||||
xAngleRadians = std::clamp(xAngleRadians, -xLimit, xLimit);
|
||||
zAngleRadians = std::clamp(zAngleRadians, -zLimit + zLimitOffset, zLimit + zLimitOffset);
|
||||
|
||||
float factor = duration*5;
|
||||
factor = std::min(factor, 1.f);
|
||||
|
@ -113,10 +113,9 @@ namespace MWMechanics
|
||||
+ 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||
attackerTerm *= attackerStats.getFatigueTerm();
|
||||
|
||||
int x = int(blockerTerm - attackerTerm);
|
||||
int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger();
|
||||
int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger();
|
||||
x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x));
|
||||
const int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger();
|
||||
const int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger();
|
||||
int x = std::clamp<int>(blockerTerm - attackerTerm, iBlockMinChance, iBlockMaxChance);
|
||||
|
||||
if (Misc::Rng::roll0to99() < x)
|
||||
{
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <components/esm/esmreader.hpp>
|
||||
#include <components/esm/esmwriter.hpp>
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
|
||||
@ -22,7 +23,7 @@ namespace MWMechanics
|
||||
mTalkedTo (false), mAlarmed (false), mAttacked (false),
|
||||
mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false),
|
||||
mHitRecovery(false), mBlock(false), mMovementFlags(0),
|
||||
mFallHeight(0), mRecalcMagicka(false), 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)
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
@ -146,7 +147,7 @@ namespace MWMechanics
|
||||
mAttributes[index] = value;
|
||||
|
||||
if (index == ESM::Attribute::Intelligence)
|
||||
mRecalcMagicka = true;
|
||||
recalculateMagicka();
|
||||
else if (index == ESM::Attribute::Strength ||
|
||||
index == ESM::Attribute::Willpower ||
|
||||
index == ESM::Attribute::Agility ||
|
||||
@ -208,11 +209,10 @@ namespace MWMechanics
|
||||
|
||||
void CreatureStats::modifyMagicEffects(const MagicEffects &effects)
|
||||
{
|
||||
if (effects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier()
|
||||
!= mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier())
|
||||
mRecalcMagicka = true;
|
||||
|
||||
bool recalc = effects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier() != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier();
|
||||
mMagicEffects.setModifiers(effects);
|
||||
if(recalc)
|
||||
recalculateMagicka();
|
||||
}
|
||||
|
||||
void CreatureStats::setAiSetting (AiSetting index, Stat<int> value)
|
||||
@ -400,19 +400,26 @@ namespace MWMechanics
|
||||
return height;
|
||||
}
|
||||
|
||||
bool CreatureStats::needToRecalcDynamicStats()
|
||||
void CreatureStats::recalculateMagicka()
|
||||
{
|
||||
if (mRecalcMagicka)
|
||||
{
|
||||
mRecalcMagicka = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
auto world = MWBase::Environment::get().getWorld();
|
||||
float intelligence = getAttribute(ESM::Attribute::Intelligence).getModified();
|
||||
|
||||
void CreatureStats::setNeedRecalcDynamicStats(bool val)
|
||||
{
|
||||
mRecalcMagicka = val;
|
||||
float base = 1.f;
|
||||
const auto& player = world->getPlayerPtr();
|
||||
if (this == &player.getClass().getCreatureStats(player))
|
||||
base = world->getStore().get<ESM::GameSetting>().find("fPCbaseMagickaMult")->mValue.getFloat();
|
||||
else
|
||||
base = world->getStore().get<ESM::GameSetting>().find("fNPCbaseMagickaMult")->mValue.getFloat();
|
||||
|
||||
double magickaFactor = base + mMagicEffects.get(EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1;
|
||||
|
||||
DynamicStat<float> magicka = getMagicka();
|
||||
float diff = (static_cast<int>(magickaFactor*intelligence)) - magicka.getBase();
|
||||
float currentToBaseRatio = magicka.getBase() > 0 ? magicka.getCurrent() / magicka.getBase() : 0;
|
||||
magicka.setModified(magicka.getModified() + diff, 0);
|
||||
magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true);
|
||||
setMagicka(magicka);
|
||||
}
|
||||
|
||||
void CreatureStats::setKnockedDown(bool value)
|
||||
@ -532,7 +539,7 @@ namespace MWMechanics
|
||||
state.mFallHeight = mFallHeight; // TODO: vertical velocity (move from PhysicActor to CreatureStats?)
|
||||
state.mLastHitObject = mLastHitObject;
|
||||
state.mLastHitAttemptObject = mLastHitAttemptObject;
|
||||
state.mRecalcDynamicStats = mRecalcMagicka;
|
||||
state.mRecalcDynamicStats = false;
|
||||
state.mDrawState = mDrawState;
|
||||
state.mLevel = mLevel;
|
||||
state.mActorId = mActorId;
|
||||
@ -586,7 +593,6 @@ namespace MWMechanics
|
||||
mFallHeight = state.mFallHeight;
|
||||
mLastHitObject = state.mLastHitObject;
|
||||
mLastHitAttemptObject = state.mLastHitAttemptObject;
|
||||
mRecalcMagicka = state.mRecalcDynamicStats;
|
||||
mDrawState = DrawState_(state.mDrawState);
|
||||
mLevel = state.mLevel;
|
||||
mActorId = state.mActorId;
|
||||
@ -627,6 +633,8 @@ namespace MWMechanics
|
||||
if (state.mHasAiSettings)
|
||||
for (int i=0; i<4; ++i)
|
||||
mAiSettings[i].readState(state.mAiSettings[i]);
|
||||
if(state.mRecalcDynamicStats)
|
||||
recalculateMagicka();
|
||||
}
|
||||
|
||||
void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime)
|
||||
|
@ -65,8 +65,6 @@ namespace MWMechanics
|
||||
std::string mLastHitObject; // The last object to hit this actor
|
||||
std::string mLastHitAttemptObject; // The last object to attempt to hit this actor
|
||||
|
||||
bool mRecalcMagicka;
|
||||
|
||||
// For merchants: the last time items were restocked and gold pool refilled.
|
||||
MWWorld::TimeStamp mLastRestock;
|
||||
|
||||
@ -103,8 +101,7 @@ namespace MWMechanics
|
||||
DrawState_ getDrawState() const;
|
||||
void setDrawState(DrawState_ state);
|
||||
|
||||
bool needToRecalcDynamicStats();
|
||||
void setNeedRecalcDynamicStats(bool val);
|
||||
void recalculateMagicka();
|
||||
|
||||
float getFallHeight() const;
|
||||
void addToFallHeight(float height);
|
||||
|
@ -13,9 +13,7 @@ float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr
|
||||
const MWWorld::Ptr& player = MWMechanics::getPlayer();
|
||||
|
||||
// [-500, 500]
|
||||
int difficultySetting = Settings::Manager::getInt("difficulty", "Game");
|
||||
difficultySetting = std::min(difficultySetting, 500);
|
||||
difficultySetting = std::max(difficultySetting, -500);
|
||||
const int difficultySetting = std::clamp(Settings::Manager::getInt("difficulty", "Game"), -500, 500);
|
||||
|
||||
static const float fDifficultyMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDifficultyMult")->mValue.getFloat();
|
||||
|
||||
|
@ -356,10 +356,10 @@ namespace MWMechanics
|
||||
ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(mWeaponType)->mWeaponClass;
|
||||
if (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo)
|
||||
{
|
||||
static const float multiplier = std::max(0.f, std::min(1.0f, Settings::Manager::getFloat("projectiles enchant multiplier", "Game")));
|
||||
static const float multiplier = std::clamp(Settings::Manager::getFloat("projectiles enchant multiplier", "Game"), 0.f, 1.f);
|
||||
MWWorld::Ptr player = getPlayer();
|
||||
int itemsInInventoryCount = player.getClass().getContainerStore(player).count(mOldItemPtr.getCellRef().getRefId());
|
||||
count = std::min(itemsInInventoryCount, std::max(1, int(getGemCharge() * multiplier / enchantPoints)));
|
||||
count = player.getClass().getContainerStore(player).count(mOldItemPtr.getCellRef().getRefId());
|
||||
count = std::clamp<int>(getGemCharge() * multiplier / enchantPoints, 1, count);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,20 @@
|
||||
#include "magiceffects.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <components/esm/effectlist.hpp>
|
||||
#include <components/esm/magiceffects.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
// Round value to prevent precision issues
|
||||
void truncate(float& value)
|
||||
{
|
||||
value = std::roundf(value * 1024.f) / 1024.f;
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
EffectKey::EffectKey() : mId (0), mArg (-1) {}
|
||||
@ -74,6 +84,7 @@ namespace MWMechanics
|
||||
{
|
||||
mModifier += param.mModifier;
|
||||
mBase += param.mBase;
|
||||
truncate(mModifier);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -81,6 +92,7 @@ namespace MWMechanics
|
||||
{
|
||||
mModifier -= param.mModifier;
|
||||
mBase -= param.mBase;
|
||||
truncate(mModifier);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ namespace MWMechanics
|
||||
MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr);
|
||||
MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats (ptr);
|
||||
|
||||
npcStats.setNeedRecalcDynamicStats(true);
|
||||
npcStats.recalculateMagicka();
|
||||
|
||||
const ESM::NPC *player = ptr.get<ESM::NPC>()->mBase;
|
||||
|
||||
@ -222,7 +222,6 @@ namespace MWMechanics
|
||||
|
||||
// forced update and current value adjustments
|
||||
mActors.updateActor (ptr, 0);
|
||||
mActors.updateActor (ptr, 0);
|
||||
|
||||
for (int i=0; i<3; ++i)
|
||||
{
|
||||
@ -546,9 +545,9 @@ namespace MWMechanics
|
||||
|
||||
x += ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Charm).getMagnitude();
|
||||
|
||||
if(clamp)
|
||||
return std::max(0,std::min(int(x),100));//, normally clamped to [0..100] when used
|
||||
return int(x);
|
||||
if (clamp)
|
||||
return std::clamp<int>(x, 0, 100);//, normally clamped to [0..100] when used
|
||||
return static_cast<int>(x);
|
||||
}
|
||||
|
||||
int MechanicsManager::getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying)
|
||||
@ -650,9 +649,9 @@ namespace MWMechanics
|
||||
int flee = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Flee).getBase();
|
||||
int fight = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Fight).getBase();
|
||||
npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee,
|
||||
std::max(0, std::min(100, flee + int(std::max(iPerMinChange, s)))));
|
||||
std::clamp(flee + int(std::max(iPerMinChange, s)), 0, 100));
|
||||
npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight,
|
||||
std::max(0, std::min(100, fight + int(std::min(-iPerMinChange, -s)))));
|
||||
std::clamp(fight + int(std::min(-iPerMinChange, -s)), 0, 100));
|
||||
}
|
||||
|
||||
float c = -std::abs(floor(r * fPerDieRollMult));
|
||||
@ -690,10 +689,10 @@ namespace MWMechanics
|
||||
float s = c * fPerDieRollMult * fPerTempMult;
|
||||
int flee = npcStats.getAiSetting (CreatureStats::AI_Flee).getBase();
|
||||
int fight = npcStats.getAiSetting (CreatureStats::AI_Fight).getBase();
|
||||
npcStats.setAiSetting (CreatureStats::AI_Flee,
|
||||
std::max(0, std::min(100, flee + std::min(-int(iPerMinChange), int(-s)))));
|
||||
npcStats.setAiSetting (CreatureStats::AI_Fight,
|
||||
std::max(0, std::min(100, fight + std::max(int(iPerMinChange), int(s)))));
|
||||
npcStats.setAiSetting(CreatureStats::AI_Flee,
|
||||
std::clamp(flee + std::min(-int(iPerMinChange), int(-s)), 0, 100));
|
||||
npcStats.setAiSetting(CreatureStats::AI_Fight,
|
||||
std::clamp(fight + std::max(int(iPerMinChange), int(s)), 0, 100));
|
||||
}
|
||||
x = floor(-c * fPerDieRollMult);
|
||||
|
||||
|
@ -371,7 +371,7 @@ int MWMechanics::NpcStats::getReputation() const
|
||||
void MWMechanics::NpcStats::setReputation(int reputation)
|
||||
{
|
||||
// Reputation is capped in original engine
|
||||
mReputation = std::min(255, std::max(0, reputation));
|
||||
mReputation = std::clamp(reputation, 0, 255);
|
||||
}
|
||||
|
||||
int MWMechanics::NpcStats::getCrimeId() const
|
||||
|
@ -1,82 +0,0 @@
|
||||
#include "spellabsorption.hpp"
|
||||
|
||||
#include <components/misc/rng.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
#include "spellutil.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
float getProbability(const MWMechanics::ActiveSpells& activeSpells)
|
||||
{
|
||||
float probability = 0.f;
|
||||
for(const auto& params : activeSpells)
|
||||
{
|
||||
for(const auto& effect : params.getEffects())
|
||||
{
|
||||
if(effect.mEffectId == ESM::MagicEffect::SpellAbsorption)
|
||||
{
|
||||
if(probability == 0.f)
|
||||
probability = effect.mMagnitude / 100;
|
||||
else
|
||||
{
|
||||
// If there are different sources of SpellAbsorption effect, multiply failing probability for all effects.
|
||||
// Real absorption probability will be the (1 - total fail chance) in this case.
|
||||
float failProbability = 1.f - probability;
|
||||
failProbability *= 1.f - effect.mMagnitude / 100;
|
||||
probability = 1.f - failProbability;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return static_cast<int>(probability * 100);
|
||||
}
|
||||
|
||||
bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
|
||||
{
|
||||
if (spellId.empty() || target.isEmpty() || caster == target || !target.getClass().isActor())
|
||||
return false;
|
||||
|
||||
CreatureStats& stats = target.getClass().getCreatureStats(target);
|
||||
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f)
|
||||
return false;
|
||||
|
||||
int chance = getProbability(stats.getActiveSpells());
|
||||
if (Misc::Rng::roll0to99() >= chance)
|
||||
return false;
|
||||
|
||||
const auto& esmStore = MWBase::Environment::get().getWorld()->getStore();
|
||||
const ESM::Static* absorbStatic = esmStore.get<ESM::Static>().find("VFX_Absorb");
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
|
||||
if (animation && !absorbStatic->mModel.empty())
|
||||
animation->addEffect( "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, std::string());
|
||||
const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellId);
|
||||
int spellCost = 0;
|
||||
if (spell)
|
||||
{
|
||||
spellCost = MWMechanics::calcSpellCost(*spell);
|
||||
}
|
||||
else
|
||||
{
|
||||
const ESM::Enchantment* enchantment = esmStore.get<ESM::Enchantment>().search(spellId);
|
||||
if (enchantment)
|
||||
spellCost = getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), caster);
|
||||
}
|
||||
|
||||
// Magicka is increased by the cost of the spell
|
||||
DynamicStat<float> magicka = stats.getMagicka();
|
||||
magicka.setCurrent(magicka.getCurrent() + spellCost);
|
||||
stats.setMagicka(magicka);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
#ifndef MWMECHANICS_SPELLABSORPTION_H
|
||||
#define MWMECHANICS_SPELLABSORPTION_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
// Try to absorb a spell based on the magnitude of every Spell Absorption effect source on the target.
|
||||
bool absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
|
||||
}
|
||||
|
||||
#endif
|
@ -22,38 +22,11 @@
|
||||
#include "actorutil.hpp"
|
||||
#include "aifollow.hpp"
|
||||
#include "creaturestats.hpp"
|
||||
#include "spellabsorption.hpp"
|
||||
#include "spelleffects.hpp"
|
||||
#include "spellutil.hpp"
|
||||
#include "summoning.hpp"
|
||||
#include "weapontype.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect,
|
||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects)
|
||||
{
|
||||
if (caster.isEmpty() || caster == target || !target.getClass().isActor())
|
||||
return false;
|
||||
|
||||
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
||||
bool isUnreflectable = magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable;
|
||||
if (!isHarmful || isUnreflectable)
|
||||
return false;
|
||||
|
||||
float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude();
|
||||
if (Misc::Rng::roll0to99() >= reflect)
|
||||
return false;
|
||||
|
||||
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect");
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
|
||||
if (animation && !reflectStatic->mModel.empty())
|
||||
animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string());
|
||||
reflectedEffects.mList.emplace_back(effect);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell)
|
||||
@ -82,7 +55,7 @@ namespace MWMechanics
|
||||
}
|
||||
|
||||
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
|
||||
const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded)
|
||||
const ESM::EffectList &effects, ESM::RangeType range, bool exploded)
|
||||
{
|
||||
const bool targetIsActor = !target.isEmpty() && target.getClass().isActor();
|
||||
if (targetIsActor)
|
||||
@ -123,7 +96,6 @@ namespace MWMechanics
|
||||
}
|
||||
}
|
||||
|
||||
ESM::EffectList reflectedEffects;
|
||||
ActiveSpells::ActiveSpellParams params(*this, caster);
|
||||
|
||||
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
|
||||
@ -136,9 +108,6 @@ namespace MWMechanics
|
||||
// throughout the iteration of this spell's
|
||||
// effects, we display a "can't re-cast" message
|
||||
|
||||
// Try absorbing the spell. Some handling must still happen for absorbed effects.
|
||||
bool absorbed = absorbSpell(mId, caster, target);
|
||||
|
||||
int currentEffectIndex = 0;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
|
||||
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
|
||||
@ -167,19 +136,6 @@ namespace MWMechanics
|
||||
&& (caster.isEmpty() || !caster.getClass().isActor()))
|
||||
continue;
|
||||
|
||||
// Notify the target actor they've been hit
|
||||
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
||||
if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful)
|
||||
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
|
||||
|
||||
// Avoid proceeding further for absorbed spells.
|
||||
if (absorbed)
|
||||
continue;
|
||||
|
||||
// Reflect harmful effects
|
||||
if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects))
|
||||
continue;
|
||||
|
||||
ActiveSpells::ActiveEffect effect;
|
||||
effect.mEffectId = effectIt->mEffectID;
|
||||
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
|
||||
@ -189,13 +145,8 @@ namespace MWMechanics
|
||||
effect.mTimeLeft = 0.f;
|
||||
effect.mEffectIndex = currentEffectIndex;
|
||||
effect.mFlags = ESM::ActiveEffect::Flag_None;
|
||||
|
||||
// Avoid applying harmful effects to the player in god mode
|
||||
if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful)
|
||||
{
|
||||
effect.mMinMagnitude = 0;
|
||||
effect.mMaxMagnitude = 0;
|
||||
}
|
||||
if(mManualSpell)
|
||||
effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect;
|
||||
|
||||
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
||||
effect.mDuration = hasDuration ? static_cast<float>(effectIt->mDuration) : 1.f;
|
||||
@ -209,14 +160,14 @@ namespace MWMechanics
|
||||
// add to list of active effects, to apply in next frame
|
||||
params.getEffects().emplace_back(effect);
|
||||
|
||||
bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
|
||||
bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
|
||||
if (castByPlayer && target != caster && targetIsActor && effectAffectsHealth)
|
||||
{
|
||||
// If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar.
|
||||
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
||||
}
|
||||
|
||||
if (targetIsActor || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||
if (!targetIsActor && magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||
{
|
||||
playEffects(target, *magicEffect);
|
||||
}
|
||||
@ -227,9 +178,6 @@ namespace MWMechanics
|
||||
|
||||
if (!target.isEmpty())
|
||||
{
|
||||
if (!reflectedEffects.mList.empty())
|
||||
inflict(caster, target, reflectedEffects, range, true, exploded);
|
||||
|
||||
if (!params.getEffects().empty())
|
||||
{
|
||||
if(targetIsActor)
|
||||
@ -237,6 +185,7 @@ namespace MWMechanics
|
||||
else
|
||||
{
|
||||
// Apply effects instantly. We can ignore effect deletion since the entire params object gets deleted afterwards anyway
|
||||
// and we can ignore reflection since non-actors cannot reflect spells
|
||||
for(auto& effect : params.getEffects())
|
||||
applyMagicEffect(target, caster, params, effect, 0.f);
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ namespace MWMechanics
|
||||
/// @note \a target can be any type of object, not just actors.
|
||||
/// @note \a caster can be any type of object, or even an empty object.
|
||||
void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
|
||||
const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false);
|
||||
const ESM::EffectList& effects, ESM::RangeType range, bool exploded=false);
|
||||
};
|
||||
|
||||
void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true);
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "../mwmechanics/aifollow.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
#include "../mwmechanics/spellresistance.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
#include "../mwmechanics/summoning.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
@ -261,6 +262,97 @@ namespace
|
||||
return false;
|
||||
}
|
||||
|
||||
void absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
|
||||
{
|
||||
const auto& esmStore = MWBase::Environment::get().getWorld()->getStore();
|
||||
const ESM::Static* absorbStatic = esmStore.get<ESM::Static>().find("VFX_Absorb");
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
|
||||
if (animation && !absorbStatic->mModel.empty())
|
||||
animation->addEffect( "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, std::string());
|
||||
const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellId);
|
||||
int spellCost = 0;
|
||||
if (spell)
|
||||
{
|
||||
spellCost = MWMechanics::calcSpellCost(*spell);
|
||||
}
|
||||
else
|
||||
{
|
||||
const ESM::Enchantment* enchantment = esmStore.get<ESM::Enchantment>().search(spellId);
|
||||
if (enchantment)
|
||||
spellCost = MWMechanics::getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), caster);
|
||||
}
|
||||
|
||||
// Magicka is increased by the cost of the spell
|
||||
auto& stats = target.getClass().getCreatureStats(target);
|
||||
auto magicka = stats.getMagicka();
|
||||
magicka.setCurrent(magicka.getCurrent() + spellCost);
|
||||
stats.setMagicka(magicka);
|
||||
}
|
||||
|
||||
MWMechanics::MagicApplicationResult applyProtections(const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
|
||||
const MWMechanics::ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, const ESM::MagicEffect* magicEffect)
|
||||
{
|
||||
auto& stats = target.getClass().getCreatureStats(target);
|
||||
auto& magnitudes = stats.getMagicEffects();
|
||||
// Apply reflect and spell absorption
|
||||
if(target != caster && spellParams.getType() != ESM::ActiveSpells::Type_Enchantment && spellParams.getType() != ESM::ActiveSpells::Type_Permanent)
|
||||
{
|
||||
bool canReflect = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable) &&
|
||||
!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Reflect) && magnitudes.get(ESM::MagicEffect::Reflect).getMagnitude() > 0.f;
|
||||
bool canAbsorb = !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_SpellAbsorption) && magnitudes.get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f;
|
||||
if(canReflect || canAbsorb)
|
||||
{
|
||||
for(const auto& activeParam : stats.getActiveSpells())
|
||||
{
|
||||
for(const auto& activeEffect : activeParam.getEffects())
|
||||
{
|
||||
if(!(activeEffect.mFlags & ESM::ActiveEffect::Flag_Applied))
|
||||
continue;
|
||||
if(activeEffect.mEffectId == ESM::MagicEffect::Reflect)
|
||||
{
|
||||
if(canReflect && Misc::Rng::roll0to99() < activeEffect.mMagnitude)
|
||||
{
|
||||
return MWMechanics::MagicApplicationResult::REFLECTED;
|
||||
}
|
||||
}
|
||||
else if(activeEffect.mEffectId == ESM::MagicEffect::SpellAbsorption)
|
||||
{
|
||||
if(canAbsorb && Misc::Rng::roll0to99() < activeEffect.mMagnitude)
|
||||
{
|
||||
absorbSpell(spellParams.getId(), caster, target);
|
||||
return MWMechanics::MagicApplicationResult::REMOVED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Notify the target actor they've been hit
|
||||
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
||||
if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful)
|
||||
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
|
||||
// Apply resistances
|
||||
if(!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances))
|
||||
{
|
||||
const ESM::Spell* spell = nullptr;
|
||||
if(spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
|
||||
spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellParams.getId());
|
||||
float magnitudeMult = MWMechanics::getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes);
|
||||
if (magnitudeMult == 0)
|
||||
{
|
||||
// Fully resisted, show message
|
||||
if (target == MWMechanics::getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
||||
else if (caster == MWMechanics::getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
||||
return MWMechanics::MagicApplicationResult::REMOVED;
|
||||
}
|
||||
effect.mMinMagnitude *= magnitudeMult;
|
||||
effect.mMaxMagnitude *= magnitudeMult;
|
||||
}
|
||||
return MWMechanics::MagicApplicationResult::APPLIED;
|
||||
}
|
||||
|
||||
static const std::map<int, std::string> sBoundItemsMap{
|
||||
{ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID"},
|
||||
{ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID"},
|
||||
@ -279,7 +371,7 @@ namespace
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, bool& invalid, bool& receivedMagicDamage)
|
||||
void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, bool& invalid, bool& receivedMagicDamage, bool& recalculateMagicka)
|
||||
{
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
bool godmode = target == getPlayer() && world->getGodModeState();
|
||||
@ -539,7 +631,7 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co
|
||||
if (!target.isInCell() || !target.getCell()->isExterior() || godmode)
|
||||
break;
|
||||
float time = world->getTimeStamp().getHour();
|
||||
float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13)));
|
||||
float timeDiff = std::clamp(std::abs(time - 13.f), 0.f, 7.f);
|
||||
float damageScale = 1.f - timeDiff / 7.f;
|
||||
// When cloudy, the sun damage effect is halved
|
||||
static float fMagicSunBlockedMult = world->getStore().get<ESM::GameSetting>().find("fMagicSunBlockedMult")->mValue.getFloat();
|
||||
@ -609,7 +701,7 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co
|
||||
fortifySkill(target, effect, effect.mMagnitude);
|
||||
break;
|
||||
case ESM::MagicEffect::FortifyMaximumMagicka:
|
||||
target.getClass().getCreatureStats(target).setNeedRecalcDynamicStats(true);
|
||||
recalculateMagicka = true;
|
||||
break;
|
||||
case ESM::MagicEffect::AbsorbHealth:
|
||||
case ESM::MagicEffect::AbsorbMagicka:
|
||||
@ -682,28 +774,29 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co
|
||||
}
|
||||
}
|
||||
|
||||
bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt)
|
||||
MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt)
|
||||
{
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
bool invalid = false;
|
||||
bool receivedMagicDamage = false;
|
||||
bool recalculateMagicka = false;
|
||||
if(effect.mEffectId == ESM::MagicEffect::Corprus && spellParams.shouldWorsen())
|
||||
{
|
||||
spellParams.worsen();
|
||||
for(auto& otherEffect : spellParams.getEffects())
|
||||
{
|
||||
if(isCorprusEffect(otherEffect))
|
||||
applyMagicEffect(target, caster, spellParams, otherEffect, invalid, receivedMagicDamage);
|
||||
applyMagicEffect(target, caster, spellParams, otherEffect, invalid, receivedMagicDamage, recalculateMagicka);
|
||||
}
|
||||
if(target == getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}");
|
||||
return false;
|
||||
return MagicApplicationResult::APPLIED;
|
||||
}
|
||||
else if(effect.mEffectId == ESM::MagicEffect::Levitate && !world->isLevitationEnabled())
|
||||
{
|
||||
if(target == getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}");
|
||||
return true;
|
||||
return MagicApplicationResult::REMOVED;
|
||||
}
|
||||
const auto* magicEffect = world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId);
|
||||
if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
|
||||
@ -711,10 +804,10 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
|
||||
if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce)
|
||||
{
|
||||
effect.mTimeLeft -= dt;
|
||||
return false;
|
||||
return MagicApplicationResult::APPLIED;
|
||||
}
|
||||
else if(!dt)
|
||||
return false;
|
||||
return MagicApplicationResult::APPLIED;
|
||||
}
|
||||
if(effect.mEffectId == ESM::MagicEffect::Lock)
|
||||
{
|
||||
@ -770,28 +863,19 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects();
|
||||
if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & (ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Ignore_Resistances)))
|
||||
auto& stats = target.getClass().getCreatureStats(target);
|
||||
auto& magnitudes = stats.getMagicEffects();
|
||||
if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
|
||||
{
|
||||
const ESM::Spell* spell = nullptr;
|
||||
if(spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
|
||||
spell = world->getStore().get<ESM::Spell>().search(spellParams.getId());
|
||||
float magnitudeMult = getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes);
|
||||
if (magnitudeMult == 0)
|
||||
{
|
||||
// Fully resisted, show message
|
||||
if (target == getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
||||
else if (caster == getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
||||
return true;
|
||||
}
|
||||
effect.mMinMagnitude *= magnitudeMult;
|
||||
effect.mMaxMagnitude *= magnitudeMult;
|
||||
MagicApplicationResult result = applyProtections(target, caster, spellParams, effect, magicEffect);
|
||||
if(result != MagicApplicationResult::APPLIED)
|
||||
return result;
|
||||
}
|
||||
float oldMagnitude = 0.f;
|
||||
if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
|
||||
oldMagnitude = effect.mMagnitude;
|
||||
else if(spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
|
||||
playEffects(target, *magicEffect);
|
||||
float magnitude = roll(effect);
|
||||
//Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here
|
||||
effect.mMagnitude = magnitude;
|
||||
@ -809,13 +893,13 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
|
||||
effect.mMagnitude = oldMagnitude;
|
||||
effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
|
||||
effect.mTimeLeft -= dt;
|
||||
return false;
|
||||
return MagicApplicationResult::APPLIED;
|
||||
}
|
||||
}
|
||||
if(effect.mEffectId == ESM::MagicEffect::Corprus)
|
||||
spellParams.worsen();
|
||||
else
|
||||
applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage);
|
||||
applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage, recalculateMagicka);
|
||||
effect.mMagnitude = magnitude;
|
||||
magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(effect.mMagnitude - oldMagnitude));
|
||||
}
|
||||
@ -832,7 +916,9 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
|
||||
effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
|
||||
if (receivedMagicDamage && target == getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
|
||||
return false;
|
||||
if(recalculateMagicka)
|
||||
target.getClass().getCreatureStats(target).recalculateMagicka();
|
||||
return MagicApplicationResult::APPLIED;
|
||||
}
|
||||
|
||||
void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect)
|
||||
@ -981,7 +1067,7 @@ void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellPara
|
||||
fortifySkill(target, effect, -effect.mMagnitude);
|
||||
break;
|
||||
case ESM::MagicEffect::FortifyMaximumMagicka:
|
||||
target.getClass().getCreatureStats(target).setNeedRecalcDynamicStats(true);
|
||||
target.getClass().getCreatureStats(target).recalculateMagicka();
|
||||
break;
|
||||
case ESM::MagicEffect::AbsorbAttribute:
|
||||
{
|
||||
|
@ -10,8 +10,13 @@
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
enum class MagicApplicationResult
|
||||
{
|
||||
APPLIED, REMOVED, REFLECTED
|
||||
};
|
||||
|
||||
// Applies a tick of a single effect. Returns true if the effect should be removed immediately
|
||||
bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt);
|
||||
MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt);
|
||||
|
||||
// Undoes permanent effects created by ESM::MagicEffect::AppliedOnce
|
||||
void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spell, const ESM::ActiveEffect& effect);
|
||||
|
@ -162,7 +162,10 @@ namespace MWMechanics
|
||||
float castChance = baseChance + castBonus;
|
||||
castChance *= stats.getFatigueTerm();
|
||||
|
||||
return std::max(0.f, cap ? std::min(100.f, castChance) : castChance);
|
||||
if (cap)
|
||||
return std::clamp(castChance, 0.f, 100.f);
|
||||
|
||||
return std::max(castChance, 0.f);
|
||||
}
|
||||
|
||||
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
|
||||
|
@ -128,8 +128,7 @@ namespace MWMechanics
|
||||
}
|
||||
|
||||
// Take hit chance in account, but do not allow rating become negative.
|
||||
float chance = getHitChance(actor, enemy, value) / 100.f;
|
||||
rating *= std::min(1.f, std::max(0.01f, chance));
|
||||
rating *= std::clamp(getHitChance(actor, enemy, value) / 100.f, 0.01f, 1.f);
|
||||
|
||||
if (weapclass != ESM::WeaponType::Ammo)
|
||||
rating *= weapon->mData.mSpeed;
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include "collisiontype.hpp"
|
||||
#include "mtphysics.hpp"
|
||||
#include "trace.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
@ -21,8 +22,8 @@ namespace MWPhysics
|
||||
|
||||
Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk)
|
||||
: mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false)
|
||||
, mMeshTranslation(shape->mCollisionBox.center), mOriginalHalfExtents(shape->mCollisionBox.extents)
|
||||
, mVelocity(0,0,0), mStuckFrames(0), mLastStuckPosition{0, 0, 0}
|
||||
, mMeshTranslation(shape->mCollisionBox.mCenter), mOriginalHalfExtents(shape->mCollisionBox.mExtents)
|
||||
, mStuckFrames(0), mLastStuckPosition{0, 0, 0}
|
||||
, mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
|
||||
, mInternalCollisionMode(true)
|
||||
, mExternalCollisionMode(true)
|
||||
@ -123,7 +124,6 @@ void Actor::updatePosition()
|
||||
mSimulationPosition = worldPosition;
|
||||
mPositionOffset = osg::Vec3f();
|
||||
mStandingOnPtr = nullptr;
|
||||
mSkipCollisions = true;
|
||||
mSkipSimulation = true;
|
||||
}
|
||||
|
||||
@ -133,11 +133,6 @@ void Actor::setSimulationPosition(const osg::Vec3f& position)
|
||||
mSimulationPosition = position;
|
||||
}
|
||||
|
||||
osg::Vec3f Actor::getSimulationPosition() const
|
||||
{
|
||||
return mSimulationPosition;
|
||||
}
|
||||
|
||||
osg::Vec3f Actor::getScaledMeshTranslation() const
|
||||
{
|
||||
return mRotation * osg::componentMultiply(mMeshTranslation, mScale);
|
||||
@ -167,17 +162,19 @@ bool Actor::setPosition(const osg::Vec3f& position)
|
||||
{
|
||||
std::scoped_lock lock(mPositionMutex);
|
||||
applyOffsetChange();
|
||||
bool hasChanged = mPosition != position || mWorldPositionChanged;
|
||||
mPreviousPosition = mPosition;
|
||||
mPosition = position;
|
||||
bool hasChanged = (mPosition != position && !mSkipSimulation) || mWorldPositionChanged;
|
||||
if (!mSkipSimulation)
|
||||
{
|
||||
mPreviousPosition = mPosition;
|
||||
mPosition = position;
|
||||
}
|
||||
return hasChanged;
|
||||
}
|
||||
|
||||
void Actor::adjustPosition(const osg::Vec3f& offset, bool ignoreCollisions)
|
||||
void Actor::adjustPosition(const osg::Vec3f& offset)
|
||||
{
|
||||
std::scoped_lock lock(mPositionMutex);
|
||||
mPositionOffset += offset;
|
||||
mSkipCollisions = mSkipCollisions || ignoreCollisions;
|
||||
}
|
||||
|
||||
void Actor::applyOffsetChange()
|
||||
@ -191,16 +188,6 @@ void Actor::applyOffsetChange()
|
||||
mWorldPositionChanged = true;
|
||||
}
|
||||
|
||||
osg::Vec3f Actor::getPosition() const
|
||||
{
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
osg::Vec3f Actor::getPreviousPosition() const
|
||||
{
|
||||
return mPreviousPosition;
|
||||
}
|
||||
|
||||
void Actor::setRotation(osg::Quat quat)
|
||||
{
|
||||
std::scoped_lock lock(mPositionMutex);
|
||||
@ -288,19 +275,15 @@ void Actor::setStandingOnPtr(const MWWorld::Ptr& ptr)
|
||||
mStandingOnPtr = ptr;
|
||||
}
|
||||
|
||||
bool Actor::skipCollisions()
|
||||
bool Actor::canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const
|
||||
{
|
||||
return std::exchange(mSkipCollisions, false);
|
||||
}
|
||||
|
||||
void Actor::setVelocity(osg::Vec3f velocity)
|
||||
{
|
||||
mVelocity = velocity;
|
||||
}
|
||||
|
||||
osg::Vec3f Actor::velocity()
|
||||
{
|
||||
return std::exchange(mVelocity, osg::Vec3f());
|
||||
const float halfZ = getHalfExtents().z();
|
||||
const osg::Vec3f actorPosition = getPosition();
|
||||
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
|
||||
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
|
||||
MWPhysics::ActorTracer tracer;
|
||||
tracer.doTrace(getCollisionObject(), startingPosition, destinationPosition, world);
|
||||
return (tracer.mFraction >= 1.0f);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
class btCollisionShape;
|
||||
class btCollisionObject;
|
||||
class btCollisionWorld;
|
||||
class btConvexShape;
|
||||
|
||||
namespace Resource
|
||||
@ -59,7 +60,6 @@ namespace MWPhysics
|
||||
* to account for e.g. scripted movements
|
||||
*/
|
||||
void setSimulationPosition(const osg::Vec3f& position);
|
||||
osg::Vec3f getSimulationPosition() const;
|
||||
|
||||
void updateCollisionObjectPosition();
|
||||
|
||||
@ -89,15 +89,11 @@ namespace MWPhysics
|
||||
void updatePosition();
|
||||
|
||||
// register a position offset that will be applied during simulation.
|
||||
void adjustPosition(const osg::Vec3f& offset, bool ignoreCollisions);
|
||||
void adjustPosition(const osg::Vec3f& offset);
|
||||
|
||||
// apply position offset. Can't be called during simulation
|
||||
void applyOffsetChange();
|
||||
|
||||
osg::Vec3f getPosition() const;
|
||||
|
||||
osg::Vec3f getPreviousPosition() const;
|
||||
|
||||
/**
|
||||
* Returns the half extents of the collision body (scaled according to rendering scale)
|
||||
* @note The reason we need this extra method is because of an inconsistency in MW - NPC race scales aren't applied to the collision shape,
|
||||
@ -160,10 +156,7 @@ namespace MWPhysics
|
||||
mLastStuckPosition = position;
|
||||
}
|
||||
|
||||
bool skipCollisions();
|
||||
|
||||
void setVelocity(osg::Vec3f velocity);
|
||||
osg::Vec3f velocity();
|
||||
bool canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const;
|
||||
|
||||
private:
|
||||
MWWorld::Ptr mStandingOnPtr;
|
||||
@ -190,13 +183,8 @@ namespace MWPhysics
|
||||
osg::Quat mRotation;
|
||||
|
||||
osg::Vec3f mScale;
|
||||
osg::Vec3f mSimulationPosition;
|
||||
osg::Vec3f mPosition;
|
||||
osg::Vec3f mPreviousPosition;
|
||||
osg::Vec3f mPositionOffset;
|
||||
osg::Vec3f mVelocity;
|
||||
bool mWorldPositionChanged;
|
||||
bool mSkipCollisions;
|
||||
bool mSkipSimulation;
|
||||
mutable std::mutex mPositionMutex;
|
||||
|
||||
|
@ -15,9 +15,9 @@ namespace MWPhysics
|
||||
const btVector3& position, const btScalar radius)
|
||||
{
|
||||
const btVector3 nearest(
|
||||
std::max(aabbMin.x(), std::min(aabbMax.x(), position.x())),
|
||||
std::max(aabbMin.y(), std::min(aabbMax.y(), position.y())),
|
||||
std::max(aabbMin.z(), std::min(aabbMax.z(), position.z()))
|
||||
std::clamp(position.x(), aabbMin.x(), aabbMax.x()),
|
||||
std::clamp(position.y(), aabbMin.y(), aabbMax.y()),
|
||||
std::clamp(position.z(), aabbMin.z(), aabbMax.z())
|
||||
);
|
||||
return nearest.distance(position) < radius;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
#include <BulletCollision/CollisionShapes/btCollisionShape.h>
|
||||
#include <BulletCollision/CollisionShapes/btConvexShape.h>
|
||||
|
||||
#include <components/esm/loadgmst.hpp>
|
||||
#include <components/misc/convert.hpp>
|
||||
@ -19,6 +20,8 @@
|
||||
#include "constants.hpp"
|
||||
#include "contacttestwrapper.h"
|
||||
#include "physicssystem.hpp"
|
||||
#include "projectile.hpp"
|
||||
#include "projectileconvexcallback.hpp"
|
||||
#include "stepper.hpp"
|
||||
#include "trace.h"
|
||||
|
||||
@ -116,7 +119,7 @@ namespace MWPhysics
|
||||
}
|
||||
|
||||
void MovementSolver::move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld,
|
||||
WorldFrameData& worldData)
|
||||
const WorldFrameData& worldData)
|
||||
{
|
||||
// Reset per-frame data
|
||||
actor.mWalkingOnWater = false;
|
||||
@ -203,7 +206,7 @@ namespace MWPhysics
|
||||
if((newPosition - nextpos).length2() > 0.0001)
|
||||
{
|
||||
// trace to where character would go if there were no obstructions
|
||||
tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld);
|
||||
tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld, actor.mIsOnGround);
|
||||
|
||||
// check for obstructions
|
||||
if(tracer.mFraction >= 1.0f)
|
||||
@ -338,7 +341,7 @@ namespace MWPhysics
|
||||
osg::Vec3f from = newPosition;
|
||||
auto dropDistance = 2*sGroundOffset + (actor.mIsOnGround ? sStepSizeDown : 0);
|
||||
osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance);
|
||||
tracer.doTrace(actor.mCollisionObject, from, to, collisionWorld);
|
||||
tracer.doTrace(actor.mCollisionObject, from, to, collisionWorld, actor.mIsOnGround);
|
||||
if(tracer.mFraction < 1.0f)
|
||||
{
|
||||
if (!isActor(tracer.mHitObject))
|
||||
@ -398,6 +401,29 @@ namespace MWPhysics
|
||||
actor.mPosition.z() -= actor.mHalfExtentsZ; // vanilla-accurate
|
||||
}
|
||||
|
||||
void MovementSolver::move(ProjectileFrameData& projectile, float time, const btCollisionWorld* collisionWorld)
|
||||
{
|
||||
btVector3 btFrom = Misc::Convert::toBullet(projectile.mPosition);
|
||||
btVector3 btTo = Misc::Convert::toBullet(projectile.mPosition + projectile.mMovement * time);
|
||||
|
||||
if (btFrom == btTo)
|
||||
return;
|
||||
|
||||
ProjectileConvexCallback resultCallback(projectile.mCaster, projectile.mCollisionObject, btFrom, btTo, projectile.mProjectile);
|
||||
resultCallback.m_collisionFilterMask = 0xff;
|
||||
resultCallback.m_collisionFilterGroup = CollisionType_Projectile;
|
||||
|
||||
const btQuaternion btrot = btQuaternion::getIdentity();
|
||||
btTransform from_ (btrot, btFrom);
|
||||
btTransform to_ (btrot, btTo);
|
||||
|
||||
const btCollisionShape* shape = projectile.mCollisionObject->getCollisionShape();
|
||||
assert(shape->isConvex());
|
||||
collisionWorld->convexSweepTest(static_cast<const btConvexShape*>(shape), from_, to_, resultCallback);
|
||||
|
||||
projectile.mPosition = Misc::Convert::toOsg(projectile.mProjectile->isActive() ? btTo : resultCallback.m_hitPointWorld);
|
||||
}
|
||||
|
||||
btVector3 addMarginToDelta(btVector3 delta)
|
||||
{
|
||||
if(delta.length2() == 0.0)
|
||||
|
@ -37,13 +37,15 @@ namespace MWPhysics
|
||||
|
||||
class Actor;
|
||||
struct ActorFrameData;
|
||||
struct ProjectileFrameData;
|
||||
struct WorldFrameData;
|
||||
|
||||
class MovementSolver
|
||||
{
|
||||
public:
|
||||
static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight);
|
||||
static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData);
|
||||
static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, const WorldFrameData& worldData);
|
||||
static void move(ProjectileFrameData& projectile, float time, const btCollisionWorld* collisionWorld);
|
||||
static void unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld);
|
||||
};
|
||||
}
|
||||
|
@ -105,10 +105,134 @@ namespace
|
||||
return actorData.mPosition.z() < actorData.mSwimLevel;
|
||||
}
|
||||
|
||||
osg::Vec3f interpolateMovements(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
|
||||
osg::Vec3f interpolateMovements(const MWPhysics::PtrHolder& ptr, float timeAccum, float physicsDt)
|
||||
{
|
||||
const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f);
|
||||
return actorData.mPosition * interpolationFactor + actor.getPreviousPosition() * (1.f - interpolationFactor);
|
||||
return ptr.getPosition() * interpolationFactor + ptr.getPreviousPosition() * (1.f - interpolationFactor);
|
||||
}
|
||||
|
||||
namespace Visitors
|
||||
{
|
||||
struct InitPosition
|
||||
{
|
||||
const btCollisionWorld* mCollisionWorld;
|
||||
void operator()(MWPhysics::ActorSimulation& sim) const
|
||||
{
|
||||
auto& [actor, frameData] = sim;
|
||||
actor->applyOffsetChange();
|
||||
frameData.mPosition = actor->getPosition();
|
||||
if (frameData.mWaterCollision && frameData.mPosition.z() < frameData.mWaterlevel && actor->canMoveToWaterSurface(frameData.mWaterlevel, mCollisionWorld))
|
||||
{
|
||||
const auto offset = osg::Vec3f(0, 0, frameData.mWaterlevel - frameData.mPosition.z());
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(actor->getPtr(), offset);
|
||||
actor->applyOffsetChange();
|
||||
frameData.mPosition = actor->getPosition();
|
||||
}
|
||||
frameData.mOldHeight = frameData.mPosition.z();
|
||||
const auto rotation = actor->getPtr().getRefData().getPosition().asRotationVec3();
|
||||
frameData.mRotation = osg::Vec2f(rotation.x(), rotation.z());
|
||||
frameData.mInertia = actor->getInertialForce();
|
||||
frameData.mStuckFrames = actor->getStuckFrames();
|
||||
frameData.mLastStuckPosition = actor->getLastStuckPosition();
|
||||
}
|
||||
void operator()(MWPhysics::ProjectileSimulation& sim) const
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct PreStep
|
||||
{
|
||||
btCollisionWorld* mCollisionWorld;
|
||||
void operator()(MWPhysics::ActorSimulation& sim) const
|
||||
{
|
||||
MWPhysics::MovementSolver::unstuck(sim.second, mCollisionWorld);
|
||||
}
|
||||
void operator()(MWPhysics::ProjectileSimulation& sim) const
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdatePosition
|
||||
{
|
||||
btCollisionWorld* mCollisionWorld;
|
||||
void operator()(MWPhysics::ActorSimulation& sim) const
|
||||
{
|
||||
auto& [actor, frameData] = sim;
|
||||
if (actor->setPosition(frameData.mPosition))
|
||||
{
|
||||
frameData.mPosition = actor->getPosition(); // account for potential position change made by script
|
||||
actor->updateCollisionObjectPosition();
|
||||
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
|
||||
}
|
||||
}
|
||||
void operator()(MWPhysics::ProjectileSimulation& sim) const
|
||||
{
|
||||
auto& [proj, frameData] = sim;
|
||||
proj->setPosition(frameData.mPosition);
|
||||
proj->updateCollisionObjectPosition();
|
||||
mCollisionWorld->updateSingleAabb(proj->getCollisionObject());
|
||||
}
|
||||
};
|
||||
|
||||
struct Move
|
||||
{
|
||||
const float mPhysicsDt;
|
||||
const btCollisionWorld* mCollisionWorld;
|
||||
const MWPhysics::WorldFrameData& mWorldFrameData;
|
||||
void operator()(MWPhysics::ActorSimulation& sim) const
|
||||
{
|
||||
MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld, mWorldFrameData);
|
||||
}
|
||||
void operator()(MWPhysics::ProjectileSimulation& sim) const
|
||||
{
|
||||
MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld);
|
||||
}
|
||||
};
|
||||
|
||||
struct Sync
|
||||
{
|
||||
const bool mAdvanceSimulation;
|
||||
const float mTimeAccum;
|
||||
const float mPhysicsDt;
|
||||
const MWPhysics::PhysicsTaskScheduler* scheduler;
|
||||
void operator()(MWPhysics::ActorSimulation& sim) const
|
||||
{
|
||||
auto& [actor, frameData] = sim;
|
||||
auto ptr = actor->getPtr();
|
||||
|
||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
const float heightDiff = frameData.mPosition.z() - frameData.mOldHeight;
|
||||
const bool isStillOnGround = (mAdvanceSimulation && frameData.mWasOnGround && frameData.mIsOnGround);
|
||||
|
||||
if (isStillOnGround || frameData.mFlying || isUnderWater(frameData) || frameData.mSlowFall < 1)
|
||||
stats.land(ptr == MWMechanics::getPlayer() && (frameData.mFlying || isUnderWater(frameData)));
|
||||
else if (heightDiff < 0)
|
||||
stats.addToFallHeight(-heightDiff);
|
||||
|
||||
actor->setSimulationPosition(::interpolateMovements(*actor, mTimeAccum, mPhysicsDt));
|
||||
actor->setLastStuckPosition(frameData.mLastStuckPosition);
|
||||
actor->setStuckFrames(frameData.mStuckFrames);
|
||||
if (mAdvanceSimulation)
|
||||
{
|
||||
MWWorld::Ptr standingOn;
|
||||
auto* ptrHolder = static_cast<MWPhysics::PtrHolder*>(scheduler->getUserPointer(frameData.mStandingOn));
|
||||
if (ptrHolder)
|
||||
standingOn = ptrHolder->getPtr();
|
||||
actor->setStandingOnPtr(standingOn);
|
||||
// the "on ground" state of an actor might have been updated by a traceDown, don't overwrite the change
|
||||
if (actor->getOnGround() == frameData.mWasOnGround)
|
||||
actor->setOnGround(frameData.mIsOnGround);
|
||||
actor->setOnSlope(frameData.mIsOnSlope);
|
||||
actor->setWalkingOnWater(frameData.mWalkingOnWater);
|
||||
actor->setInertialForce(frameData.mInertia);
|
||||
}
|
||||
}
|
||||
void operator()(MWPhysics::ProjectileSimulation& sim) const
|
||||
{
|
||||
auto& [proj, frameData] = sim;
|
||||
proj->setSimulationPosition(::interpolateMovements(*proj, mTimeAccum, mPhysicsDt));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace Config
|
||||
@ -235,13 +359,12 @@ namespace MWPhysics
|
||||
return std::make_tuple(numSteps, actualDelta);
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector<std::shared_ptr<Actor>>&& actors, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
|
||||
void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector<Simulation>&& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
|
||||
{
|
||||
// This function run in the main thread.
|
||||
// While the mSimulationMutex is held, background physics threads can't run.
|
||||
|
||||
MaybeExclusiveLock lock(mSimulationMutex, mNumThreads);
|
||||
assert(actors.size() == actorsData.size());
|
||||
|
||||
double timeStart = mTimer->tick();
|
||||
|
||||
@ -259,19 +382,19 @@ namespace MWPhysics
|
||||
timeAccum -= numSteps*newDelta;
|
||||
|
||||
// init
|
||||
for (size_t i = 0; i < actors.size(); ++i)
|
||||
const Visitors::InitPosition vis{mCollisionWorld};
|
||||
for (auto& sim : simulations)
|
||||
{
|
||||
actorsData[i].updatePosition(*actors[i], mCollisionWorld);
|
||||
std::visit(vis, sim);
|
||||
}
|
||||
mPrevStepCount = numSteps;
|
||||
mRemainingSteps = numSteps;
|
||||
mTimeAccum = timeAccum;
|
||||
mPhysicsDt = newDelta;
|
||||
mActors = std::move(actors);
|
||||
mActorsFrameData = std::move(actorsData);
|
||||
mSimulations = std::move(simulations);
|
||||
mAdvanceSimulation = (mRemainingSteps != 0);
|
||||
mNewFrame = true;
|
||||
mNumJobs = mActorsFrameData.size();
|
||||
mNumJobs = mSimulations.size();
|
||||
mNextLOS.store(0, std::memory_order_relaxed);
|
||||
mNextJob.store(0, std::memory_order_release);
|
||||
|
||||
@ -301,8 +424,7 @@ namespace MWPhysics
|
||||
MaybeExclusiveLock lock(mSimulationMutex, mNumThreads);
|
||||
mBudget.reset(mDefaultPhysicsDt);
|
||||
mAsyncBudget.reset(0.0f);
|
||||
mActors.clear();
|
||||
mActorsFrameData.clear();
|
||||
mSimulations.clear();
|
||||
for (const auto& [_, actor] : actors)
|
||||
{
|
||||
actor->updatePosition();
|
||||
@ -448,7 +570,7 @@ namespace MWPhysics
|
||||
}
|
||||
else if (const auto projectile = std::dynamic_pointer_cast<Projectile>(ptr))
|
||||
{
|
||||
projectile->commitPositionChange();
|
||||
projectile->updateCollisionObjectPosition();
|
||||
mCollisionWorld->updateSingleAabb(projectile->getCollisionObject());
|
||||
}
|
||||
}
|
||||
@ -467,47 +589,11 @@ namespace MWPhysics
|
||||
|
||||
void PhysicsTaskScheduler::updateActorsPositions()
|
||||
{
|
||||
for (size_t i = 0; i < mActors.size(); ++i)
|
||||
const Visitors::UpdatePosition vis{mCollisionWorld};
|
||||
for (auto& sim : mSimulations)
|
||||
{
|
||||
if (mActors[i]->setPosition(mActorsFrameData[i].mPosition))
|
||||
{
|
||||
MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads);
|
||||
mActorsFrameData[i].mPosition = mActors[i]->getPosition(); // account for potential position change made by script
|
||||
mActors[i]->updateCollisionObjectPosition();
|
||||
mCollisionWorld->updateSingleAabb(mActors[i]->getCollisionObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const
|
||||
{
|
||||
auto ptr = actor.getPtr();
|
||||
|
||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight;
|
||||
const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mIsOnGround);
|
||||
|
||||
if (isStillOnGround || actorData.mFlying || isUnderWater(actorData) || actorData.mSlowFall < 1)
|
||||
stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || isUnderWater(actorData)));
|
||||
else if (heightDiff < 0)
|
||||
stats.addToFallHeight(-heightDiff);
|
||||
|
||||
actor.setSimulationPosition(interpolateMovements(actor, actorData, timeAccum, dt));
|
||||
actor.setLastStuckPosition(actorData.mLastStuckPosition);
|
||||
actor.setStuckFrames(actorData.mStuckFrames);
|
||||
if (simulationPerformed)
|
||||
{
|
||||
MWWorld::Ptr standingOn;
|
||||
auto* ptrHolder = static_cast<MWPhysics::PtrHolder*>(getUserPointer(actorData.mStandingOn));
|
||||
if (ptrHolder)
|
||||
standingOn = ptrHolder->getPtr();
|
||||
actor.setStandingOnPtr(standingOn);
|
||||
// the "on ground" state of an actor might have been updated by a traceDown, don't overwrite the change
|
||||
if (actor.getOnGround() == actorData.mWasOnGround)
|
||||
actor.setOnGround(actorData.mIsOnGround);
|
||||
actor.setOnSlope(actorData.mIsOnSlope);
|
||||
actor.setWalkingOnWater(actorData.mWalkingOnWater);
|
||||
actor.setInertialForce(actorData.mInertia);
|
||||
MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads);
|
||||
std::visit(vis, sim);
|
||||
}
|
||||
}
|
||||
|
||||
@ -532,10 +618,11 @@ namespace MWPhysics
|
||||
{
|
||||
mPreStepBarrier->wait([this] { afterPreStep(); });
|
||||
int job = 0;
|
||||
const Visitors::Move vis{mPhysicsDt, mCollisionWorld, *mWorldFrameData};
|
||||
while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
|
||||
{
|
||||
MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads);
|
||||
MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData);
|
||||
std::visit(vis, mSimulations[job]);
|
||||
}
|
||||
|
||||
mPostStepBarrier->wait([this] { afterPostStep(); });
|
||||
@ -577,7 +664,7 @@ namespace MWPhysics
|
||||
void PhysicsTaskScheduler::releaseSharedStates()
|
||||
{
|
||||
std::scoped_lock lock(mSimulationMutex, mUpdateAabbMutex);
|
||||
mActors.clear();
|
||||
mSimulations.clear();
|
||||
mUpdateAabb.clear();
|
||||
}
|
||||
|
||||
@ -586,10 +673,11 @@ namespace MWPhysics
|
||||
updateAabbs();
|
||||
if (!mRemainingSteps)
|
||||
return;
|
||||
for (size_t i = 0; i < mActors.size(); ++i)
|
||||
const Visitors::PreStep vis{mCollisionWorld};
|
||||
for (auto& sim : mSimulations)
|
||||
{
|
||||
MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads);
|
||||
MovementSolver::unstuck(mActorsFrameData[i], mCollisionWorld);
|
||||
std::visit(vis, sim);
|
||||
}
|
||||
}
|
||||
|
||||
@ -618,7 +706,8 @@ namespace MWPhysics
|
||||
|
||||
void PhysicsTaskScheduler::syncWithMainThread()
|
||||
{
|
||||
for (size_t i = 0; i < mActors.size(); ++i)
|
||||
updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt);
|
||||
const Visitors::Sync vis{mAdvanceSimulation, mTimeAccum, mPhysicsDt, this};
|
||||
for (auto& sim : mSimulations)
|
||||
std::visit(vis, sim);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <shared_mutex>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <variant>
|
||||
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
|
||||
@ -39,7 +40,7 @@ namespace MWPhysics
|
||||
/// @param timeAccum accumulated time from previous run to interpolate movements
|
||||
/// @param actorsData per actor data needed to compute new positions
|
||||
/// @return new position of each actor
|
||||
void applyQueuedMovements(float & timeAccum, std::vector<std::shared_ptr<Actor>>&& actors, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
|
||||
void applyQueuedMovements(float & timeAccum, std::vector<Simulation>&& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
|
||||
|
||||
void resetSimulation(const ActorMap& actors);
|
||||
|
||||
@ -57,14 +58,12 @@ namespace MWPhysics
|
||||
bool getLineOfSight(const std::shared_ptr<Actor>& actor1, const std::shared_ptr<Actor>& actor2);
|
||||
void debugDraw();
|
||||
void* getUserPointer(const btCollisionObject* object) const;
|
||||
|
||||
void releaseSharedStates(); // destroy all objects whose destructor can't be safely called from ~PhysicsTaskScheduler()
|
||||
|
||||
private:
|
||||
void doSimulation();
|
||||
void worker();
|
||||
void updateActorsPositions();
|
||||
void updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const;
|
||||
bool hasLineOfSight(const Actor* actor1, const Actor* actor2);
|
||||
void refreshLOSCache();
|
||||
void updateAabbs();
|
||||
@ -77,8 +76,7 @@ namespace MWPhysics
|
||||
void syncWithMainThread();
|
||||
|
||||
std::unique_ptr<WorldFrameData> mWorldFrameData;
|
||||
std::vector<std::shared_ptr<Actor>> mActors;
|
||||
std::vector<ActorFrameData> mActorsFrameData;
|
||||
std::vector<Simulation> mSimulations;
|
||||
std::unordered_set<const btCollisionObject*> mCollisionObjects;
|
||||
float mDefaultPhysicsDt;
|
||||
float mPhysicsDt;
|
||||
|
@ -24,7 +24,7 @@ namespace MWPhysics
|
||||
, mTaskScheduler(scheduler)
|
||||
{
|
||||
mPtr = ptr;
|
||||
mCollisionObject = BulletHelpers::makeCollisionObject(mShapeInstance->getCollisionShape(),
|
||||
mCollisionObject = BulletHelpers::makeCollisionObject(mShapeInstance->mCollisionShape.get(),
|
||||
Misc::Convert::toBullet(mPosition), Misc::Convert::toBullet(rotation));
|
||||
mCollisionObject->setUserPointer(this);
|
||||
mShapeInstance->setLocalScaling(mScale);
|
||||
@ -109,9 +109,9 @@ namespace MWPhysics
|
||||
if (mShapeInstance->mAnimatedShapes.empty())
|
||||
return false;
|
||||
|
||||
assert (mShapeInstance->getCollisionShape()->isCompound());
|
||||
assert (mShapeInstance->mCollisionShape->isCompound());
|
||||
|
||||
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->getCollisionShape());
|
||||
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->mCollisionShape.get());
|
||||
for (const auto& [recIndex, shapeIndex] : mShapeInstance->mAnimatedShapes)
|
||||
{
|
||||
auto nodePathFound = mRecIndexToNodePath.find(recIndex);
|
||||
|
@ -60,19 +60,6 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
bool canMoveToWaterSurface(const MWPhysics::Actor* physicActor, const float waterlevel, btCollisionWorld* world)
|
||||
{
|
||||
if (!physicActor)
|
||||
return false;
|
||||
const float halfZ = physicActor->getHalfExtents().z();
|
||||
const osg::Vec3f actorPosition = physicActor->getPosition();
|
||||
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
|
||||
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
|
||||
MWPhysics::ActorTracer tracer;
|
||||
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world);
|
||||
return (tracer.mFraction >= 1.0f);
|
||||
}
|
||||
|
||||
void handleJump(const MWWorld::Ptr &ptr)
|
||||
{
|
||||
if (!ptr.getClass().isActor())
|
||||
@ -370,6 +357,8 @@ namespace MWPhysics
|
||||
|
||||
bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const
|
||||
{
|
||||
if (actor1 == actor2) return true;
|
||||
|
||||
const auto it1 = mActors.find(actor1.mRef);
|
||||
const auto it2 = mActors.find(actor2.mRef);
|
||||
if (it1 == mActors.end() || it2 == mActors.end())
|
||||
@ -386,7 +375,8 @@ namespace MWPhysics
|
||||
|
||||
bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel)
|
||||
{
|
||||
return ::canMoveToWaterSurface(getActor(actor), waterlevel, mCollisionWorld.get());
|
||||
const auto* physactor = getActor(actor);
|
||||
return physactor && physactor->canMoveToWaterSurface(waterlevel, mCollisionWorld.get());
|
||||
}
|
||||
|
||||
osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const
|
||||
@ -491,7 +481,7 @@ namespace MWPhysics
|
||||
if (ptr.mRef->mData.mPhysicsPostponed)
|
||||
return;
|
||||
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
|
||||
if (!shapeInstance || !shapeInstance->getCollisionShape())
|
||||
if (!shapeInstance || !shapeInstance->mCollisionShape)
|
||||
return;
|
||||
|
||||
assert(!getObject(ptr));
|
||||
@ -526,10 +516,10 @@ namespace MWPhysics
|
||||
|
||||
void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
|
||||
{
|
||||
if (auto found = mObjects.find(old.mRef); found != mObjects.end())
|
||||
found->second->updatePtr(updated);
|
||||
else if (auto found = mActors.find(old.mRef); found != mActors.end())
|
||||
found->second->updatePtr(updated);
|
||||
if (auto foundObject = mObjects.find(old.mRef); foundObject != mObjects.end())
|
||||
foundObject->second->updatePtr(updated);
|
||||
else if (auto foundActor = mActors.find(old.mRef); foundActor != mActors.end())
|
||||
foundActor->second->updatePtr(updated);
|
||||
|
||||
for (auto& [_, actor] : mActors)
|
||||
{
|
||||
@ -592,33 +582,6 @@ namespace MWPhysics
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position) const
|
||||
{
|
||||
const auto foundProjectile = mProjectiles.find(projectileId);
|
||||
assert(foundProjectile != mProjectiles.end());
|
||||
auto* projectile = foundProjectile->second.get();
|
||||
|
||||
btVector3 btFrom = Misc::Convert::toBullet(projectile->getPosition());
|
||||
btVector3 btTo = Misc::Convert::toBullet(position);
|
||||
|
||||
if (btFrom == btTo)
|
||||
return;
|
||||
|
||||
ProjectileConvexCallback resultCallback(projectile->getCasterCollisionObject(), projectile->getCollisionObject(), btFrom, btTo, projectile);
|
||||
resultCallback.m_collisionFilterMask = 0xff;
|
||||
resultCallback.m_collisionFilterGroup = CollisionType_Projectile;
|
||||
|
||||
const btQuaternion btrot = btQuaternion::getIdentity();
|
||||
btTransform from_ (btrot, btFrom);
|
||||
btTransform to_ (btrot, btTo);
|
||||
|
||||
mTaskScheduler->convexSweepTest(projectile->getConvexShape(), from_, to_, resultCallback);
|
||||
|
||||
const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(projectile->getHitPosition());
|
||||
projectile->setPosition(newpos);
|
||||
mTaskScheduler->updateSingleAabb(foundProjectile->second);
|
||||
}
|
||||
|
||||
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate)
|
||||
{
|
||||
if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end())
|
||||
@ -655,7 +618,7 @@ namespace MWPhysics
|
||||
osg::ref_ptr<const Resource::BulletShape> shape = mShapeManager->getShape(mesh);
|
||||
|
||||
// Try to get shape from basic model as fallback for creatures
|
||||
if (!ptr.getClass().isNpc() && shape && shape->mCollisionBox.extents.length2() == 0)
|
||||
if (!ptr.getClass().isNpc() && shape && shape->mCollisionBox.mExtents.length2() == 0)
|
||||
{
|
||||
const std::string fallbackModel = ptr.getClass().getModel(ptr);
|
||||
if (fallbackModel != mesh)
|
||||
@ -680,7 +643,7 @@ namespace MWPhysics
|
||||
{
|
||||
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
|
||||
assert(shapeInstance);
|
||||
float radius = computeRadius ? shapeInstance->mCollisionBox.extents.length() / 2.f : 1.f;
|
||||
float radius = computeRadius ? shapeInstance->mCollisionBox.mExtents.length() / 2.f : 1.f;
|
||||
|
||||
mProjectileId++;
|
||||
|
||||
@ -727,11 +690,10 @@ namespace MWPhysics
|
||||
actor->setVelocity(osg::Vec3f());
|
||||
}
|
||||
|
||||
std::pair<std::vector<std::shared_ptr<Actor>>, std::vector<ActorFrameData>> PhysicsSystem::prepareFrameData(bool willSimulate)
|
||||
std::vector<Simulation> PhysicsSystem::prepareSimulation(bool willSimulate)
|
||||
{
|
||||
std::pair<std::vector<std::shared_ptr<Actor>>, std::vector<ActorFrameData>> framedata;
|
||||
framedata.first.reserve(mActors.size());
|
||||
framedata.second.reserve(mActors.size());
|
||||
std::vector<Simulation> simulations;
|
||||
simulations.reserve(mActors.size() + mProjectiles.size());
|
||||
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
for (const auto& [ref, physicActor] : mActors)
|
||||
{
|
||||
@ -756,18 +718,23 @@ namespace MWPhysics
|
||||
physicActor->setCanWaterWalk(waterCollision);
|
||||
|
||||
// Slow fall reduces fall speed by a factor of (effect magnitude / 200)
|
||||
const float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f));
|
||||
const float slowFall = 1.f - std::clamp(effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f, 0.f, 1.f);
|
||||
const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState();
|
||||
const bool inert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0);
|
||||
|
||||
framedata.first.emplace_back(physicActor);
|
||||
framedata.second.emplace_back(*physicActor, inert, waterCollision, slowFall, waterlevel);
|
||||
simulations.emplace_back(ActorSimulation{physicActor, ActorFrameData{*physicActor, inert, waterCollision, slowFall, waterlevel}});
|
||||
|
||||
// if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly.
|
||||
if (willSimulate)
|
||||
handleJump(ptr);
|
||||
}
|
||||
return framedata;
|
||||
|
||||
for (const auto& [id, projectile] : mProjectiles)
|
||||
{
|
||||
simulations.emplace_back(ProjectileSimulation{projectile, ProjectileFrameData{*projectile}});
|
||||
}
|
||||
|
||||
return simulations;
|
||||
}
|
||||
|
||||
void PhysicsSystem::stepSimulation(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
|
||||
@ -793,9 +760,9 @@ namespace MWPhysics
|
||||
mTaskScheduler->resetSimulation(mActors);
|
||||
else
|
||||
{
|
||||
auto [actors, framedata] = prepareFrameData(mTimeAccum >= mPhysicsDt);
|
||||
auto simulations = prepareSimulation(mTimeAccum >= mPhysicsDt);
|
||||
// modifies mTimeAccum
|
||||
mTaskScheduler->applyQueuedMovements(mTimeAccum, std::move(actors), std::move(framedata), frameStart, frameNumber, stats);
|
||||
mTaskScheduler->applyQueuedMovements(mTimeAccum, std::move(simulations), frameStart, frameNumber, stats);
|
||||
}
|
||||
}
|
||||
|
||||
@ -974,25 +941,17 @@ namespace MWPhysics
|
||||
, mWasOnGround(actor.getOnGround())
|
||||
, mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr()))
|
||||
, mWaterCollision(waterCollision)
|
||||
, mSkipCollisionDetection(actor.skipCollisions() || !actor.getCollisionMode())
|
||||
, mSkipCollisionDetection(!actor.getCollisionMode())
|
||||
{
|
||||
}
|
||||
|
||||
void ActorFrameData::updatePosition(Actor& actor, btCollisionWorld* world)
|
||||
ProjectileFrameData::ProjectileFrameData(Projectile& projectile)
|
||||
: mPosition(projectile.getPosition())
|
||||
, mMovement(projectile.velocity())
|
||||
, mCaster(projectile.getCasterCollisionObject())
|
||||
, mCollisionObject(projectile.getCollisionObject())
|
||||
, mProjectile(&projectile)
|
||||
{
|
||||
actor.applyOffsetChange();
|
||||
mPosition = actor.getPosition();
|
||||
if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(&actor, mWaterlevel, world))
|
||||
{
|
||||
mPosition.z() = mWaterlevel;
|
||||
MWBase::Environment::get().getWorld()->moveObject(actor.getPtr(), mPosition, false);
|
||||
}
|
||||
mOldHeight = mPosition.z();
|
||||
const auto rotation = actor.getPtr().getRefData().getPosition().asRotationVec3();
|
||||
mRotation = osg::Vec2f(rotation.x(), rotation.z());
|
||||
mInertia = actor.getInertialForce();
|
||||
mStuckFrames = actor.getStuckFrames();
|
||||
mLastStuckPosition = actor.getLastStuckPosition();
|
||||
}
|
||||
|
||||
WorldFrameData::WorldFrameData()
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <variant>
|
||||
|
||||
#include <osg/Quat>
|
||||
#include <osg/BoundingBox>
|
||||
@ -75,7 +76,6 @@ namespace MWPhysics
|
||||
struct ActorFrameData
|
||||
{
|
||||
ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel);
|
||||
void updatePosition(Actor& actor, btCollisionWorld* world);
|
||||
osg::Vec3f mPosition;
|
||||
osg::Vec3f mInertia;
|
||||
const btCollisionObject* mStandingOn;
|
||||
@ -100,6 +100,16 @@ namespace MWPhysics
|
||||
const bool mSkipCollisionDetection;
|
||||
};
|
||||
|
||||
struct ProjectileFrameData
|
||||
{
|
||||
explicit ProjectileFrameData(Projectile& projectile);
|
||||
osg::Vec3f mPosition;
|
||||
osg::Vec3f mMovement;
|
||||
const btCollisionObject* mCaster;
|
||||
const btCollisionObject* mCollisionObject;
|
||||
Projectile* mProjectile;
|
||||
};
|
||||
|
||||
struct WorldFrameData
|
||||
{
|
||||
WorldFrameData();
|
||||
@ -107,6 +117,10 @@ namespace MWPhysics
|
||||
osg::Vec3f mStormDirection;
|
||||
};
|
||||
|
||||
using ActorSimulation = std::pair<std::shared_ptr<Actor>, ActorFrameData>;
|
||||
using ProjectileSimulation = std::pair<std::shared_ptr<Projectile>, ProjectileFrameData>;
|
||||
using Simulation = std::variant<ActorSimulation, ProjectileSimulation>;
|
||||
|
||||
class PhysicsSystem : public RayCastingInterface
|
||||
{
|
||||
public:
|
||||
@ -124,7 +138,6 @@ namespace MWPhysics
|
||||
|
||||
int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius);
|
||||
void setCaster(int projectileId, const MWWorld::Ptr& caster);
|
||||
void updateProjectile(const int projectileId, const osg::Vec3f &position) const;
|
||||
void removeProjectile(const int projectileId);
|
||||
|
||||
void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
|
||||
@ -253,7 +266,7 @@ namespace MWPhysics
|
||||
|
||||
void updateWater();
|
||||
|
||||
std::pair<std::vector<std::shared_ptr<Actor>>, std::vector<ActorFrameData>> prepareFrameData(bool willSimulate);
|
||||
std::vector<Simulation> prepareSimulation(bool willSimulate);
|
||||
|
||||
std::unique_ptr<btBroadphaseInterface> mBroadphase;
|
||||
std::unique_ptr<btDefaultCollisionConfiguration> mCollisionConfiguration;
|
||||
|
@ -31,14 +31,15 @@ Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, f
|
||||
mCollisionObject->setCollisionShape(mShape.get());
|
||||
mCollisionObject->setUserPointer(this);
|
||||
|
||||
setPosition(position);
|
||||
mPosition = position;
|
||||
mPreviousPosition = position;
|
||||
setCaster(caster);
|
||||
|
||||
const int collisionMask = CollisionType_World | CollisionType_HeightMap |
|
||||
CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile;
|
||||
mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask);
|
||||
|
||||
commitPositionChange();
|
||||
updateCollisionObjectPosition();
|
||||
}
|
||||
|
||||
Projectile::~Projectile()
|
||||
@ -48,29 +49,12 @@ Projectile::~Projectile()
|
||||
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||
}
|
||||
|
||||
void Projectile::commitPositionChange()
|
||||
void Projectile::updateCollisionObjectPosition()
|
||||
{
|
||||
std::scoped_lock lock(mMutex);
|
||||
if (mTransformUpdatePending)
|
||||
{
|
||||
auto& trans = mCollisionObject->getWorldTransform();
|
||||
trans.setOrigin(Misc::Convert::toBullet(mPosition));
|
||||
mCollisionObject->setWorldTransform(trans);
|
||||
mTransformUpdatePending = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Projectile::setPosition(const osg::Vec3f &position)
|
||||
{
|
||||
std::scoped_lock lock(mMutex);
|
||||
mPosition = position;
|
||||
mTransformUpdatePending = true;
|
||||
}
|
||||
|
||||
osg::Vec3f Projectile::getPosition() const
|
||||
{
|
||||
std::scoped_lock lock(mMutex);
|
||||
return mPosition;
|
||||
auto& trans = mCollisionObject->getWorldTransform();
|
||||
trans.setOrigin(Misc::Convert::toBullet(mPosition));
|
||||
mCollisionObject->setWorldTransform(trans);
|
||||
}
|
||||
|
||||
void Projectile::hit(const btCollisionObject* target, btVector3 pos, btVector3 normal)
|
||||
|
@ -36,10 +36,7 @@ namespace MWPhysics
|
||||
|
||||
btConvexShape* getConvexShape() const { return mConvexShape; }
|
||||
|
||||
void commitPositionChange();
|
||||
|
||||
void setPosition(const osg::Vec3f& position);
|
||||
osg::Vec3f getPosition() const;
|
||||
void updateCollisionObjectPosition();
|
||||
|
||||
bool isActive() const
|
||||
{
|
||||
@ -80,13 +77,11 @@ namespace MWPhysics
|
||||
std::unique_ptr<btCollisionShape> mShape;
|
||||
btConvexShape* mConvexShape;
|
||||
|
||||
bool mTransformUpdatePending;
|
||||
bool mHitWater;
|
||||
std::atomic<bool> mActive;
|
||||
MWWorld::Ptr mCaster;
|
||||
const btCollisionObject* mCasterColObj;
|
||||
const btCollisionObject* mHitTarget;
|
||||
osg::Vec3f mPosition;
|
||||
btVector3 mHitPosition;
|
||||
btVector3 mHitNormal;
|
||||
|
||||
|
@ -30,9 +30,49 @@ namespace MWPhysics
|
||||
return mCollisionObject.get();
|
||||
}
|
||||
|
||||
void setVelocity(osg::Vec3f velocity)
|
||||
{
|
||||
mVelocity = velocity;
|
||||
}
|
||||
|
||||
osg::Vec3f velocity()
|
||||
{
|
||||
return std::exchange(mVelocity, osg::Vec3f());
|
||||
}
|
||||
|
||||
void setSimulationPosition(const osg::Vec3f& position)
|
||||
{
|
||||
mSimulationPosition = position;
|
||||
}
|
||||
|
||||
osg::Vec3f getSimulationPosition() const
|
||||
{
|
||||
return mSimulationPosition;
|
||||
}
|
||||
|
||||
void setPosition(const osg::Vec3f& position)
|
||||
{
|
||||
mPreviousPosition = mPosition;
|
||||
mPosition = position;
|
||||
}
|
||||
|
||||
osg::Vec3f getPosition() const
|
||||
{
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
osg::Vec3f getPreviousPosition() const
|
||||
{
|
||||
return mPreviousPosition;
|
||||
}
|
||||
|
||||
protected:
|
||||
MWWorld::Ptr mPtr;
|
||||
std::unique_ptr<btCollisionObject> mCollisionObject;
|
||||
osg::Vec3f mVelocity;
|
||||
osg::Vec3f mSimulationPosition;
|
||||
osg::Vec3f mPosition;
|
||||
osg::Vec3f mPreviousPosition;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ namespace MWPhysics
|
||||
// Stairstepping algorithms work by moving up to avoid the step, moving forwards, then moving back down onto the ground.
|
||||
// This algorithm has a couple of minor problems, but they don't cause problems for sane geometry, and just prevent stepping on insane geometry.
|
||||
|
||||
mUpStepper.doTrace(mColObj, position, position + osg::Vec3f(0.0f, 0.0f, Constants::sStepSizeUp), mColWorld);
|
||||
mUpStepper.doTrace(mColObj, position, position + osg::Vec3f(0.0f, 0.0f, Constants::sStepSizeUp), mColWorld, onGround);
|
||||
|
||||
float upDistance = 0;
|
||||
if(!mUpStepper.mHitObject)
|
||||
@ -117,7 +117,7 @@ namespace MWPhysics
|
||||
downStepSize = upDistance;
|
||||
else
|
||||
downStepSize = moveDistance + upDistance + sStepSizeDown;
|
||||
mDownStepper.doTrace(mColObj, tracerDest, tracerDest + osg::Vec3f(0.0f, 0.0f, -downStepSize), mColWorld);
|
||||
mDownStepper.doTrace(mColObj, tracerDest, tracerDest + osg::Vec3f(0.0f, 0.0f, -downStepSize), mColWorld, onGround);
|
||||
|
||||
// can't step down onto air, non-walkable-slopes, or actors
|
||||
// NOTE: using a capsule causes isWalkableSlope (used in canStepDown) to fail on certain geometry that were intended to be valid at the bottoms of stairs
|
||||
|
@ -12,38 +12,84 @@
|
||||
namespace MWPhysics
|
||||
{
|
||||
|
||||
void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world)
|
||||
ActorConvexCallback sweepHelper(const btCollisionObject *actor, const btVector3& from, const btVector3& to, const btCollisionWorld* world, bool actorFilter)
|
||||
{
|
||||
const btVector3 btstart = Misc::Convert::toBullet(start);
|
||||
const btVector3 btend = Misc::Convert::toBullet(end);
|
||||
|
||||
const btTransform &trans = actor->getWorldTransform();
|
||||
btTransform from(trans);
|
||||
btTransform to(trans);
|
||||
from.setOrigin(btstart);
|
||||
to.setOrigin(btend);
|
||||
|
||||
const btVector3 motion = btstart-btend;
|
||||
ActorConvexCallback newTraceCallback(actor, motion, btScalar(0.0), world);
|
||||
// Inherit the actor's collision group and mask
|
||||
newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
btTransform transFrom(trans);
|
||||
btTransform transTo(trans);
|
||||
transFrom.setOrigin(from);
|
||||
transTo.setOrigin(to);
|
||||
|
||||
const btCollisionShape *shape = actor->getCollisionShape();
|
||||
assert(shape->isConvex());
|
||||
world->convexSweepTest(static_cast<const btConvexShape*>(shape), from, to, newTraceCallback);
|
||||
|
||||
const btVector3 motion = from - to; // FIXME: this is backwards; means ActorConvexCallback is doing dot product tests backwards too
|
||||
ActorConvexCallback traceCallback(actor, motion, btScalar(0.0), world);
|
||||
// Inherit the actor's collision group and mask
|
||||
traceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
traceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
if(actorFilter)
|
||||
traceCallback.m_collisionFilterMask &= ~CollisionType_Actor;
|
||||
|
||||
world->convexSweepTest(static_cast<const btConvexShape*>(shape), transFrom, transTo, traceCallback);
|
||||
return traceCallback;
|
||||
}
|
||||
|
||||
void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world, bool attempt_short_trace)
|
||||
{
|
||||
const btVector3 btstart = Misc::Convert::toBullet(start);
|
||||
btVector3 btend = Misc::Convert::toBullet(end);
|
||||
|
||||
// Because Bullet's collision trace tests touch *all* geometry in its path, a lot of long collision tests
|
||||
// will unnecessarily test against complex meshes that are dozens of units away. This wouldn't normally be
|
||||
// a problem, but bullet isn't the fastest in the world when it comes to doing tests against triangle meshes.
|
||||
// Therefore, we try out a short trace first, then only fall back to the full length trace if needed.
|
||||
// This trace needs to be at least a couple units long, but there's no one particular ideal length.
|
||||
// The length of 2.1 chosen here is a "works well in practice after testing a few random lengths" value.
|
||||
// (Also, we only do this short test if the intended collision trace is long enough for it to make sense.)
|
||||
const float fallback_length = 2.1f;
|
||||
bool doing_short_trace = false;
|
||||
// For some reason, typical scenes perform a little better if we increase the threshold length for the length test.
|
||||
// (Multiplying by 2 in 'square distance' units gives us about 1.4x the threshold length. In benchmarks this was
|
||||
// slightly better for the performance of normal scenes than 4.0, and just plain better than 1.0.)
|
||||
if(attempt_short_trace && (btend-btstart).length2() > fallback_length*fallback_length*2.0)
|
||||
{
|
||||
btend = btstart + (btend-btstart).normalized()*fallback_length;
|
||||
doing_short_trace = true;
|
||||
}
|
||||
|
||||
const auto traceCallback = sweepHelper(actor, btstart, btend, world, false);
|
||||
|
||||
// Copy the hit data over to our trace results struct:
|
||||
if(newTraceCallback.hasHit())
|
||||
if(traceCallback.hasHit())
|
||||
{
|
||||
mFraction = newTraceCallback.m_closestHitFraction;
|
||||
mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld);
|
||||
mFraction = traceCallback.m_closestHitFraction;
|
||||
// ensure fraction is correct (covers intended distance traveled instead of actual distance traveled)
|
||||
if(doing_short_trace && (end-start).length2() > 0.0)
|
||||
mFraction *= (btend-btstart).length() / (end-start).length();
|
||||
mPlaneNormal = Misc::Convert::toOsg(traceCallback.m_hitNormalWorld);
|
||||
mEndPos = (end-start)*mFraction + start;
|
||||
mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld);
|
||||
mHitObject = newTraceCallback.m_hitCollisionObject;
|
||||
mHitPoint = Misc::Convert::toOsg(traceCallback.m_hitPointWorld);
|
||||
mHitObject = traceCallback.m_hitCollisionObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(doing_short_trace)
|
||||
{
|
||||
btend = Misc::Convert::toBullet(end);
|
||||
const auto newTraceCallback = sweepHelper(actor, btstart, btend, world, false);
|
||||
|
||||
if(newTraceCallback.hasHit())
|
||||
{
|
||||
mFraction = newTraceCallback.m_closestHitFraction;
|
||||
mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld);
|
||||
mEndPos = (end-start)*mFraction + start;
|
||||
mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld);
|
||||
mHitObject = newTraceCallback.m_hitCollisionObject;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// fallthrough
|
||||
mEndPos = end;
|
||||
mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f);
|
||||
mFraction = 1.0f;
|
||||
@ -54,25 +100,11 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star
|
||||
|
||||
void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world)
|
||||
{
|
||||
const btVector3 btstart = Misc::Convert::toBullet(start);
|
||||
const btVector3 btend = Misc::Convert::toBullet(end);
|
||||
|
||||
const btTransform &trans = actor->getCollisionObject()->getWorldTransform();
|
||||
btTransform from(trans.getBasis(), btstart);
|
||||
btTransform to(trans.getBasis(), btend);
|
||||
|
||||
const btVector3 motion = btstart-btend;
|
||||
ActorConvexCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world);
|
||||
// Inherit the actor's collision group and mask
|
||||
newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
newTraceCallback.m_collisionFilterMask &= ~CollisionType_Actor;
|
||||
|
||||
world->convexSweepTest(actor->getConvexShape(), from, to, newTraceCallback);
|
||||
if(newTraceCallback.hasHit())
|
||||
const auto traceCallback = sweepHelper(actor->getCollisionObject(), Misc::Convert::toBullet(start), Misc::Convert::toBullet(end), world, true);
|
||||
if(traceCallback.hasHit())
|
||||
{
|
||||
mFraction = newTraceCallback.m_closestHitFraction;
|
||||
mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld);
|
||||
mFraction = traceCallback.m_closestHitFraction;
|
||||
mPlaneNormal = Misc::Convert::toOsg(traceCallback.m_hitNormalWorld);
|
||||
mEndPos = (end-start)*mFraction + start;
|
||||
}
|
||||
else
|
||||
|
@ -20,7 +20,7 @@ namespace MWPhysics
|
||||
|
||||
float mFraction;
|
||||
|
||||
void doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world);
|
||||
void doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world, bool attempt_short_trace = false);
|
||||
void findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world);
|
||||
};
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
|
||||
#include <components/sceneutil/attach.hpp>
|
||||
#include <components/sceneutil/lightmanager.hpp>
|
||||
#include <components/sceneutil/lightutil.hpp>
|
||||
#include <components/sceneutil/visitor.hpp>
|
||||
@ -74,7 +75,7 @@ PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::st
|
||||
osg::ref_ptr<osg::Node> instance = mResourceSystem->getSceneManager()->getInstance(model, parent);
|
||||
|
||||
const NodeMap& nodeMap = getNodeMap();
|
||||
NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename));
|
||||
NodeMap::const_iterator found = nodeMap.find(bonename);
|
||||
if (found == nodeMap.end())
|
||||
return PartHolderPtr();
|
||||
|
||||
@ -84,30 +85,58 @@ PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::st
|
||||
return PartHolderPtr(new PartHolder(instance));
|
||||
}
|
||||
|
||||
std::string ActorAnimation::getShieldMesh(const MWWorld::ConstPtr& shield) const
|
||||
osg::ref_ptr<osg::Node> ActorAnimation::attach(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool isLight)
|
||||
{
|
||||
osg::ref_ptr<const osg::Node> templateNode = mResourceSystem->getSceneManager()->getTemplate(model);
|
||||
|
||||
const NodeMap& nodeMap = getNodeMap();
|
||||
auto found = nodeMap.find(bonename);
|
||||
if (found == nodeMap.end())
|
||||
throw std::runtime_error("Can't find attachment node " + bonename);
|
||||
if(isLight)
|
||||
{
|
||||
osg::Quat rotation(osg::DegreesToRadians(-90.f), osg::Vec3f(1,0,0));
|
||||
return SceneUtil::attach(templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager(), &rotation);
|
||||
}
|
||||
return SceneUtil::attach(templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager());
|
||||
}
|
||||
|
||||
std::string ActorAnimation::getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const
|
||||
{
|
||||
std::string mesh = shield.getClass().getModel(shield);
|
||||
const ESM::Armor *armor = shield.get<ESM::Armor>()->mBase;
|
||||
const std::vector<ESM::PartReference>& bodyparts = armor->mParts.mParts;
|
||||
// Try to recover the body part model, use ground model as a fallback otherwise.
|
||||
if (!bodyparts.empty())
|
||||
{
|
||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
|
||||
|
||||
// Try to get shield model from bodyparts first, with ground model as fallback
|
||||
for (const auto& part : bodyparts)
|
||||
{
|
||||
// Assume all creatures use the male mesh.
|
||||
if (part.mPart != ESM::PRT_Shield || part.mMale.empty())
|
||||
if (part.mPart != ESM::PRT_Shield)
|
||||
continue;
|
||||
const ESM::BodyPart *bodypart = partStore.search(part.mMale);
|
||||
if (bodypart && bodypart->mData.mType == ESM::BodyPart::MT_Armor && !bodypart->mModel.empty())
|
||||
|
||||
std::string bodypartName;
|
||||
if (female && !part.mFemale.empty())
|
||||
bodypartName = part.mFemale;
|
||||
else if (!part.mMale.empty())
|
||||
bodypartName = part.mMale;
|
||||
|
||||
if (!bodypartName.empty())
|
||||
{
|
||||
mesh = "meshes\\" + bodypart->mModel;
|
||||
break;
|
||||
const ESM::BodyPart *bodypart = partStore.search(bodypartName);
|
||||
if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor)
|
||||
return std::string();
|
||||
if (!bodypart->mModel.empty())
|
||||
return "meshes\\" + bodypart->mModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
return shield.getClass().getModel(shield);
|
||||
}
|
||||
|
||||
std::string ActorAnimation::getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const
|
||||
{
|
||||
std::string mesh = getShieldMesh(shield, false);
|
||||
|
||||
if (mesh.empty())
|
||||
return mesh;
|
||||
@ -143,7 +172,7 @@ bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const
|
||||
const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr);
|
||||
const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||
if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && !getShieldMesh(*shield).empty())
|
||||
if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && !getSheathedShieldMesh(*shield).empty())
|
||||
{
|
||||
if(stats.getDrawState() != MWMechanics::DrawState_Weapon)
|
||||
return false;
|
||||
@ -201,7 +230,7 @@ void ActorAnimation::updateHolsteredShield(bool showCarriedLeft)
|
||||
return;
|
||||
}
|
||||
|
||||
std::string mesh = getShieldMesh(*shield);
|
||||
std::string mesh = getSheathedShieldMesh(*shield);
|
||||
if (mesh.empty())
|
||||
return;
|
||||
|
||||
@ -255,7 +284,7 @@ bool ActorAnimation::useShieldAnimations() const
|
||||
const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||
if (weapon != inv.end() && shield != inv.end() &&
|
||||
shield->getType() == ESM::Armor::sRecordId &&
|
||||
!getShieldMesh(*shield).empty())
|
||||
!getSheathedShieldMesh(*shield).empty())
|
||||
{
|
||||
auto type = weapon->getType();
|
||||
if(type == ESM::Weapon::sRecordId)
|
||||
|
@ -45,7 +45,8 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener
|
||||
virtual void updateHolsteredWeapon(bool showHolsteredWeapons);
|
||||
virtual void updateHolsteredShield(bool showCarriedLeft);
|
||||
virtual void updateQuiver();
|
||||
virtual std::string getShieldMesh(const MWWorld::ConstPtr& shield) const;
|
||||
std::string getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const;
|
||||
virtual std::string getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const;
|
||||
virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon);
|
||||
virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor);
|
||||
virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename)
|
||||
@ -53,6 +54,7 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener
|
||||
osg::Vec4f stubColor = osg::Vec4f(0,0,0,0);
|
||||
return attachMesh(model, bonename, false, &stubColor);
|
||||
};
|
||||
osg::ref_ptr<osg::Node> attach(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool isLight);
|
||||
|
||||
PartHolderPtr mScabbard;
|
||||
PartHolderPtr mHolsteredShield;
|
||||
|
@ -389,11 +389,6 @@ namespace MWRender
|
||||
mAlpha = alpha;
|
||||
}
|
||||
|
||||
void setLightSource(const osg::ref_ptr<SceneUtil::LightSource>& lightSource)
|
||||
{
|
||||
mLightSource = lightSource;
|
||||
}
|
||||
|
||||
protected:
|
||||
void setDefaults(osg::StateSet* stateset) override
|
||||
{
|
||||
@ -416,13 +411,10 @@ namespace MWRender
|
||||
{
|
||||
osg::Material* material = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
|
||||
material->setAlpha(osg::Material::FRONT_AND_BACK, mAlpha);
|
||||
if (mLightSource)
|
||||
mLightSource->setActorFade(mAlpha);
|
||||
}
|
||||
|
||||
private:
|
||||
float mAlpha;
|
||||
osg::ref_ptr<SceneUtil::LightSource> mLightSource;
|
||||
};
|
||||
|
||||
struct Animation::AnimSource
|
||||
@ -968,8 +960,9 @@ namespace MWRender
|
||||
{
|
||||
osg::ref_ptr<osg::Node> node = getNodeMap().at(it->first); // this should not throw, we already checked for the node existing in addAnimSource
|
||||
|
||||
node->addUpdateCallback(it->second);
|
||||
mActiveControllers.emplace_back(node, it->second);
|
||||
osg::Callback* callback = it->second->getAsCallback();
|
||||
node->addUpdateCallback(callback);
|
||||
mActiveControllers.emplace_back(node, callback);
|
||||
|
||||
if (blendMask == 0 && node == mAccumRoot)
|
||||
{
|
||||
@ -1319,10 +1312,10 @@ namespace MWRender
|
||||
|
||||
cache.insert(std::make_pair(model, created));
|
||||
|
||||
return sceneMgr->createInstance(created);
|
||||
return sceneMgr->getInstance(created);
|
||||
}
|
||||
else
|
||||
return sceneMgr->createInstance(found->second);
|
||||
return sceneMgr->getInstance(found->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1484,6 +1477,7 @@ namespace MWRender
|
||||
bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior();
|
||||
|
||||
mExtraLightSource = SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior);
|
||||
mExtraLightSource->setActorFade(mAlpha);
|
||||
}
|
||||
|
||||
void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture)
|
||||
@ -1510,7 +1504,7 @@ namespace MWRender
|
||||
parentNode = mInsert;
|
||||
else
|
||||
{
|
||||
NodeMap::const_iterator found = getNodeMap().find(Misc::StringUtils::lowerCase(bonename));
|
||||
NodeMap::const_iterator found = getNodeMap().find(bonename);
|
||||
if (found == getNodeMap().end())
|
||||
throw std::runtime_error("Can't find bone " + bonename);
|
||||
|
||||
@ -1619,8 +1613,7 @@ namespace MWRender
|
||||
|
||||
const osg::Node* Animation::getNode(const std::string &name) const
|
||||
{
|
||||
std::string lowerName = Misc::StringUtils::lowerCase(name);
|
||||
NodeMap::const_iterator found = getNodeMap().find(lowerName);
|
||||
NodeMap::const_iterator found = getNodeMap().find(name);
|
||||
if (found == getNodeMap().end())
|
||||
return nullptr;
|
||||
else
|
||||
@ -1639,7 +1632,6 @@ namespace MWRender
|
||||
if (mTransparencyUpdater == nullptr)
|
||||
{
|
||||
mTransparencyUpdater = new TransparencyUpdater(alpha);
|
||||
mTransparencyUpdater->setLightSource(mExtraLightSource);
|
||||
mObjectRoot->addCullCallback(mTransparencyUpdater);
|
||||
}
|
||||
else
|
||||
@ -1650,6 +1642,8 @@ namespace MWRender
|
||||
mObjectRoot->removeCullCallback(mTransparencyUpdater);
|
||||
mTransparencyUpdater = nullptr;
|
||||
}
|
||||
if (mExtraLightSource)
|
||||
mExtraLightSource->setActorFade(alpha);
|
||||
}
|
||||
|
||||
void Animation::setLightEffect(float effect)
|
||||
|
@ -7,8 +7,10 @@
|
||||
#include <components/sceneutil/textkeymap.hpp>
|
||||
#include <components/sceneutil/util.hpp>
|
||||
#include <components/sceneutil/nodecallback.hpp>
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
@ -157,6 +159,8 @@ public:
|
||||
|
||||
virtual bool updateCarriedLeftVisible(const int weaptype) const { return false; };
|
||||
|
||||
typedef std::unordered_map<std::string, osg::ref_ptr<osg::MatrixTransform>, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual> NodeMap;
|
||||
|
||||
protected:
|
||||
class AnimationTime : public SceneUtil::ControllerSource
|
||||
{
|
||||
@ -250,8 +254,6 @@ protected:
|
||||
|
||||
std::shared_ptr<AnimationTime> mAnimationTimePtr[sNumBlendMasks];
|
||||
|
||||
// Stored in all lowercase for a case-insensitive lookup
|
||||
typedef std::map<std::string, osg::ref_ptr<osg::MatrixTransform> > NodeMap;
|
||||
mutable NodeMap mNodeMap;
|
||||
mutable bool mNodeMapCreated;
|
||||
|
||||
|
@ -236,7 +236,7 @@ namespace MWRender
|
||||
mTotalMovement += speed * duration;
|
||||
speed /= (1.f + speed / 500.f);
|
||||
float maxDelta = 300.f * duration;
|
||||
mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta);
|
||||
mSmoothedSpeed += std::clamp(speed - mSmoothedSpeed, -maxDelta, maxDelta);
|
||||
|
||||
mMaxNextCameraDistance = mCameraDistance + duration * (100.f + mBaseCameraDistance);
|
||||
updateStandingPreviewMode();
|
||||
@ -434,7 +434,7 @@ namespace MWRender
|
||||
{
|
||||
const float epsilon = 0.000001f;
|
||||
float limit = static_cast<float>(osg::PI_2) - epsilon;
|
||||
mPitch = osg::clampBetween(angle, -limit, limit);
|
||||
mPitch = std::clamp(angle, -limit, limit);
|
||||
}
|
||||
|
||||
float Camera::getCameraDistance() const
|
||||
@ -460,7 +460,7 @@ namespace MWRender
|
||||
}
|
||||
|
||||
mIsNearest = mBaseCameraDistance <= mNearest;
|
||||
mBaseCameraDistance = osg::clampBetween(mBaseCameraDistance, mNearest, mFurthest);
|
||||
mBaseCameraDistance = std::clamp(mBaseCameraDistance, mNearest, mFurthest);
|
||||
Settings::Manager::setFloat("third person camera distance", "Camera", mBaseCameraDistance);
|
||||
}
|
||||
|
||||
|
@ -190,7 +190,9 @@ namespace MWRender
|
||||
mTexture->setInternalFormat(GL_RGBA);
|
||||
mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
mTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
mTexture->setUserValue("premultiplied alpha", true);
|
||||
|
||||
mTextureStateSet = new osg::StateSet;
|
||||
mTextureStateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE_MINUS_SRC_ALPHA));
|
||||
|
||||
mCamera = new osg::Camera;
|
||||
// hints that the camera is not relative to the master camera
|
||||
|
@ -18,6 +18,7 @@ namespace osg
|
||||
class Camera;
|
||||
class Group;
|
||||
class Viewport;
|
||||
class StateSet;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
@ -41,6 +42,8 @@ namespace MWRender
|
||||
void rebuild();
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> getTexture();
|
||||
/// Get the osg::StateSet required to render the texture correctly, if any.
|
||||
osg::StateSet* getTextureStateSet() { return mTextureStateSet; }
|
||||
|
||||
private:
|
||||
CharacterPreview(const CharacterPreview&);
|
||||
@ -54,6 +57,7 @@ namespace MWRender
|
||||
osg::ref_ptr<osg::Group> mParent;
|
||||
Resource::ResourceSystem* mResourceSystem;
|
||||
osg::ref_ptr<osg::Texture2D> mTexture;
|
||||
osg::ref_ptr<osg::StateSet> mTextureStateSet;
|
||||
osg::ref_ptr<osg::Camera> mCamera;
|
||||
osg::ref_ptr<DrawOnceCallback> mDrawOnceCallback;
|
||||
|
||||
|
@ -126,7 +126,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
||||
if (bonename != "Weapon Bone")
|
||||
{
|
||||
const NodeMap& nodeMap = getNodeMap();
|
||||
NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename));
|
||||
NodeMap::const_iterator found = nodeMap.find(bonename);
|
||||
if (found == nodeMap.end())
|
||||
bonename = "Weapon Bone";
|
||||
}
|
||||
@ -139,32 +139,13 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
||||
bonename = "Shield Bone";
|
||||
if (item.getType() == ESM::Armor::sRecordId)
|
||||
{
|
||||
// Shield body part model should be used if possible.
|
||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||
for (const auto& part : item.get<ESM::Armor>()->mBase->mParts.mParts)
|
||||
{
|
||||
// Assume all creatures use the male mesh.
|
||||
if (part.mPart != ESM::PRT_Shield || part.mMale.empty())
|
||||
continue;
|
||||
const ESM::BodyPart *bodypart = store.get<ESM::BodyPart>().search(part.mMale);
|
||||
if (bodypart && bodypart->mData.mType == ESM::BodyPart::MT_Armor && !bodypart->mModel.empty())
|
||||
{
|
||||
itemModel = "meshes\\" + bodypart->mModel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
itemModel = getShieldMesh(item, false);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
osg::ref_ptr<const osg::Node> node = mResourceSystem->getSceneManager()->getTemplate(itemModel);
|
||||
|
||||
const NodeMap& nodeMap = getNodeMap();
|
||||
NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename));
|
||||
if (found == nodeMap.end())
|
||||
throw std::runtime_error("Can't find attachment node " + bonename);
|
||||
osg::ref_ptr<osg::Node> attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get());
|
||||
osg::ref_ptr<osg::Node> attached = attach(itemModel, bonename, bonename, item.getType() == ESM::Light::sRecordId);
|
||||
|
||||
scene.reset(new PartHolder(attached));
|
||||
|
||||
|
@ -562,8 +562,8 @@ bool LocalMap::isPositionExplored (float nX, float nY, int x, int y)
|
||||
if (!segment.mFogOfWarImage)
|
||||
return false;
|
||||
|
||||
nX = std::max(0.f, std::min(1.f, nX));
|
||||
nY = std::max(0.f, std::min(1.f, nY));
|
||||
nX = std::clamp(nX, 0.f, 1.f);
|
||||
nY = std::clamp(nY, 0.f, 1.f);
|
||||
|
||||
int texU = static_cast<int>((sFogOfWarResolution - 1) * nX);
|
||||
int texV = static_cast<int>((sFogOfWarResolution - 1) * nY);
|
||||
@ -648,7 +648,7 @@ void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orient
|
||||
uint32_t clr = *(uint32_t*)data;
|
||||
uint8_t alpha = (clr >> 24);
|
||||
|
||||
alpha = std::min( alpha, (uint8_t) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) );
|
||||
alpha = std::min( alpha, (uint8_t) (std::clamp((sqrDist/sqrExploreRadius)*255, 0.f, 1.f)));
|
||||
uint32_t val = (uint32_t) (alpha << 24);
|
||||
if ( *data != val)
|
||||
{
|
||||
|
@ -82,34 +82,6 @@ std::string getVampireHead(const std::string& race, bool female)
|
||||
return "meshes\\" + bodyPart->mModel;
|
||||
}
|
||||
|
||||
std::string getShieldBodypartMesh(const std::vector<ESM::PartReference>& bodyparts, bool female)
|
||||
{
|
||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
|
||||
for (const auto& part : bodyparts)
|
||||
{
|
||||
if (part.mPart != ESM::PRT_Shield)
|
||||
continue;
|
||||
|
||||
std::string bodypartName;
|
||||
if (female && !part.mFemale.empty())
|
||||
bodypartName = part.mFemale;
|
||||
else if (!part.mMale.empty())
|
||||
bodypartName = part.mMale;
|
||||
|
||||
if (!bodypartName.empty())
|
||||
{
|
||||
const ESM::BodyPart *bodypart = partStore.search(bodypartName);
|
||||
if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor)
|
||||
return std::string();
|
||||
if (!bodypart->mModel.empty())
|
||||
return "meshes\\" + bodypart->mModel;
|
||||
}
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -547,14 +519,9 @@ void NpcAnimation::updateNpcBase()
|
||||
mWeaponAnimationTime->updateStartTime();
|
||||
}
|
||||
|
||||
std::string NpcAnimation::getShieldMesh(const MWWorld::ConstPtr& shield) const
|
||||
std::string NpcAnimation::getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const
|
||||
{
|
||||
std::string mesh = shield.getClass().getModel(shield);
|
||||
const ESM::Armor *armor = shield.get<ESM::Armor>()->mBase;
|
||||
const std::vector<ESM::PartReference>& bodyparts = armor->mParts.mParts;
|
||||
// Try to recover the body part model, use ground model as a fallback otherwise.
|
||||
if (!bodyparts.empty())
|
||||
mesh = getShieldBodypartMesh(bodyparts, !mNpc->isMale());
|
||||
std::string mesh = getShieldMesh(shield, !mNpc->isMale());
|
||||
|
||||
if (mesh.empty())
|
||||
return std::string();
|
||||
@ -678,7 +645,7 @@ void NpcAnimation::updateParts()
|
||||
{
|
||||
const ESM::Light *light = part.get<ESM::Light>()->mBase;
|
||||
addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft,
|
||||
1, "meshes\\"+light->mModel);
|
||||
1, "meshes\\"+light->mModel, false, nullptr, true);
|
||||
if (mObjectParts[ESM::PRT_Shield])
|
||||
addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), light);
|
||||
}
|
||||
@ -708,16 +675,9 @@ void NpcAnimation::updateParts()
|
||||
|
||||
|
||||
|
||||
PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor)
|
||||
PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight)
|
||||
{
|
||||
osg::ref_ptr<const osg::Node> templateNode = mResourceSystem->getSceneManager()->getTemplate(model);
|
||||
|
||||
const NodeMap& nodeMap = getNodeMap();
|
||||
NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename));
|
||||
if (found == nodeMap.end())
|
||||
throw std::runtime_error("Can't find attachment node " + bonename);
|
||||
|
||||
osg::ref_ptr<osg::Node> attached = SceneUtil::attach(templateNode, mObjectRoot, bonefilter, found->second);
|
||||
osg::ref_ptr<osg::Node> attached = attach(model, bonename, bonefilter, isLight);
|
||||
if (enchantedGlow)
|
||||
mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor);
|
||||
|
||||
@ -790,7 +750,7 @@ bool NpcAnimation::isFemalePart(const ESM::BodyPart* bodypart)
|
||||
return bodypart->mData.mFlags & ESM::BodyPart::BPF_Female;
|
||||
}
|
||||
|
||||
bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, bool enchantedGlow, osg::Vec4f* glowColor)
|
||||
bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight)
|
||||
{
|
||||
if(priority <= mPartPriorities[type])
|
||||
return false;
|
||||
@ -813,7 +773,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
|
||||
if (weaponBonename != bonename)
|
||||
{
|
||||
const NodeMap& nodeMap = getNodeMap();
|
||||
NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(weaponBonename));
|
||||
NodeMap::const_iterator found = nodeMap.find(weaponBonename);
|
||||
if (found != nodeMap.end())
|
||||
bonename = weaponBonename;
|
||||
}
|
||||
@ -822,7 +782,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
|
||||
|
||||
// PRT_Hair seems to be the only type that breaks consistency and uses a filter that's different from the attachment bone
|
||||
const std::string bonefilter = (type == ESM::PRT_Hair) ? "hair" : bonename;
|
||||
mObjectParts[type] = insertBoundedPart(mesh, bonename, bonefilter, enchantedGlow, glowColor);
|
||||
mObjectParts[type] = insertBoundedPart(mesh, bonename, bonefilter, enchantedGlow, glowColor, isLight);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
@ -1011,13 +971,10 @@ void NpcAnimation::showCarriedLeft(bool show)
|
||||
// For shields we must try to use the body part model
|
||||
if (iter->getType() == ESM::Armor::sRecordId)
|
||||
{
|
||||
const ESM::Armor *armor = iter->get<ESM::Armor>()->mBase;
|
||||
const std::vector<ESM::PartReference>& bodyparts = armor->mParts.mParts;
|
||||
if (!bodyparts.empty())
|
||||
mesh = getShieldBodypartMesh(bodyparts, !mNpc->isMale());
|
||||
mesh = getShieldMesh(*iter, !mNpc->isMale());
|
||||
}
|
||||
if (mesh.empty() || addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1,
|
||||
mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor))
|
||||
mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor, iter->getType() == ESM::Light::sRecordId))
|
||||
{
|
||||
if (mesh.empty())
|
||||
reserveIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1);
|
||||
|
@ -83,13 +83,13 @@ private:
|
||||
NpcType getNpcType() const;
|
||||
|
||||
PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename,
|
||||
const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor=nullptr);
|
||||
const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight);
|
||||
|
||||
void removeIndividualPart(ESM::PartReferenceType type);
|
||||
void reserveIndividualPart(ESM::PartReferenceType type, int group, int priority);
|
||||
|
||||
bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh,
|
||||
bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr);
|
||||
bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr, bool isLight = false);
|
||||
void removePartGroup(int group);
|
||||
void addPartGroup(int group, int priority, const std::vector<ESM::PartReference> &parts,
|
||||
bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr);
|
||||
@ -105,7 +105,7 @@ private:
|
||||
protected:
|
||||
void addControllers() override;
|
||||
bool isArrowAttached() const override;
|
||||
std::string getShieldMesh(const MWWorld::ConstPtr& shield) const override;
|
||||
std::string getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const override;
|
||||
|
||||
public:
|
||||
/**
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user