mirror of
https://github.com/OpenMW/openmw.git
synced 2025-06-20 21:41:35 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
6d98866be0
264 changed files with 8493 additions and 5628 deletions
|
@ -8,6 +8,9 @@ stages:
|
||||||
- docker
|
- docker
|
||||||
- linux
|
- linux
|
||||||
image: debian:bullseye
|
image: debian:bullseye
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "push"
|
||||||
|
|
||||||
|
|
||||||
.Debian:
|
.Debian:
|
||||||
extends: .Debian_Image
|
extends: .Debian_Image
|
||||||
|
@ -52,7 +55,7 @@ Coverity:
|
||||||
extends: .Debian_Image
|
extends: .Debian_Image
|
||||||
stage: build
|
stage: build
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||||
before_script:
|
before_script:
|
||||||
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic coverity
|
- 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
|
- 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:
|
variables:
|
||||||
CC: gcc
|
CC: gcc
|
||||||
CXX: g++
|
CXX: g++
|
||||||
artifacts:
|
|
||||||
|
|
||||||
Debian_GCC:
|
Debian_GCC:
|
||||||
extends: .Debian
|
extends: .Debian
|
||||||
|
@ -162,6 +164,7 @@ Debian_Clang_tests_Debug:
|
||||||
only:
|
only:
|
||||||
variables:
|
variables:
|
||||||
- $CI_PROJECT_ID == "7107382"
|
- $CI_PROJECT_ID == "7107382"
|
||||||
|
- $CI_PIPELINE_SOURCE == "push"
|
||||||
cache:
|
cache:
|
||||||
paths:
|
paths:
|
||||||
- ccache/
|
- ccache/
|
||||||
|
@ -184,7 +187,6 @@ Debian_Clang_tests_Debug:
|
||||||
macOS11_Xcode12:
|
macOS11_Xcode12:
|
||||||
extends: .MacOS
|
extends: .MacOS
|
||||||
image: macos-11-xcode-12
|
image: macos-11-xcode-12
|
||||||
allow_failure: true
|
|
||||||
cache:
|
cache:
|
||||||
key: macOS11_Xcode12.v1
|
key: macOS11_Xcode12.v1
|
||||||
variables:
|
variables:
|
||||||
|
@ -193,6 +195,7 @@ macOS11_Xcode12:
|
||||||
macOS10.15_Xcode11:
|
macOS10.15_Xcode11:
|
||||||
extends: .MacOS
|
extends: .MacOS
|
||||||
image: macos-10.15-xcode-11
|
image: macos-10.15-xcode-11
|
||||||
|
allow_failure: true
|
||||||
cache:
|
cache:
|
||||||
key: macOS10.15_Xcode11.v1
|
key: macOS10.15_Xcode11.v1
|
||||||
variables:
|
variables:
|
||||||
|
@ -213,6 +216,8 @@ variables: &tests-targets
|
||||||
.Windows_Ninja_Base:
|
.Windows_Ninja_Base:
|
||||||
tags:
|
tags:
|
||||||
- windows
|
- windows
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "push"
|
||||||
before_script:
|
before_script:
|
||||||
- Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
|
- Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
|
||||||
- choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1
|
- 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:
|
.Windows_MSBuild_Base:
|
||||||
tags:
|
tags:
|
||||||
- windows
|
- windows
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "push"
|
||||||
before_script:
|
before_script:
|
||||||
- Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
|
- Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
|
||||||
- choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1
|
- 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
|
||||||
- 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:
|
Windows_MSBuild_Engine_Release:
|
||||||
extends:
|
extends:
|
||||||
- .Windows_MSBuild_Base
|
- .Windows_MSBuild_Base
|
||||||
|
@ -444,6 +460,8 @@ Debian_AndroidNDK_arm64-v8a:
|
||||||
tags:
|
tags:
|
||||||
- linux
|
- linux
|
||||||
image: debian:bullseye
|
image: debian:bullseye
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "push"
|
||||||
variables:
|
variables:
|
||||||
CCACHE_SIZE: 3G
|
CCACHE_SIZE: 3G
|
||||||
cache:
|
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 #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 #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 #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 #3846: Strings starting with "-" fail to compile if not enclosed in quotes
|
||||||
Bug #3905: Great House Dagoth issues
|
Bug #3905: Great House Dagoth issues
|
||||||
Bug #4203: Resurrecting an actor should close the loot GUI
|
Bug #4203: Resurrecting an actor should close the loot GUI
|
||||||
Bug #4602: Robert's Bodies: crash inside createInstance()
|
Bug #4602: Robert's Bodies: crash inside createInstance()
|
||||||
Bug #4700: Editor: Incorrect command implementation
|
Bug #4700: Editor: Incorrect command implementation
|
||||||
Bug #4744: Invisible particles must still be processed
|
Bug #4744: Invisible particles must still be processed
|
||||||
|
Bug #5088: Sky abruptly changes direction during certain weather transitions
|
||||||
Bug #5100: Persuasion doesn't always clamp the resulting disposition
|
Bug #5100: Persuasion doesn't always clamp the resulting disposition
|
||||||
Bug #5120: Scripted object spawning updates physics system
|
Bug #5120: Scripted object spawning updates physics system
|
||||||
Bug #5207: Loose summons can be present in scene
|
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 #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention
|
||||||
Bug #5842: GetDisposition adds temporary disposition change from different actors
|
Bug #5842: GetDisposition adds temporary disposition change from different actors
|
||||||
Bug #5863: GetEffect should return true after the player has teleported
|
Bug #5863: GetEffect should return true after the player has teleported
|
||||||
|
Bug #5913: Failed assertion during Ritual of Trees quest
|
||||||
|
Bug #5937: Lights always need to be rotated by 90 degrees
|
||||||
Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher
|
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 #6051: NaN water height in ESM file is not handled gracefully
|
||||||
Bug #6066: addtopic "return" does not work from within script. No errors thrown
|
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 #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 #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 #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 #6172: Some creatures can't open doors
|
||||||
Bug #6174: Spellmaking and Enchanting sliders differences from vanilla
|
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 #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla
|
||||||
Bug #6197: Infinite Casting Loop
|
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 #6258: Barter menu glitches out when modifying prices
|
||||||
Bug #6273: Respawning NPCs rotation is inconsistent
|
Bug #6273: Respawning NPCs rotation is inconsistent
|
||||||
Bug #6282: Laura craft doesn't follow the player character
|
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 #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 #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 #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 #890: OpenMW-CS: Column filtering
|
||||||
Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record
|
Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record
|
||||||
Feature #2780: A way to see current OpenMW version in the console
|
Feature #2780: A way to see current OpenMW version in the console
|
||||||
|
@ -72,6 +82,7 @@
|
||||||
Feature #6017: Separate persistent and temporary cell references when saving
|
Feature #6017: Separate persistent and temporary cell references when saving
|
||||||
Feature #6032: Reverse-z depth buffer
|
Feature #6032: Reverse-z depth buffer
|
||||||
Feature #6078: First person should not clear 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 #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly
|
||||||
Feature #6199: Support FBO Rendering
|
Feature #6199: Support FBO Rendering
|
||||||
Feature #6249: Alpha testing support for Collada
|
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 #6201: Remove the "Note: No relevant classes found. No output generated" warnings
|
||||||
Task #6264: Remove the old classes in animation.cpp
|
Task #6264: Remove the old classes in animation.cpp
|
||||||
|
|
||||||
|
|
||||||
0.47.0
|
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)
|
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()
|
endif()
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
add_compile_options("/utf-8")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
find_package(OpenGL REQUIRED)
|
find_package(OpenGL REQUIRED)
|
||||||
|
|
||||||
|
@ -707,6 +711,8 @@ endif()
|
||||||
if (BUILD_OPENMW AND APPLE)
|
if (BUILD_OPENMW AND APPLE)
|
||||||
# Without these flags LuaJit crashes on startup on OSX
|
# Without these flags LuaJit crashes on startup on OSX
|
||||||
set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000")
|
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()
|
endif()
|
||||||
|
|
||||||
# Apple bundling
|
# Apple bundling
|
||||||
|
|
|
@ -32,7 +32,7 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config:
|
||||||
{
|
{
|
||||||
ui.setupUi (this);
|
ui.setupUi (this);
|
||||||
setObjectName ("DataFilesPage");
|
setObjectName ("DataFilesPage");
|
||||||
mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget);
|
mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget, /*showOMWScripts=*/true);
|
||||||
const QString encoding = mGameSettings.value("encoding", "win1252");
|
const QString encoding = mGameSettings.value("encoding", "win1252");
|
||||||
mSelector->setEncoding(encoding);
|
mSelector->setEncoding(encoding);
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ QSet<QString> CellNameLoader::getCellNames(QStringList &contentPaths)
|
||||||
|
|
||||||
// Loop through all content files
|
// Loop through all content files
|
||||||
for (auto &contentPath : contentPaths) {
|
for (auto &contentPath : contentPaths) {
|
||||||
|
if (contentPath.endsWith(".omwscripts", Qt::CaseInsensitive))
|
||||||
|
continue;
|
||||||
esmReader.open(contentPath.toStdString());
|
esmReader.open(contentPath.toStdString());
|
||||||
|
|
||||||
// Loop through all records
|
// Loop through all records
|
||||||
|
|
|
@ -296,7 +296,7 @@ namespace CSMWorld
|
||||||
const std::string& destination, const UniversalId::Type type)
|
const std::string& destination, const UniversalId::Type type)
|
||||||
{
|
{
|
||||||
int index = cloneRecordImp(origin, destination, type);
|
int index = cloneRecordImp(origin, destination, type);
|
||||||
mRecords.at(index)->get().mPlugin = 0;
|
mRecords.at(index)->get().setPlugin(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename ESXRecordT, typename IdAccessorT>
|
template<typename ESXRecordT, typename IdAccessorT>
|
||||||
|
@ -311,7 +311,7 @@ namespace CSMWorld
|
||||||
int index = touchRecordImp(id);
|
int index = touchRecordImp(id);
|
||||||
if (index >= 0)
|
if (index >= 0)
|
||||||
{
|
{
|
||||||
mRecords.at(index)->get().mPlugin = 0;
|
mRecords.at(index)->get().setPlugin(0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ namespace CSMWorld
|
||||||
|
|
||||||
QVariant LandPluginIndexColumn::get(const Record<Land>& record) const
|
QVariant LandPluginIndexColumn::get(const Record<Land>& record) const
|
||||||
{
|
{
|
||||||
return record.get().mPlugin;
|
return record.get().getPlugin();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LandPluginIndexColumn::isEditable() const
|
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));
|
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()
|
void CSMWorld::ScriptContext::invalidateIds()
|
||||||
{
|
{
|
||||||
mIdsUpdated = false;
|
mIdsUpdated = false;
|
||||||
|
|
|
@ -39,9 +39,6 @@ namespace CSMWorld
|
||||||
bool isId (const std::string& name) const override;
|
bool isId (const std::string& name) const override;
|
||||||
///< Does \a name match an ID, that can be referenced?
|
///< 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 invalidateIds();
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
|
|
|
@ -24,7 +24,7 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) :
|
||||||
resize(400, 400);
|
resize(400, 400);
|
||||||
|
|
||||||
setObjectName ("FileDialog");
|
setObjectName ("FileDialog");
|
||||||
mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget);
|
mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget, /*showOMWScripts=*/false);
|
||||||
mAdjusterWidget = new AdjusterWidget (this);
|
mAdjusterWidget = new AdjusterWidget (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -111,7 +111,7 @@ namespace CSVRender
|
||||||
if (!mesh.empty() && node != mNodeMap.end())
|
if (!mesh.empty() && node != mNodeMap.end())
|
||||||
{
|
{
|
||||||
auto instance = sceneMgr->getInstance(mesh);
|
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 OuterRadius = InnerRadius + MarkerShaftWidth;
|
||||||
|
|
||||||
const float SegmentDistance = 100.f;
|
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 VerticesPerSegment = 4;
|
||||||
const size_t IndicesPerSegment = 24;
|
const size_t IndicesPerSegment = 24;
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ set(GAME_HEADER
|
||||||
source_group(game FILES ${GAME} ${GAME_HEADER})
|
source_group(game FILES ${GAME} ${GAME_HEADER})
|
||||||
|
|
||||||
add_openmw_dir (mwrender
|
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
|
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager
|
||||||
bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation
|
bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation
|
||||||
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor
|
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
|
containerstore actiontalk actiontake manualref player cellvisitors failedaction
|
||||||
cells localscripts customdata inventorystore ptr actionopen actionread actionharvest
|
cells localscripts customdata inventorystore ptr actionopen actionread actionharvest
|
||||||
actionequip timestamp actionalchemy cellstore actionapply actioneat
|
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
|
contentloader esmloader actiontrap cellreflist cellref weather projectilemanager
|
||||||
cellpreloader datetimemanager
|
cellpreloader datetimemanager
|
||||||
)
|
)
|
||||||
|
@ -94,7 +94,7 @@ add_openmw_dir (mwmechanics
|
||||||
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance
|
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance
|
||||||
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
|
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
|
||||||
character actors objects aistate trading weaponpriority spellpriority weapontype spellutil
|
character actors objects aistate trading weaponpriority spellpriority weapontype spellutil
|
||||||
spellabsorption spelleffects
|
spelleffects
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwstate
|
add_openmw_dir (mwstate
|
||||||
|
|
|
@ -495,11 +495,6 @@ void OMW::Engine::addGroundcoverFile(const std::string& file)
|
||||||
mGroundcoverFiles.emplace_back(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)
|
void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame)
|
||||||
{
|
{
|
||||||
mSkipMenu = skipMenu;
|
mSkipMenu = skipMenu;
|
||||||
|
@ -674,7 +669,7 @@ void OMW::Engine::setWindowIcon()
|
||||||
void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||||
{
|
{
|
||||||
mEnvironment.setStateManager (
|
mEnvironment.setStateManager (
|
||||||
new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0)));
|
new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles));
|
||||||
|
|
||||||
createWindow(settings);
|
createWindow(settings);
|
||||||
|
|
||||||
|
@ -714,7 +709,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||||
|
|
||||||
mViewer->addEventHandler(mScreenCaptureHandler);
|
mViewer->addEventHandler(mScreenCaptureHandler);
|
||||||
|
|
||||||
mLuaManager = new MWLua::LuaManager(mVFS.get(), mLuaScriptListFiles);
|
mLuaManager = new MWLua::LuaManager(mVFS.get());
|
||||||
mEnvironment.setLuaManager(mLuaManager);
|
mEnvironment.setLuaManager(mLuaManager);
|
||||||
|
|
||||||
// Create input and UI first to set up a bootstrapping environment for
|
// Create input and UI first to set up a bootstrapping environment for
|
||||||
|
|
|
@ -72,7 +72,6 @@ namespace OMW
|
||||||
std::string mCellName;
|
std::string mCellName;
|
||||||
std::vector<std::string> mContentFiles;
|
std::vector<std::string> mContentFiles;
|
||||||
std::vector<std::string> mGroundcoverFiles;
|
std::vector<std::string> mGroundcoverFiles;
|
||||||
std::vector<std::string> mLuaScriptListFiles;
|
|
||||||
bool mSkipMenu;
|
bool mSkipMenu;
|
||||||
bool mUseSound;
|
bool mUseSound;
|
||||||
bool mCompileAll;
|
bool mCompileAll;
|
||||||
|
@ -146,7 +145,6 @@ namespace OMW
|
||||||
*/
|
*/
|
||||||
void addContentFile(const std::string& file);
|
void addContentFile(const std::string& file);
|
||||||
void addGroundcoverFile(const std::string& file);
|
void addGroundcoverFile(const std::string& file);
|
||||||
void addLuaScriptListFile(const std::string& file);
|
|
||||||
|
|
||||||
/// Disable or enable all sounds
|
/// Disable or enable all sounds
|
||||||
void setSoundUsage(bool soundUsage);
|
void setSoundUsage(bool soundUsage);
|
||||||
|
|
|
@ -124,9 +124,11 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
||||||
engine.addGroundcoverFile(file);
|
engine.addGroundcoverFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
StringsVector luaScriptLists = variables["lua-scripts"].as<Files::EscapeStringVector>().toStdStringVector();
|
if (variables.count("lua-scripts"))
|
||||||
for (const auto& file : luaScriptLists)
|
{
|
||||||
engine.addLuaScriptListFile(file);
|
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
|
// startup-settings
|
||||||
engine.setCell(variables["start"].as<Files::EscapeHashString>().toStdString());
|
engine.setCell(variables["start"].as<Files::EscapeHashString>().toStdString());
|
||||||
|
|
|
@ -30,6 +30,7 @@ namespace MWBase
|
||||||
virtual ~LuaManager() = default;
|
virtual ~LuaManager() = default;
|
||||||
|
|
||||||
virtual void newGameStarted() = 0;
|
virtual void newGameStarted() = 0;
|
||||||
|
virtual void gameLoaded() = 0;
|
||||||
virtual void registerObject(const MWWorld::Ptr& ptr) = 0;
|
virtual void registerObject(const MWWorld::Ptr& ptr) = 0;
|
||||||
virtual void deregisterObject(const MWWorld::Ptr& ptr) = 0;
|
virtual void deregisterObject(const MWWorld::Ptr& ptr) = 0;
|
||||||
virtual void objectAddedToScene(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;
|
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
|
///< @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
|
///< @return an updated Ptr
|
||||||
|
|
||||||
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
|
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
|
||||||
|
|
|
@ -82,7 +82,7 @@ namespace MWClass
|
||||||
|
|
||||||
const Creature::GMST& Creature::getGmst()
|
const Creature::GMST& Creature::getGmst()
|
||||||
{
|
{
|
||||||
static const GMST gmst = []
|
static const GMST staticGmst = []
|
||||||
{
|
{
|
||||||
GMST gmst;
|
GMST gmst;
|
||||||
|
|
||||||
|
@ -105,14 +105,17 @@ namespace MWClass
|
||||||
|
|
||||||
return gmst;
|
return gmst;
|
||||||
} ();
|
} ();
|
||||||
return gmst;
|
return staticGmst;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Creature::ensureCustomData (const MWWorld::Ptr& ptr) const
|
void Creature::ensureCustomData (const MWWorld::Ptr& ptr) const
|
||||||
{
|
{
|
||||||
if (!ptr.getRefData().getCustomData())
|
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>();
|
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
||||||
|
|
||||||
|
@ -156,10 +159,7 @@ namespace MWClass
|
||||||
|
|
||||||
data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold);
|
data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold);
|
||||||
|
|
||||||
data->mCreatureStats.setNeedRecalcDynamicStats(false);
|
resetter.mPtr = {};
|
||||||
|
|
||||||
// store
|
|
||||||
ptr.getRefData().setCustomData(std::move(data));
|
|
||||||
|
|
||||||
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
|
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include "../mwmechanics/levelledlist.hpp"
|
#include "../mwmechanics/levelledlist.hpp"
|
||||||
|
|
||||||
|
#include "../mwworld/cellstore.hpp"
|
||||||
#include "../mwworld/customdata.hpp"
|
#include "../mwworld/customdata.hpp"
|
||||||
#include "../mwmechanics/creaturestats.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
|
std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const
|
||||||
{
|
{
|
||||||
return "";
|
return "";
|
||||||
|
|
|
@ -32,6 +32,10 @@ namespace MWClass
|
||||||
///< Write additional state from \a ptr into \a state.
|
///< Write additional state from \a ptr into \a state.
|
||||||
|
|
||||||
void respawn (const MWWorld::Ptr& ptr) const override;
|
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()
|
const Npc::GMST& Npc::getGmst()
|
||||||
{
|
{
|
||||||
static const GMST gmst = []
|
static const GMST staticGmst = []
|
||||||
{
|
{
|
||||||
GMST gmst;
|
GMST gmst;
|
||||||
|
|
||||||
|
@ -296,14 +296,18 @@ namespace MWClass
|
||||||
|
|
||||||
return gmst;
|
return gmst;
|
||||||
} ();
|
} ();
|
||||||
return gmst;
|
return staticGmst;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const
|
void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const
|
||||||
{
|
{
|
||||||
if (!ptr.getRefData().getCustomData())
|
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>();
|
MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
|
||||||
|
|
||||||
|
@ -334,8 +338,6 @@ namespace MWClass
|
||||||
data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel);
|
data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel);
|
||||||
data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition);
|
data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition);
|
||||||
data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation);
|
data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation);
|
||||||
|
|
||||||
data->mNpcStats.setNeedRecalcDynamicStats(false);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -351,7 +353,7 @@ namespace MWClass
|
||||||
autoCalculateAttributes(ref->mBase, data->mNpcStats);
|
autoCalculateAttributes(ref->mBase, data->mNpcStats);
|
||||||
autoCalculateSkills(ref->mBase, data->mNpcStats, ptr, spellsInitialised);
|
autoCalculateSkills(ref->mBase, data->mNpcStats, ptr, spellsInitialised);
|
||||||
|
|
||||||
data->mNpcStats.setNeedRecalcDynamicStats(true);
|
recalculate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persistent actors with 0 health do not play death animation
|
// Persistent actors with 0 health do not play death animation
|
||||||
|
@ -387,7 +389,9 @@ namespace MWClass
|
||||||
data->mNpcStats.setGoldPool(gold);
|
data->mNpcStats.setGoldPool(gold);
|
||||||
|
|
||||||
// store
|
// store
|
||||||
ptr.getRefData().setCustomData(std::move(data));
|
resetter.mPtr = {};
|
||||||
|
if(recalculate)
|
||||||
|
data->mNpcStats.recalculateMagicka();
|
||||||
|
|
||||||
// inventory
|
// inventory
|
||||||
// setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items
|
// 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)
|
// Clamp permanent disposition change so that final disposition doesn't go below 0 (could happen with intimidate)
|
||||||
npcStats.setBaseDisposition(0);
|
npcStats.setBaseDisposition(0);
|
||||||
int zero = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false);
|
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);
|
npcStats.setBaseDisposition(disposition);
|
||||||
}
|
}
|
||||||
|
|
|
@ -347,8 +347,7 @@ namespace MWGui
|
||||||
{
|
{
|
||||||
if (!mScrollBar->getVisible())
|
if (!mScrollBar->getVisible())
|
||||||
return;
|
return;
|
||||||
mScrollBar->setScrollPosition(std::min(static_cast<int>(mScrollBar->getScrollRange()-1),
|
mScrollBar->setScrollPosition(std::clamp<int>(mScrollBar->getScrollPosition() - _rel*0.3, 0, mScrollBar->getScrollRange() - 1));
|
||||||
std::max(0, static_cast<int>(mScrollBar->getScrollPosition() - _rel*0.3))));
|
|
||||||
onScrollbarMoved(mScrollBar, mScrollBar->getScrollPosition());
|
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()));
|
mEnchantmentPoints->setCaption(std::to_string(static_cast<int>(mEnchanting.getEnchantPoints(false))) + " / " + std::to_string(mEnchanting.getMaxEnchantValue()));
|
||||||
mCharge->setCaption(std::to_string(mEnchanting.getGemCharge()));
|
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()));
|
mCastCost->setCaption(std::to_string(mEnchanting.getEffectiveCastCost()));
|
||||||
mPrice->setCaption(std::to_string(mEnchanting.getEnchantPrice()));
|
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();
|
static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fNPCHealthBarFade")->mValue.getFloat();
|
||||||
if (fNPCHealthBarFade > 0.f)
|
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)
|
, mTrading(false)
|
||||||
, mUpdateTimer(0.f)
|
, mUpdateTimer(0.f)
|
||||||
{
|
{
|
||||||
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture()));
|
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture(), mPreview->getTextureStateSet()));
|
||||||
mPreview->rebuild();
|
mPreview->rebuild();
|
||||||
|
|
||||||
mMainWidget->castType<MyGUI::Window>()->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize);
|
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)
|
for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i)
|
||||||
{
|
{
|
||||||
Utf8Stream stream (i->first.c_str());
|
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;
|
continue;
|
||||||
|
|
||||||
visitor (i->second.getName());
|
visitor (i->second.getName());
|
||||||
|
|
|
@ -273,7 +273,7 @@ bool KeyboardNavigation::switchFocus(int direction, bool wrap)
|
||||||
if (wrap)
|
if (wrap)
|
||||||
index = (index + keyFocusList.size())%keyFocusList.size();
|
index = (index + keyFocusList.size())%keyFocusList.size();
|
||||||
else
|
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];
|
MyGUI::Widget* next = keyFocusList[index];
|
||||||
int vertdiff = next->getTop() - focus->getTop();
|
int vertdiff = next->getTop() - focus->getTop();
|
||||||
|
|
|
@ -138,7 +138,7 @@ namespace MWGui
|
||||||
mPreview->rebuild();
|
mPreview->rebuild();
|
||||||
mPreview->setAngle (mCurrentAngle);
|
mPreview->setAngle (mCurrentAngle);
|
||||||
|
|
||||||
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture()));
|
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture(), mPreview->getTextureStateSet()));
|
||||||
mPreviewImage->setRenderItemTexture(mPreviewTexture.get());
|
mPreviewImage->setRenderItemTexture(mPreviewTexture.get());
|
||||||
mPreviewImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f));
|
mPreviewImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f));
|
||||||
|
|
||||||
|
|
|
@ -171,7 +171,7 @@ namespace MWGui
|
||||||
else
|
else
|
||||||
valueStr = MyGUI::utility::toString(int(value));
|
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);
|
value = (value-min)/(max-min);
|
||||||
|
|
||||||
scroll->setScrollPosition(static_cast<size_t>(value * (scroll->getScrollRange() - 1)));
|
scroll->setScrollPosition(static_cast<size_t>(value * (scroll->getScrollRange() - 1)));
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "sortfilteritemmodel.hpp"
|
#include "sortfilteritemmodel.hpp"
|
||||||
|
|
||||||
#include <components/misc/stringops.hpp>
|
#include <components/misc/stringops.hpp>
|
||||||
|
#include <components/misc/utf8stream.hpp>
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/esm/loadalch.hpp>
|
#include <components/esm/loadalch.hpp>
|
||||||
#include <components/esm/loadappa.hpp>
|
#include <components/esm/loadappa.hpp>
|
||||||
|
@ -69,8 +70,8 @@ namespace
|
||||||
return compareType(leftType, rightType);
|
return compareType(leftType, rightType);
|
||||||
|
|
||||||
// compare items by name
|
// compare items by name
|
||||||
std::string leftName = Misc::StringUtils::lowerCaseUtf8(left.mBase.getClass().getName(left.mBase));
|
std::string leftName = Utf8Stream::lowerCaseUtf8(left.mBase.getClass().getName(left.mBase));
|
||||||
std::string rightName = Misc::StringUtils::lowerCaseUtf8(right.mBase.getClass().getName(right.mBase));
|
std::string rightName = Utf8Stream::lowerCaseUtf8(right.mBase.getClass().getName(right.mBase));
|
||||||
|
|
||||||
result = leftName.compare(rightName);
|
result = leftName.compare(rightName);
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
|
@ -213,7 +214,7 @@ namespace MWGui
|
||||||
|
|
||||||
if (!mNameFilter.empty())
|
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;
|
return itemName.find(mNameFilter) != std::string::npos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +227,7 @@ namespace MWGui
|
||||||
|
|
||||||
for (const auto& effect : effects)
|
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)
|
if (ciEffect.find(mEffectFilter) != std::string::npos)
|
||||||
return true;
|
return true;
|
||||||
|
@ -285,7 +286,7 @@ namespace MWGui
|
||||||
return false;
|
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)
|
if(compare.find(mNameFilter) == std::string::npos)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -318,12 +319,12 @@ namespace MWGui
|
||||||
|
|
||||||
void SortFilterItemModel::setNameFilter (const std::string& filter)
|
void SortFilterItemModel::setNameFilter (const std::string& filter)
|
||||||
{
|
{
|
||||||
mNameFilter = Misc::StringUtils::lowerCaseUtf8(filter);
|
mNameFilter = Utf8Stream::lowerCaseUtf8(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SortFilterItemModel::setEffectFilter (const std::string& filter)
|
void SortFilterItemModel::setEffectFilter (const std::string& filter)
|
||||||
{
|
{
|
||||||
mEffectFilter = Misc::StringUtils::lowerCaseUtf8(filter);
|
mEffectFilter = Utf8Stream::lowerCaseUtf8(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SortFilterItemModel::update()
|
void SortFilterItemModel::update()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "spellmodel.hpp"
|
#include "spellmodel.hpp"
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
|
#include <components/misc/utf8stream.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
@ -69,7 +70,7 @@ namespace MWGui
|
||||||
fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effect.mAttribute], "");
|
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)
|
if (convert.find(filter) != std::string::npos)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
@ -90,14 +91,14 @@ namespace MWGui
|
||||||
const MWWorld::ESMStore &esmStore =
|
const MWWorld::ESMStore &esmStore =
|
||||||
MWBase::Environment::get().getWorld()->getStore();
|
MWBase::Environment::get().getWorld()->getStore();
|
||||||
|
|
||||||
std::string filter = Misc::StringUtils::lowerCaseUtf8(mFilter);
|
std::string filter = Utf8Stream::lowerCaseUtf8(mFilter);
|
||||||
|
|
||||||
for (const ESM::Spell* spell : spells)
|
for (const ESM::Spell* spell : spells)
|
||||||
{
|
{
|
||||||
if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell)
|
if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
std::string name = Misc::StringUtils::lowerCaseUtf8(spell->mName);
|
std::string name = Utf8Stream::lowerCaseUtf8(spell->mName);
|
||||||
|
|
||||||
if (name.find(filter) == std::string::npos
|
if (name.find(filter) == std::string::npos
|
||||||
&& !matchingEffectExists(filter, spell->mEffects))
|
&& !matchingEffectExists(filter, spell->mEffects))
|
||||||
|
@ -139,7 +140,7 @@ namespace MWGui
|
||||||
if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce)
|
if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce)
|
||||||
continue;
|
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
|
if (name.find(filter) == std::string::npos
|
||||||
&& !matchingEffectExists(filter, enchant->mEffects))
|
&& !matchingEffectExists(filter, enchant->mEffects))
|
||||||
|
|
|
@ -599,8 +599,7 @@ namespace MWGui
|
||||||
text += "\n#{fontcolourhtml=normal}#{sExpelled}";
|
text += "\n#{fontcolourhtml=normal}#{sExpelled}";
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int rank = factionPair.second;
|
const int rank = std::clamp(factionPair.second, 0, 9);
|
||||||
rank = std::max(0, std::min(9, rank));
|
|
||||||
text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank];
|
text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank];
|
||||||
|
|
||||||
if (rank < 9)
|
if (rank < 9)
|
||||||
|
|
|
@ -70,7 +70,7 @@ namespace MWInput
|
||||||
}
|
}
|
||||||
|
|
||||||
float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input");
|
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);
|
mBindingsManager->setJoystickDeadZone(deadZoneRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -245,8 +245,8 @@ namespace MWInput
|
||||||
mMouseWheel += mouseWheelMove;
|
mMouseWheel += mouseWheelMove;
|
||||||
|
|
||||||
const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
||||||
mGuiCursorX = std::max(0.f, std::min(mGuiCursorX, float(viewSize.width - 1)));
|
mGuiCursorX = std::clamp<float>(mGuiCursorX, 0.f, viewSize.width - 1);
|
||||||
mGuiCursorY = std::max(0.f, std::min(mGuiCursorY, float(viewSize.height - 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));
|
MyGUI::InputManager::getInstance().injectMouseMove(static_cast<int>(mGuiCursorX), static_cast<int>(mGuiCursorY), static_cast<int>(mMouseWheel));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "actions.hpp"
|
#include "actions.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace MWLua
|
||||||
sol::usertype<AsyncPackageId> api = context.mLua->sol().new_usertype<AsyncPackageId>("AsyncPackage");
|
sol::usertype<AsyncPackageId> api = context.mLua->sol().new_usertype<AsyncPackageId>("AsyncPackage");
|
||||||
api["registerTimerCallback"] = [](const AsyncPackageId& asyncId, std::string_view name, sol::function callback)
|
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)};
|
return TimerCallback{asyncId, std::string(name)};
|
||||||
};
|
};
|
||||||
api["newTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
|
api["newTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
|
||||||
|
@ -31,35 +31,34 @@ namespace MWLua
|
||||||
{
|
{
|
||||||
callback.mAsyncId.mContainer->setupSerializableTimer(
|
callback.mAsyncId.mContainer->setupSerializableTimer(
|
||||||
TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay,
|
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,
|
api["newTimerInHours"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
|
||||||
const TimerCallback& callback, sol::object callbackArg)
|
const TimerCallback& callback, sol::object callbackArg)
|
||||||
{
|
{
|
||||||
callback.mAsyncId.mContainer->setupSerializableTimer(
|
callback.mAsyncId.mContainer->setupSerializableTimer(
|
||||||
TimeUnit::HOURS, world->getGameTimeInHours() + delay,
|
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)
|
api["newUnsavableTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
|
||||||
{
|
{
|
||||||
asyncId.mContainer->setupUnsavableTimer(
|
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)
|
api["newUnsavableTimerInHours"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
|
||||||
{
|
{
|
||||||
asyncId.mContainer->setupUnsavableTimer(
|
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)
|
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)
|
auto initializer = [](sol::table hiddenData)
|
||||||
{
|
{
|
||||||
LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY];
|
LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::sScriptIdKey];
|
||||||
hiddenData[Callback::SCRIPT_NAME_KEY] = id.toString();
|
return AsyncPackageId{id.mContainer, id.mIndex, hiddenData};
|
||||||
return AsyncPackageId{id.mContainer, id.mPath, hiddenData};
|
|
||||||
};
|
};
|
||||||
return sol::make_object(context.mLua->sol(), initializer);
|
return sol::make_object(context.mLua->sol(), initializer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,8 @@ namespace MWLua
|
||||||
class GlobalScripts : public LuaUtil::ScriptsContainer
|
class GlobalScripts : public LuaUtil::ScriptsContainer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
GlobalScripts(LuaUtil::LuaState* lua) : LuaUtil::ScriptsContainer(lua, "Global")
|
GlobalScripts(LuaUtil::LuaState* lua) :
|
||||||
|
LuaUtil::ScriptsContainer(lua, "Global", ESM::LuaScriptCfg::sGlobal)
|
||||||
{
|
{
|
||||||
registerEngineHandlers({&mActorActiveHandlers, &mNewGameHandlers, &mPlayerAddedHandlers});
|
registerEngineHandlers({&mActorActiveHandlers, &mNewGameHandlers, &mPlayerAddedHandlers});
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,14 +82,14 @@ namespace MWLua
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj)
|
LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj, ESM::LuaScriptCfg::Flags autoStartMode)
|
||||||
: LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id())), mData(obj)
|
: LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id()), autoStartMode), mData(obj)
|
||||||
{
|
{
|
||||||
this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData));
|
this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData));
|
||||||
registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers});
|
registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers});
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalScripts::receiveEngineEvent(const EngineEvent& event, ObjectRegistry*)
|
void LocalScripts::receiveEngineEvent(const EngineEvent& event)
|
||||||
{
|
{
|
||||||
std::visit([this](auto&& arg)
|
std::visit([this](auto&& arg)
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace MWLua
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static void initializeSelfPackage(const Context&);
|
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; }
|
MWBase::LuaManager::ActorControls* getActorControls() { return &mData.mControls; }
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ namespace MWLua
|
||||||
};
|
};
|
||||||
using EngineEvent = std::variant<OnActive, OnInactive, OnConsume>;
|
using EngineEvent = std::variant<OnActive, OnInactive, OnConsume>;
|
||||||
|
|
||||||
void receiveEngineEvent(const EngineEvent&, ObjectRegistry*);
|
void receiveEngineEvent(const EngineEvent&);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SelfObject mData;
|
SelfObject mData;
|
||||||
|
|
|
@ -25,7 +25,7 @@ namespace MWLua
|
||||||
{
|
{
|
||||||
auto* lua = context.mLua;
|
auto* lua = context.mLua;
|
||||||
sol::table api(lua->sol(), sol::create);
|
sol::table api(lua->sol(), sol::create);
|
||||||
api["API_REVISION"] = 7;
|
api["API_REVISION"] = 8;
|
||||||
api["quit"] = [lua]()
|
api["quit"] = [lua]()
|
||||||
{
|
{
|
||||||
std::string traceback = lua->sol()["debug"]["traceback"]().get<std::string>();
|
std::string traceback = lua->sol()["debug"]["traceback"]().get<std::string>();
|
||||||
|
|
|
@ -48,7 +48,7 @@ namespace MWLua
|
||||||
struct AsyncPackageId
|
struct AsyncPackageId
|
||||||
{
|
{
|
||||||
LuaUtil::ScriptsContainer* mContainer;
|
LuaUtil::ScriptsContainer* mContainer;
|
||||||
std::string mScript;
|
int mScriptId;
|
||||||
sol::table mHiddenData;
|
sol::table mHiddenData;
|
||||||
};
|
};
|
||||||
sol::function getAsyncPackageInitializer(const Context&);
|
sol::function getAsyncPackageInitializer(const Context&);
|
||||||
|
|
|
@ -7,11 +7,11 @@
|
||||||
#include <components/esm/luascripts.hpp>
|
#include <components/esm/luascripts.hpp>
|
||||||
|
|
||||||
#include <components/lua/utilpackage.hpp>
|
#include <components/lua/utilpackage.hpp>
|
||||||
#include <components/lua/omwscriptsparser.hpp>
|
|
||||||
|
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
|
#include "../mwworld/esmstore.hpp"
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
#include "luabindings.hpp"
|
#include "luabindings.hpp"
|
||||||
|
@ -20,10 +20,9 @@
|
||||||
namespace MWLua
|
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();
|
Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion();
|
||||||
mGlobalScriptList = LuaUtil::parseOMWScriptsFiles(vfs, scriptLists);
|
|
||||||
|
|
||||||
mGlobalSerializer = createUserdataSerializer(false, mWorldView.getObjectRegistry());
|
mGlobalSerializer = createUserdataSerializer(false, mWorldView.getObjectRegistry());
|
||||||
mLocalSerializer = createUserdataSerializer(true, mWorldView.getObjectRegistry());
|
mLocalSerializer = createUserdataSerializer(true, mWorldView.getObjectRegistry());
|
||||||
|
@ -33,6 +32,14 @@ namespace MWLua
|
||||||
mGlobalScripts.setSerializer(mGlobalSerializer.get());
|
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()
|
void LuaManager::init()
|
||||||
{
|
{
|
||||||
Context context;
|
Context context;
|
||||||
|
@ -67,23 +74,10 @@ namespace MWLua
|
||||||
mLocalSettingsPackage = initLocalSettingsPackage(localContext);
|
mLocalSettingsPackage = initLocalSettingsPackage(localContext);
|
||||||
mPlayerSettingsPackage = initPlayerSettingsPackage(localContext);
|
mPlayerSettingsPackage = initPlayerSettingsPackage(localContext);
|
||||||
|
|
||||||
mInputEvents.clear();
|
initConfiguration();
|
||||||
for (const std::string& path : mGlobalScriptList)
|
|
||||||
if (mGlobalScripts.addNewScript(path))
|
|
||||||
Log(Debug::Info) << "Global script started: " << path;
|
|
||||||
mInitialized = true;
|
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)
|
void LuaManager::update(bool paused, float dt)
|
||||||
{
|
{
|
||||||
ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry();
|
ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry();
|
||||||
|
@ -160,7 +154,7 @@ namespace MWLua
|
||||||
}
|
}
|
||||||
LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts();
|
LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts();
|
||||||
if (scripts)
|
if (scripts)
|
||||||
scripts->receiveEngineEvent(e.mEvent, objectRegistry);
|
scripts->receiveEngineEvent(e.mEvent);
|
||||||
}
|
}
|
||||||
mLocalEngineEvents.clear();
|
mLocalEngineEvents.clear();
|
||||||
|
|
||||||
|
@ -173,6 +167,11 @@ namespace MWLua
|
||||||
mPlayerChanged = false;
|
mPlayerChanged = false;
|
||||||
mGlobalScripts.playerAdded(GObject(getId(mPlayer), objectRegistry));
|
mGlobalScripts.playerAdded(GObject(getId(mPlayer), objectRegistry));
|
||||||
}
|
}
|
||||||
|
if (mNewGameStarted)
|
||||||
|
{
|
||||||
|
mNewGameStarted = false;
|
||||||
|
mGlobalScripts.newGameStarted();
|
||||||
|
}
|
||||||
|
|
||||||
for (ObjectId id : mActorAddedEvents)
|
for (ObjectId id : mActorAddedEvents)
|
||||||
mGlobalScripts.actorActive(GObject(id, objectRegistry));
|
mGlobalScripts.actorActive(GObject(id, objectRegistry));
|
||||||
|
@ -205,8 +204,11 @@ namespace MWLua
|
||||||
mInputEvents.clear();
|
mInputEvents.clear();
|
||||||
mActorAddedEvents.clear();
|
mActorAddedEvents.clear();
|
||||||
mLocalEngineEvents.clear();
|
mLocalEngineEvents.clear();
|
||||||
|
mNewGameStarted = false;
|
||||||
mPlayerChanged = false;
|
mPlayerChanged = false;
|
||||||
mWorldView.clear();
|
mWorldView.clear();
|
||||||
|
mGlobalScripts.removeAllScripts();
|
||||||
|
mGlobalScriptsStarted = false;
|
||||||
if (!mPlayer.isEmpty())
|
if (!mPlayer.isEmpty())
|
||||||
{
|
{
|
||||||
mPlayer.getCellRef().unsetRefNum();
|
mPlayer.getCellRef().unsetRefNum();
|
||||||
|
@ -225,17 +227,38 @@ namespace MWLua
|
||||||
mPlayer = ptr;
|
mPlayer = ptr;
|
||||||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||||
if (!localScripts)
|
if (!localScripts)
|
||||||
localScripts = createLocalScripts(ptr);
|
localScripts = createLocalScripts(ptr, ESM::LuaScriptCfg::sPlayer);
|
||||||
mActiveLocalScripts.insert(localScripts);
|
mActiveLocalScripts.insert(localScripts);
|
||||||
mLocalEngineEvents.push_back({getId(ptr), LocalScripts::OnActive{}});
|
mLocalEngineEvents.push_back({getId(ptr), LocalScripts::OnActive{}});
|
||||||
mPlayerChanged = true;
|
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)
|
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
|
||||||
{
|
{
|
||||||
mWorldView.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.
|
mWorldView.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.
|
||||||
|
|
||||||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
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)
|
if (localScripts)
|
||||||
{
|
{
|
||||||
mActiveLocalScripts.insert(localScripts);
|
mActiveLocalScripts.insert(localScripts);
|
||||||
|
@ -281,26 +304,26 @@ namespace MWLua
|
||||||
return localScripts->getActorControls();
|
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();
|
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||||
if (!localScripts)
|
if (!localScripts)
|
||||||
{
|
{
|
||||||
localScripts = createLocalScripts(ptr);
|
localScripts = createLocalScripts(ptr, getLuaScriptFlag(ptr));
|
||||||
if (ptr.isInCell() && MWBase::Environment::get().getWorld()->isCellActive(ptr.getCell()))
|
if (ptr.isInCell() && MWBase::Environment::get().getWorld()->isCellActive(ptr.getCell()))
|
||||||
mActiveLocalScripts.insert(localScripts);
|
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(mInitialized);
|
||||||
|
assert(flag != ESM::LuaScriptCfg::sGlobal);
|
||||||
std::shared_ptr<LocalScripts> scripts;
|
std::shared_ptr<LocalScripts> scripts;
|
||||||
// When loading a game, it can be called before LuaManager::setPlayer,
|
if (flag == ESM::LuaScriptCfg::sPlayer)
|
||||||
// so we can't just check ptr == mPlayer here.
|
|
||||||
if (ptr.getCellRef().getRefIdRef() == "player")
|
|
||||||
{
|
{
|
||||||
|
assert(ptr.getCellRef().getRefIdRef() == "player");
|
||||||
scripts = std::make_shared<PlayerScripts>(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()));
|
scripts = std::make_shared<PlayerScripts>(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()));
|
||||||
scripts->addPackage("openmw.ui", mUserInterfacePackage);
|
scripts->addPackage("openmw.ui", mUserInterfacePackage);
|
||||||
scripts->addPackage("openmw.camera", mCameraPackage);
|
scripts->addPackage("openmw.camera", mCameraPackage);
|
||||||
|
@ -309,11 +332,12 @@ namespace MWLua
|
||||||
}
|
}
|
||||||
else
|
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.settings", mLocalSettingsPackage);
|
||||||
}
|
}
|
||||||
scripts->addPackage("openmw.nearby", mNearbyPackage);
|
scripts->addPackage("openmw.nearby", mNearbyPackage);
|
||||||
scripts->setSerializer(mLocalSerializer.get());
|
scripts->setSerializer(mLocalSerializer.get());
|
||||||
|
scripts->addAutoStartedScripts();
|
||||||
|
|
||||||
MWWorld::RefData& refData = ptr.getRefData();
|
MWWorld::RefData& refData = ptr.getRefData();
|
||||||
refData.setLuaScripts(std::move(scripts));
|
refData.setLuaScripts(std::move(scripts));
|
||||||
|
@ -344,8 +368,9 @@ namespace MWLua
|
||||||
loadEvents(mLua.sol(), reader, mGlobalEvents, mLocalEvents, mContentFileMapping, mGlobalLoader.get());
|
loadEvents(mLua.sol(), reader, mGlobalEvents, mLocalEvents, mContentFileMapping, mGlobalLoader.get());
|
||||||
|
|
||||||
mGlobalScripts.setSerializer(mGlobalLoader.get());
|
mGlobalScripts.setSerializer(mGlobalLoader.get());
|
||||||
mGlobalScripts.load(globalScripts, false);
|
mGlobalScripts.load(globalScripts);
|
||||||
mGlobalScripts.setSerializer(mGlobalSerializer.get());
|
mGlobalScripts.setSerializer(mGlobalSerializer.get());
|
||||||
|
mGlobalScriptsStarted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LuaManager::saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data)
|
void LuaManager::saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data)
|
||||||
|
@ -366,10 +391,10 @@ namespace MWLua
|
||||||
}
|
}
|
||||||
|
|
||||||
mWorldView.getObjectRegistry()->registerPtr(ptr);
|
mWorldView.getObjectRegistry()->registerPtr(ptr);
|
||||||
LocalScripts* scripts = createLocalScripts(ptr);
|
LocalScripts* scripts = createLocalScripts(ptr, getLuaScriptFlag(ptr));
|
||||||
|
|
||||||
scripts->setSerializer(mLocalLoader.get());
|
scripts->setSerializer(mLocalLoader.get());
|
||||||
scripts->load(data, true);
|
scripts->load(data);
|
||||||
scripts->setSerializer(mLocalSerializer.get());
|
scripts->setSerializer(mLocalSerializer.get());
|
||||||
|
|
||||||
// LiveCellRef is usually copied after loading, so this Ptr will become invalid and should be deregistered.
|
// 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";
|
Log(Debug::Info) << "Reload Lua";
|
||||||
mLua.dropScriptCache();
|
mLua.dropScriptCache();
|
||||||
|
initConfiguration();
|
||||||
|
|
||||||
{ // Reload global scripts
|
{ // Reload global scripts
|
||||||
ESM::LuaScripts data;
|
ESM::LuaScripts data;
|
||||||
mGlobalScripts.save(data);
|
mGlobalScripts.save(data);
|
||||||
mGlobalScripts.removeAllScripts();
|
mGlobalScripts.load(data);
|
||||||
for (const std::string& path : mGlobalScriptList)
|
|
||||||
if (mGlobalScripts.addNewScript(path))
|
|
||||||
Log(Debug::Info) << "Global script restarted: " << path;
|
|
||||||
mGlobalScripts.load(data, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [id, ptr] : mWorldView.getObjectRegistry()->mObjectMapping)
|
for (const auto& [id, ptr] : mWorldView.getObjectRegistry()->mObjectMapping)
|
||||||
|
@ -398,8 +420,10 @@ namespace MWLua
|
||||||
continue;
|
continue;
|
||||||
ESM::LuaScripts data;
|
ESM::LuaScripts data;
|
||||||
scripts->save(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
|
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
|
class LuaManager : public MWBase::LuaManager
|
||||||
{
|
{
|
||||||
public:
|
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();
|
void init();
|
||||||
|
|
||||||
// Called by engine.cpp every frame. For performance reasons it works in a separate
|
// 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.
|
// Available everywhere through the MWBase::LuaManager interface.
|
||||||
// LuaManager queues these events and propagates to scripts on the next `update` call.
|
// 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 objectAddedToScene(const MWWorld::Ptr& ptr) override;
|
||||||
void objectRemovedFromScene(const MWWorld::Ptr& ptr) override;
|
void objectRemovedFromScene(const MWWorld::Ptr& ptr) override;
|
||||||
void registerObject(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 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".
|
void setupPlayer(const MWWorld::Ptr& ptr) override; // Should be called once after each "clear".
|
||||||
|
|
||||||
// Used only in luabindings
|
// Used only in Lua bindings
|
||||||
void addLocalScript(const MWWorld::Ptr&, const std::string& scriptPath);
|
void addCustomLocalScript(const MWWorld::Ptr&, int scriptId);
|
||||||
void addAction(std::unique_ptr<Action>&& action) { mActionQueue.push_back(std::move(action)); }
|
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 addTeleportPlayerAction(std::unique_ptr<TeleportAction>&& action) { mTeleportPlayerAction = std::move(action); }
|
||||||
void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); }
|
void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); }
|
||||||
|
@ -81,21 +69,27 @@ namespace MWLua
|
||||||
void reloadAllScripts() override;
|
void reloadAllScripts() override;
|
||||||
|
|
||||||
// Used to call Lua callbacks from C++
|
// 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.
|
// Wraps Lua callback into an std::function.
|
||||||
// NOTE: Resulted function is not thread safe. Can not be used while LuaManager::update() or
|
// NOTE: Resulted function is not thread safe. Can not be used while LuaManager::update() or
|
||||||
// any other Lua-related function is running.
|
// any other Lua-related function is running.
|
||||||
template <class Arg>
|
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)); };
|
return [this, c](Arg arg) { this->queueCallback(c, sol::make_object(c.mFunc.lua_state(), arg)); };
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr);
|
void initConfiguration();
|
||||||
|
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags);
|
||||||
|
|
||||||
bool mInitialized = false;
|
bool mInitialized = false;
|
||||||
|
bool mGlobalScriptsStarted = false;
|
||||||
|
LuaUtil::ScriptsConfiguration mConfiguration;
|
||||||
LuaUtil::LuaState mLua;
|
LuaUtil::LuaState mLua;
|
||||||
sol::table mNearbyPackage;
|
sol::table mNearbyPackage;
|
||||||
sol::table mUserInterfacePackage;
|
sol::table mUserInterfacePackage;
|
||||||
|
@ -104,12 +98,12 @@ namespace MWLua
|
||||||
sol::table mLocalSettingsPackage;
|
sol::table mLocalSettingsPackage;
|
||||||
sol::table mPlayerSettingsPackage;
|
sol::table mPlayerSettingsPackage;
|
||||||
|
|
||||||
std::vector<std::string> mGlobalScriptList;
|
|
||||||
GlobalScripts mGlobalScripts{&mLua};
|
GlobalScripts mGlobalScripts{&mLua};
|
||||||
std::set<LocalScripts*> mActiveLocalScripts;
|
std::set<LocalScripts*> mActiveLocalScripts;
|
||||||
WorldView mWorldView;
|
WorldView mWorldView;
|
||||||
|
|
||||||
bool mPlayerChanged = false;
|
bool mPlayerChanged = false;
|
||||||
|
bool mNewGameStarted = false;
|
||||||
MWWorld::Ptr mPlayer;
|
MWWorld::Ptr mPlayer;
|
||||||
|
|
||||||
GlobalEventQueue mGlobalEvents;
|
GlobalEventQueue mGlobalEvents;
|
||||||
|
@ -127,7 +121,7 @@ namespace MWLua
|
||||||
|
|
||||||
struct CallbackWithData
|
struct CallbackWithData
|
||||||
{
|
{
|
||||||
Callback mCallback;
|
LuaUtil::Callback mCallback;
|
||||||
sol::object mArg;
|
sol::object mArg;
|
||||||
};
|
};
|
||||||
std::vector<CallbackWithData> mQueuedCallbacks;
|
std::vector<CallbackWithData> mQueuedCallbacks;
|
||||||
|
|
|
@ -1,19 +1,6 @@
|
||||||
#include "object.hpp"
|
#include "object.hpp"
|
||||||
|
|
||||||
#include "../mwclass/activator.hpp"
|
#include <unordered_map>
|
||||||
#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"
|
|
||||||
|
|
||||||
namespace MWLua
|
namespace MWLua
|
||||||
{
|
{
|
||||||
|
@ -23,28 +10,34 @@ namespace MWLua
|
||||||
return std::to_string(id.mIndex) + "_" + std::to_string(id.mContentFile);
|
return std::to_string(id.mIndex) + "_" + std::to_string(id.mContentFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
const static std::map<std::type_index, std::string_view> classNames = {
|
struct LuaObjectTypeInfo
|
||||||
{typeid(MWClass::Activator), "Activator"},
|
{
|
||||||
{typeid(MWClass::Armor), "Armor"},
|
std::string_view mName;
|
||||||
{typeid(MWClass::Book), "Book"},
|
ESM::LuaScriptCfg::Flags mFlag = 0;
|
||||||
{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"},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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);
|
auto it = luaObjectTypeInfo.find(type);
|
||||||
if (it != classNames.end())
|
if (it != luaObjectTypeInfo.end())
|
||||||
return it->second;
|
return it->second.mName;
|
||||||
else
|
else
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
@ -55,13 +48,31 @@ namespace MWLua
|
||||||
return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker";
|
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")
|
if (ptr.getCellRef().getRefIdRef() == "player")
|
||||||
return "Player";
|
return "Player";
|
||||||
if (isMarker(ptr))
|
if (isMarker(ptr))
|
||||||
return "Marker";
|
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)
|
std::string ptrToString(const MWWorld::Ptr& ptr)
|
||||||
|
@ -69,7 +80,7 @@ namespace MWLua
|
||||||
std::string res = "object";
|
std::string res = "object";
|
||||||
res.append(idToString(getId(ptr)));
|
res.append(idToString(getId(ptr)));
|
||||||
res.append(" (");
|
res.append(" (");
|
||||||
res.append(getMWClassName(ptr));
|
res.append(getLuaObjectTypeName(ptr));
|
||||||
res.append(", ");
|
res.append(", ");
|
||||||
res.append(ptr.getCellRef().getRefIdRef());
|
res.append(ptr.getCellRef().getRefIdRef());
|
||||||
res.append(")");
|
res.append(")");
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
#include <typeindex>
|
#include <typeindex>
|
||||||
|
|
||||||
#include <components/esm/cellref.hpp>
|
#include <components/esm/cellref.hpp>
|
||||||
|
#include <components/esm/defs.hpp>
|
||||||
|
#include <components/esm/luascripts.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
@ -19,8 +21,12 @@ namespace MWLua
|
||||||
std::string idToString(const ObjectId& id);
|
std::string idToString(const ObjectId& id);
|
||||||
std::string ptrToString(const MWWorld::Ptr& ptr);
|
std::string ptrToString(const MWWorld::Ptr& ptr);
|
||||||
bool isMarker(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 getLuaObjectTypeName(ESM::RecNameInts recordType, std::string_view fallback = "Unknown");
|
||||||
std::string_view getMWClassName(const MWWorld::Ptr& ptr);
|
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.
|
// Holds a mapping ObjectId -> MWWord::Ptr.
|
||||||
class ObjectRegistry
|
class ObjectRegistry
|
||||||
|
@ -64,7 +70,7 @@ namespace MWLua
|
||||||
ObjectId id() const { return mId; }
|
ObjectId id() const { return mId; }
|
||||||
|
|
||||||
std::string toString() const;
|
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.
|
// Updates and returns the underlying Ptr. Throws an exception if object is not available.
|
||||||
const MWWorld::Ptr& ptr() const;
|
const MWWorld::Ptr& ptr() const;
|
||||||
|
|
|
@ -42,13 +42,12 @@ namespace MWLua
|
||||||
template <typename ObjT>
|
template <typename ObjT>
|
||||||
using Cell = std::conditional_t<std::is_same_v<ObjT, LObject>, LCell, GCell>;
|
using Cell = std::conditional_t<std::is_same_v<ObjT, LObject>, LCell, GCell>;
|
||||||
|
|
||||||
template <class Class>
|
static const MWWorld::Ptr& requireRecord(ESM::RecNameInts recordType, const MWWorld::Ptr& ptr)
|
||||||
static const MWWorld::Ptr& requireClass(const MWWorld::Ptr& ptr)
|
|
||||||
{
|
{
|
||||||
if (typeid(Class) != typeid(ptr.getClass()))
|
if (ptr.getType() != recordType)
|
||||||
{
|
{
|
||||||
std::string msg = "Requires type '";
|
std::string msg = "Requires type '";
|
||||||
msg.append(getMWClassName(typeid(Class)));
|
msg.append(getLuaObjectTypeName(recordType));
|
||||||
msg.append("', but applied to ");
|
msg.append("', but applied to ");
|
||||||
msg.append(ptrToString(ptr));
|
msg.append(ptrToString(ptr));
|
||||||
throw std::runtime_error(msg);
|
throw std::runtime_error(msg);
|
||||||
|
@ -141,9 +140,43 @@ namespace MWLua
|
||||||
|
|
||||||
if constexpr (std::is_same_v<ObjectT, GObject>)
|
if constexpr (std::is_same_v<ObjectT, GObject>)
|
||||||
{ // Only for global scripts
|
{ // 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,
|
objectT["teleport"] = [luaManager=context.mLuaManager](const GObject& object, std::string_view cell,
|
||||||
|
@ -189,7 +222,7 @@ namespace MWLua
|
||||||
template <class ObjectT>
|
template <class ObjectT>
|
||||||
static void addDoorBindings(sol::usertype<ObjectT>& objectT, const Context& context)
|
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)
|
objectT["isTeleport"] = sol::readonly_property([ptr](const ObjectT& o)
|
||||||
{
|
{
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace MWLua
|
||||||
class PlayerScripts : public LocalScripts
|
class PlayerScripts : public LocalScripts
|
||||||
{
|
{
|
||||||
public:
|
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,
|
registerEngineHandlers({&mKeyPressHandlers, &mKeyReleaseHandlers,
|
||||||
&mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers,
|
&mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers,
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "activespells.hpp"
|
#include "activespells.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
#include <components/misc/rng.hpp>
|
#include <components/misc/rng.hpp>
|
||||||
|
@ -14,6 +16,8 @@
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
|
#include "../mwrender/animation.hpp"
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/inventorystore.hpp"
|
#include "../mwworld/inventorystore.hpp"
|
||||||
|
@ -96,6 +100,11 @@ namespace MWMechanics
|
||||||
, mType(params.mType), mWorsenings(params.mWorsenings), mNextWorsening({params.mNextWorsening})
|
, 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 ActiveSpells::ActiveSpellParams::toEsm() const
|
||||||
{
|
{
|
||||||
ESM::ActiveSpells::ActiveSpellParams params;
|
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?
|
const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid?
|
||||||
bool removedSpell = false;
|
bool removedSpell = false;
|
||||||
|
std::optional<ActiveSpellParams> reflected;
|
||||||
for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
|
for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
|
||||||
{
|
{
|
||||||
bool remove = applyMagicEffect(ptr, caster, *spellIt, *it, duration);
|
auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration);
|
||||||
if(remove)
|
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);
|
it = spellIt->mEffects.erase(it);
|
||||||
else
|
else
|
||||||
++it;
|
++it;
|
||||||
|
@ -231,6 +249,14 @@ namespace MWMechanics
|
||||||
if(removedSpell)
|
if(removedSpell)
|
||||||
break;
|
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)
|
if(removedSpell)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor);
|
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;
|
ESM::ActiveSpells::ActiveSpellParams toEsm() const;
|
||||||
|
|
||||||
friend class ActiveSpells;
|
friend class ActiveSpells;
|
||||||
|
|
|
@ -240,10 +240,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
// magic effects
|
// magic effects
|
||||||
adjustMagicEffects (ptr, duration);
|
adjustMagicEffects (ptr, duration);
|
||||||
if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats())
|
|
||||||
calculateDynamicStats (ptr);
|
|
||||||
|
|
||||||
calculateCreatureStatModifiers (ptr, duration);
|
|
||||||
// fatigue restoration
|
// fatigue restoration
|
||||||
calculateRestoration(ptr, duration);
|
calculateRestoration(ptr, duration);
|
||||||
}
|
}
|
||||||
|
@ -654,29 +651,6 @@ namespace MWMechanics
|
||||||
updateSummons(creature, mTimerDisposeSummonsCorpses == 0.f);
|
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)
|
void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, double hours, bool sleep)
|
||||||
{
|
{
|
||||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
|
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
|
||||||
|
@ -771,14 +745,6 @@ namespace MWMechanics
|
||||||
stats.setFatigue (fatigue);
|
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)
|
bool Actors::isAttackPreparing(const MWWorld::Ptr& ptr)
|
||||||
{
|
{
|
||||||
PtrActorMap::iterator it = mActors.find(ptr);
|
PtrActorMap::iterator it = mActors.find(ptr);
|
||||||
|
@ -1047,13 +1013,10 @@ namespace MWMechanics
|
||||||
void Actors::updateProcessingRange()
|
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)
|
// 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 maxRange = 7168.f;
|
||||||
static const float minProcessingRange = maxProcessingRange / 2.f;
|
static const float minRange = maxRange / 2.f;
|
||||||
|
|
||||||
float actorsProcessingRange = Settings::Manager::getFloat("actors processing range", "Game");
|
mActorsProcessingRange = std::clamp(Settings::Manager::getFloat("actors processing range", "Game"), minRange, maxRange);
|
||||||
actorsProcessingRange = std::min(actorsProcessingRange, maxProcessingRange);
|
|
||||||
actorsProcessingRange = std::max(actorsProcessingRange, minProcessingRange);
|
|
||||||
mActorsProcessingRange = actorsProcessingRange;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately)
|
void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately)
|
||||||
|
@ -1349,7 +1312,7 @@ namespace MWMechanics
|
||||||
angleToApproachingActor = std::atan2(deltaPos.x(), deltaPos.y());
|
angleToApproachingActor = std::atan2(deltaPos.x(), deltaPos.y());
|
||||||
osg::Vec2f posAtT = relPos + relSpeed * t;
|
osg::Vec2f posAtT = relPos + relSpeed * t;
|
||||||
float coef = (posAtT.x() * relSpeed.x() + posAtT.y() * relSpeed.y()) / (collisionDist * collisionDist * maxSpeed);
|
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;
|
movementCorrection = posAtT * coef;
|
||||||
if (otherPtr.getClass().getCreatureStats(otherPtr).isDead())
|
if (otherPtr.getClass().getCreatureStats(otherPtr).isDead())
|
||||||
// In case of dead body still try to go around (it looks natural), but reduce the correction twice.
|
// 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);
|
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
|
// 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);
|
||||||
updateDrowning(iter->first, duration, ctrl->isKnockedOut(), isPlayer);
|
|
||||||
|
|
||||||
if (timerUpdateEquippedLight == 0)
|
|
||||||
updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches);
|
|
||||||
}
|
}
|
||||||
|
if(timerUpdateEquippedLight == 0 && iter->first.getClass().hasInventoryStore(iter->first))
|
||||||
|
updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches);
|
||||||
|
|
||||||
if (luaControls && isConscious(iter->first))
|
if (luaControls && isConscious(iter->first))
|
||||||
{
|
{
|
||||||
|
@ -1711,10 +1672,6 @@ namespace MWMechanics
|
||||||
if (iter->first.getType() == ESM::Creature::sRecordId)
|
if (iter->first.getType() == ESM::Creature::sRecordId)
|
||||||
soulTrap(iter->first);
|
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))
|
if (cls.isEssential(iter->first))
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}");
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}");
|
||||||
}
|
}
|
||||||
|
@ -1730,8 +1687,6 @@ namespace MWMechanics
|
||||||
// Make sure spell effects are removed
|
// Make sure spell effects are removed
|
||||||
purgeSpellEffects(stats.getActorId());
|
purgeSpellEffects(stats.getActorId());
|
||||||
|
|
||||||
// Reset dynamic stats, attributes and skills
|
|
||||||
calculateCreatureStatModifiers(iter->first, 0);
|
|
||||||
stats.getMagicEffects().add(ESM::MagicEffect::Vampirism, vampirism);
|
stats.getMagicEffects().add(ESM::MagicEffect::Vampirism, vampirism);
|
||||||
|
|
||||||
if (isPlayer)
|
if (isPlayer)
|
||||||
|
@ -1816,10 +1771,6 @@ namespace MWMechanics
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
adjustMagicEffects (iter->first, duration);
|
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);
|
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(iter->first);
|
||||||
if (animation)
|
if (animation)
|
||||||
|
@ -2209,7 +2160,6 @@ namespace MWMechanics
|
||||||
void Actors::updateMagicEffects(const MWWorld::Ptr &ptr)
|
void Actors::updateMagicEffects(const MWWorld::Ptr &ptr)
|
||||||
{
|
{
|
||||||
adjustMagicEffects(ptr, 0.f);
|
adjustMagicEffects(ptr, 0.f);
|
||||||
calculateCreatureStatModifiers(ptr, 0.f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Actors::isReadyToBlock(const MWWorld::Ptr &ptr) const
|
bool Actors::isReadyToBlock(const MWWorld::Ptr &ptr) const
|
||||||
|
|
|
@ -43,10 +43,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
void adjustMagicEffects (const MWWorld::Ptr& creature, float duration);
|
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 calculateRestoration (const MWWorld::Ptr& ptr, float duration);
|
||||||
|
|
||||||
void updateDrowning (const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer);
|
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();
|
const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects();
|
||||||
return effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0;
|
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::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::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);
|
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
|
#endif
|
||||||
|
|
|
@ -2044,7 +2044,7 @@ void CharacterController::update(float duration)
|
||||||
mIsMovingBackward = vec.y() < 0;
|
mIsMovingBackward = vec.y() < 0;
|
||||||
|
|
||||||
float maxDelta = osg::PI * duration * (2.5f - cosDelta);
|
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);
|
stats.setSideMovementAngle(stats.getSideMovementAngle() + delta);
|
||||||
effectiveRotation += delta;
|
effectiveRotation += delta;
|
||||||
}
|
}
|
||||||
|
@ -2286,7 +2286,7 @@ void CharacterController::update(float duration)
|
||||||
float swimmingPitch = mAnimation->getBodyPitchRadians();
|
float swimmingPitch = mAnimation->getBodyPitchRadians();
|
||||||
float targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0];
|
float targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0];
|
||||||
float maxSwimPitchDelta = 3.0f * duration;
|
float maxSwimPitchDelta = 3.0f * duration;
|
||||||
swimmingPitch += osg::clampBetween(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta);
|
swimmingPitch += std::clamp(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta);
|
||||||
mAnimation->setBodyPitchRadians(swimmingPitch);
|
mAnimation->setBodyPitchRadians(swimmingPitch);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -2522,7 +2522,7 @@ void CharacterController::unpersistAnimationState()
|
||||||
{
|
{
|
||||||
float start = mAnimation->getTextKeyTime(anim.mGroup+": start");
|
float start = mAnimation->getTextKeyTime(anim.mGroup+": start");
|
||||||
float stop = mAnimation->getTextKeyTime(anim.mGroup+": stop");
|
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);
|
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();
|
float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude();
|
||||||
if (chameleon)
|
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);
|
visibility = std::min(visibility, alpha);
|
||||||
|
@ -2965,8 +2965,8 @@ void CharacterController::updateHeadTracking(float duration)
|
||||||
const double xLimit = osg::DegreesToRadians(40.0);
|
const double xLimit = osg::DegreesToRadians(40.0);
|
||||||
const double zLimit = osg::DegreesToRadians(30.0);
|
const double zLimit = osg::DegreesToRadians(30.0);
|
||||||
double zLimitOffset = mAnimation->getUpperBodyYawRadians();
|
double zLimitOffset = mAnimation->getUpperBodyYawRadians();
|
||||||
xAngleRadians = osg::clampBetween(xAngleRadians, -xLimit, xLimit);
|
xAngleRadians = std::clamp(xAngleRadians, -xLimit, xLimit);
|
||||||
zAngleRadians = osg::clampBetween(zAngleRadians, -zLimit + zLimitOffset, zLimit + zLimitOffset);
|
zAngleRadians = std::clamp(zAngleRadians, -zLimit + zLimitOffset, zLimit + zLimitOffset);
|
||||||
|
|
||||||
float factor = duration*5;
|
float factor = duration*5;
|
||||||
factor = std::min(factor, 1.f);
|
factor = std::min(factor, 1.f);
|
||||||
|
|
|
@ -113,10 +113,9 @@ namespace MWMechanics
|
||||||
+ 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified();
|
+ 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||||
attackerTerm *= attackerStats.getFatigueTerm();
|
attackerTerm *= attackerStats.getFatigueTerm();
|
||||||
|
|
||||||
int x = int(blockerTerm - attackerTerm);
|
const int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger();
|
||||||
int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger();
|
const int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger();
|
||||||
int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger();
|
int x = std::clamp<int>(blockerTerm - attackerTerm, iBlockMinChance, iBlockMaxChance);
|
||||||
x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x));
|
|
||||||
|
|
||||||
if (Misc::Rng::roll0to99() < x)
|
if (Misc::Rng::roll0to99() < x)
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <components/esm/esmreader.hpp>
|
#include <components/esm/esmreader.hpp>
|
||||||
#include <components/esm/esmwriter.hpp>
|
#include <components/esm/esmwriter.hpp>
|
||||||
|
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
#include "../mwworld/player.hpp"
|
#include "../mwworld/player.hpp"
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ namespace MWMechanics
|
||||||
mTalkedTo (false), mAlarmed (false), mAttacked (false),
|
mTalkedTo (false), mAlarmed (false), mAttacked (false),
|
||||||
mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false),
|
mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false),
|
||||||
mHitRecovery(false), mBlock(false), mMovementFlags(0),
|
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)
|
mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0)
|
||||||
{
|
{
|
||||||
for (int i=0; i<4; ++i)
|
for (int i=0; i<4; ++i)
|
||||||
|
@ -146,7 +147,7 @@ namespace MWMechanics
|
||||||
mAttributes[index] = value;
|
mAttributes[index] = value;
|
||||||
|
|
||||||
if (index == ESM::Attribute::Intelligence)
|
if (index == ESM::Attribute::Intelligence)
|
||||||
mRecalcMagicka = true;
|
recalculateMagicka();
|
||||||
else if (index == ESM::Attribute::Strength ||
|
else if (index == ESM::Attribute::Strength ||
|
||||||
index == ESM::Attribute::Willpower ||
|
index == ESM::Attribute::Willpower ||
|
||||||
index == ESM::Attribute::Agility ||
|
index == ESM::Attribute::Agility ||
|
||||||
|
@ -208,11 +209,10 @@ namespace MWMechanics
|
||||||
|
|
||||||
void CreatureStats::modifyMagicEffects(const MagicEffects &effects)
|
void CreatureStats::modifyMagicEffects(const MagicEffects &effects)
|
||||||
{
|
{
|
||||||
if (effects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier()
|
bool recalc = effects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier() != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier();
|
||||||
!= mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier())
|
|
||||||
mRecalcMagicka = true;
|
|
||||||
|
|
||||||
mMagicEffects.setModifiers(effects);
|
mMagicEffects.setModifiers(effects);
|
||||||
|
if(recalc)
|
||||||
|
recalculateMagicka();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreatureStats::setAiSetting (AiSetting index, Stat<int> value)
|
void CreatureStats::setAiSetting (AiSetting index, Stat<int> value)
|
||||||
|
@ -400,19 +400,26 @@ namespace MWMechanics
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CreatureStats::needToRecalcDynamicStats()
|
void CreatureStats::recalculateMagicka()
|
||||||
{
|
{
|
||||||
if (mRecalcMagicka)
|
auto world = MWBase::Environment::get().getWorld();
|
||||||
{
|
float intelligence = getAttribute(ESM::Attribute::Intelligence).getModified();
|
||||||
mRecalcMagicka = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreatureStats::setNeedRecalcDynamicStats(bool val)
|
float base = 1.f;
|
||||||
{
|
const auto& player = world->getPlayerPtr();
|
||||||
mRecalcMagicka = val;
|
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)
|
void CreatureStats::setKnockedDown(bool value)
|
||||||
|
@ -532,7 +539,7 @@ namespace MWMechanics
|
||||||
state.mFallHeight = mFallHeight; // TODO: vertical velocity (move from PhysicActor to CreatureStats?)
|
state.mFallHeight = mFallHeight; // TODO: vertical velocity (move from PhysicActor to CreatureStats?)
|
||||||
state.mLastHitObject = mLastHitObject;
|
state.mLastHitObject = mLastHitObject;
|
||||||
state.mLastHitAttemptObject = mLastHitAttemptObject;
|
state.mLastHitAttemptObject = mLastHitAttemptObject;
|
||||||
state.mRecalcDynamicStats = mRecalcMagicka;
|
state.mRecalcDynamicStats = false;
|
||||||
state.mDrawState = mDrawState;
|
state.mDrawState = mDrawState;
|
||||||
state.mLevel = mLevel;
|
state.mLevel = mLevel;
|
||||||
state.mActorId = mActorId;
|
state.mActorId = mActorId;
|
||||||
|
@ -586,7 +593,6 @@ namespace MWMechanics
|
||||||
mFallHeight = state.mFallHeight;
|
mFallHeight = state.mFallHeight;
|
||||||
mLastHitObject = state.mLastHitObject;
|
mLastHitObject = state.mLastHitObject;
|
||||||
mLastHitAttemptObject = state.mLastHitAttemptObject;
|
mLastHitAttemptObject = state.mLastHitAttemptObject;
|
||||||
mRecalcMagicka = state.mRecalcDynamicStats;
|
|
||||||
mDrawState = DrawState_(state.mDrawState);
|
mDrawState = DrawState_(state.mDrawState);
|
||||||
mLevel = state.mLevel;
|
mLevel = state.mLevel;
|
||||||
mActorId = state.mActorId;
|
mActorId = state.mActorId;
|
||||||
|
@ -627,6 +633,8 @@ namespace MWMechanics
|
||||||
if (state.mHasAiSettings)
|
if (state.mHasAiSettings)
|
||||||
for (int i=0; i<4; ++i)
|
for (int i=0; i<4; ++i)
|
||||||
mAiSettings[i].readState(state.mAiSettings[i]);
|
mAiSettings[i].readState(state.mAiSettings[i]);
|
||||||
|
if(state.mRecalcDynamicStats)
|
||||||
|
recalculateMagicka();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime)
|
void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime)
|
||||||
|
|
|
@ -65,8 +65,6 @@ namespace MWMechanics
|
||||||
std::string mLastHitObject; // The last object to hit this actor
|
std::string mLastHitObject; // The last object to hit this actor
|
||||||
std::string mLastHitAttemptObject; // The last object to attempt 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.
|
// For merchants: the last time items were restocked and gold pool refilled.
|
||||||
MWWorld::TimeStamp mLastRestock;
|
MWWorld::TimeStamp mLastRestock;
|
||||||
|
|
||||||
|
@ -103,8 +101,7 @@ namespace MWMechanics
|
||||||
DrawState_ getDrawState() const;
|
DrawState_ getDrawState() const;
|
||||||
void setDrawState(DrawState_ state);
|
void setDrawState(DrawState_ state);
|
||||||
|
|
||||||
bool needToRecalcDynamicStats();
|
void recalculateMagicka();
|
||||||
void setNeedRecalcDynamicStats(bool val);
|
|
||||||
|
|
||||||
float getFallHeight() const;
|
float getFallHeight() const;
|
||||||
void addToFallHeight(float height);
|
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();
|
const MWWorld::Ptr& player = MWMechanics::getPlayer();
|
||||||
|
|
||||||
// [-500, 500]
|
// [-500, 500]
|
||||||
int difficultySetting = Settings::Manager::getInt("difficulty", "Game");
|
const int difficultySetting = std::clamp(Settings::Manager::getInt("difficulty", "Game"), -500, 500);
|
||||||
difficultySetting = std::min(difficultySetting, 500);
|
|
||||||
difficultySetting = std::max(difficultySetting, -500);
|
|
||||||
|
|
||||||
static const float fDifficultyMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDifficultyMult")->mValue.getFloat();
|
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;
|
ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(mWeaponType)->mWeaponClass;
|
||||||
if (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo)
|
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();
|
MWWorld::Ptr player = getPlayer();
|
||||||
int itemsInInventoryCount = player.getClass().getContainerStore(player).count(mOldItemPtr.getCellRef().getRefId());
|
count = player.getClass().getContainerStore(player).count(mOldItemPtr.getCellRef().getRefId());
|
||||||
count = std::min(itemsInInventoryCount, std::max(1, int(getGemCharge() * multiplier / enchantPoints)));
|
count = std::clamp<int>(getGemCharge() * multiplier / enchantPoints, 1, count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,20 @@
|
||||||
#include "magiceffects.hpp"
|
#include "magiceffects.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <components/esm/effectlist.hpp>
|
#include <components/esm/effectlist.hpp>
|
||||||
#include <components/esm/magiceffects.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
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
EffectKey::EffectKey() : mId (0), mArg (-1) {}
|
EffectKey::EffectKey() : mId (0), mArg (-1) {}
|
||||||
|
@ -74,6 +84,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
mModifier += param.mModifier;
|
mModifier += param.mModifier;
|
||||||
mBase += param.mBase;
|
mBase += param.mBase;
|
||||||
|
truncate(mModifier);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +92,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
mModifier -= param.mModifier;
|
mModifier -= param.mModifier;
|
||||||
mBase -= param.mBase;
|
mBase -= param.mBase;
|
||||||
|
truncate(mModifier);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ namespace MWMechanics
|
||||||
MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr);
|
MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr);
|
||||||
MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats (ptr);
|
MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats (ptr);
|
||||||
|
|
||||||
npcStats.setNeedRecalcDynamicStats(true);
|
npcStats.recalculateMagicka();
|
||||||
|
|
||||||
const ESM::NPC *player = ptr.get<ESM::NPC>()->mBase;
|
const ESM::NPC *player = ptr.get<ESM::NPC>()->mBase;
|
||||||
|
|
||||||
|
@ -222,7 +222,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
// forced update and current value adjustments
|
// forced update and current value adjustments
|
||||||
mActors.updateActor (ptr, 0);
|
mActors.updateActor (ptr, 0);
|
||||||
mActors.updateActor (ptr, 0);
|
|
||||||
|
|
||||||
for (int i=0; i<3; ++i)
|
for (int i=0; i<3; ++i)
|
||||||
{
|
{
|
||||||
|
@ -546,9 +545,9 @@ namespace MWMechanics
|
||||||
|
|
||||||
x += ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Charm).getMagnitude();
|
x += ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Charm).getMagnitude();
|
||||||
|
|
||||||
if(clamp)
|
if (clamp)
|
||||||
return std::max(0,std::min(int(x),100));//, normally clamped to [0..100] when used
|
return std::clamp<int>(x, 0, 100);//, normally clamped to [0..100] when used
|
||||||
return int(x);
|
return static_cast<int>(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MechanicsManager::getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying)
|
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 flee = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Flee).getBase();
|
||||||
int fight = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Fight).getBase();
|
int fight = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Fight).getBase();
|
||||||
npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee,
|
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,
|
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));
|
float c = -std::abs(floor(r * fPerDieRollMult));
|
||||||
|
@ -690,10 +689,10 @@ namespace MWMechanics
|
||||||
float s = c * fPerDieRollMult * fPerTempMult;
|
float s = c * fPerDieRollMult * fPerTempMult;
|
||||||
int flee = npcStats.getAiSetting (CreatureStats::AI_Flee).getBase();
|
int flee = npcStats.getAiSetting (CreatureStats::AI_Flee).getBase();
|
||||||
int fight = npcStats.getAiSetting (CreatureStats::AI_Fight).getBase();
|
int fight = npcStats.getAiSetting (CreatureStats::AI_Fight).getBase();
|
||||||
npcStats.setAiSetting (CreatureStats::AI_Flee,
|
npcStats.setAiSetting(CreatureStats::AI_Flee,
|
||||||
std::max(0, std::min(100, flee + std::min(-int(iPerMinChange), int(-s)))));
|
std::clamp(flee + std::min(-int(iPerMinChange), int(-s)), 0, 100));
|
||||||
npcStats.setAiSetting (CreatureStats::AI_Fight,
|
npcStats.setAiSetting(CreatureStats::AI_Fight,
|
||||||
std::max(0, std::min(100, fight + std::max(int(iPerMinChange), int(s)))));
|
std::clamp(fight + std::max(int(iPerMinChange), int(s)), 0, 100));
|
||||||
}
|
}
|
||||||
x = floor(-c * fPerDieRollMult);
|
x = floor(-c * fPerDieRollMult);
|
||||||
|
|
||||||
|
|
|
@ -371,7 +371,7 @@ int MWMechanics::NpcStats::getReputation() const
|
||||||
void MWMechanics::NpcStats::setReputation(int reputation)
|
void MWMechanics::NpcStats::setReputation(int reputation)
|
||||||
{
|
{
|
||||||
// Reputation is capped in original engine
|
// 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
|
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 "actorutil.hpp"
|
||||||
#include "aifollow.hpp"
|
#include "aifollow.hpp"
|
||||||
#include "creaturestats.hpp"
|
#include "creaturestats.hpp"
|
||||||
#include "spellabsorption.hpp"
|
|
||||||
#include "spelleffects.hpp"
|
#include "spelleffects.hpp"
|
||||||
#include "spellutil.hpp"
|
#include "spellutil.hpp"
|
||||||
#include "summoning.hpp"
|
#include "summoning.hpp"
|
||||||
#include "weapontype.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
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell)
|
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,
|
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();
|
const bool targetIsActor = !target.isEmpty() && target.getClass().isActor();
|
||||||
if (targetIsActor)
|
if (targetIsActor)
|
||||||
|
@ -123,7 +96,6 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ESM::EffectList reflectedEffects;
|
|
||||||
ActiveSpells::ActiveSpellParams params(*this, caster);
|
ActiveSpells::ActiveSpellParams params(*this, caster);
|
||||||
|
|
||||||
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
|
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
|
||||||
|
@ -136,9 +108,6 @@ namespace MWMechanics
|
||||||
// throughout the iteration of this spell's
|
// throughout the iteration of this spell's
|
||||||
// effects, we display a "can't re-cast" message
|
// 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;
|
int currentEffectIndex = 0;
|
||||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
|
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
|
||||||
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
|
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
|
||||||
|
@ -167,19 +136,6 @@ namespace MWMechanics
|
||||||
&& (caster.isEmpty() || !caster.getClass().isActor()))
|
&& (caster.isEmpty() || !caster.getClass().isActor()))
|
||||||
continue;
|
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;
|
ActiveSpells::ActiveEffect effect;
|
||||||
effect.mEffectId = effectIt->mEffectID;
|
effect.mEffectId = effectIt->mEffectID;
|
||||||
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
|
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
|
||||||
|
@ -189,13 +145,8 @@ namespace MWMechanics
|
||||||
effect.mTimeLeft = 0.f;
|
effect.mTimeLeft = 0.f;
|
||||||
effect.mEffectIndex = currentEffectIndex;
|
effect.mEffectIndex = currentEffectIndex;
|
||||||
effect.mFlags = ESM::ActiveEffect::Flag_None;
|
effect.mFlags = ESM::ActiveEffect::Flag_None;
|
||||||
|
if(mManualSpell)
|
||||||
// Avoid applying harmful effects to the player in god mode
|
effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect;
|
||||||
if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful)
|
|
||||||
{
|
|
||||||
effect.mMinMagnitude = 0;
|
|
||||||
effect.mMaxMagnitude = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
||||||
effect.mDuration = hasDuration ? static_cast<float>(effectIt->mDuration) : 1.f;
|
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
|
// add to list of active effects, to apply in next frame
|
||||||
params.getEffects().emplace_back(effect);
|
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 (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.
|
// 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);
|
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);
|
playEffects(target, *magicEffect);
|
||||||
}
|
}
|
||||||
|
@ -227,9 +178,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (!target.isEmpty())
|
if (!target.isEmpty())
|
||||||
{
|
{
|
||||||
if (!reflectedEffects.mList.empty())
|
|
||||||
inflict(caster, target, reflectedEffects, range, true, exploded);
|
|
||||||
|
|
||||||
if (!params.getEffects().empty())
|
if (!params.getEffects().empty())
|
||||||
{
|
{
|
||||||
if(targetIsActor)
|
if(targetIsActor)
|
||||||
|
@ -237,6 +185,7 @@ namespace MWMechanics
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Apply effects instantly. We can ignore effect deletion since the entire params object gets deleted afterwards anyway
|
// 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())
|
for(auto& effect : params.getEffects())
|
||||||
applyMagicEffect(target, caster, params, effect, 0.f);
|
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 target can be any type of object, not just actors.
|
||||||
/// @note \a caster can be any type of object, or even an empty object.
|
/// @note \a caster can be any type of object, or even an empty object.
|
||||||
void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
|
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);
|
void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true);
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "../mwmechanics/aifollow.hpp"
|
#include "../mwmechanics/aifollow.hpp"
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
#include "../mwmechanics/npcstats.hpp"
|
||||||
#include "../mwmechanics/spellresistance.hpp"
|
#include "../mwmechanics/spellresistance.hpp"
|
||||||
|
#include "../mwmechanics/spellutil.hpp"
|
||||||
#include "../mwmechanics/summoning.hpp"
|
#include "../mwmechanics/summoning.hpp"
|
||||||
|
|
||||||
#include "../mwrender/animation.hpp"
|
#include "../mwrender/animation.hpp"
|
||||||
|
@ -261,6 +262,97 @@ namespace
|
||||||
return false;
|
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{
|
static const std::map<int, std::string> sBoundItemsMap{
|
||||||
{ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID"},
|
{ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID"},
|
||||||
{ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID"},
|
{ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID"},
|
||||||
|
@ -279,7 +371,7 @@ namespace
|
||||||
namespace MWMechanics
|
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();
|
const auto world = MWBase::Environment::get().getWorld();
|
||||||
bool godmode = target == getPlayer() && world->getGodModeState();
|
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)
|
if (!target.isInCell() || !target.getCell()->isExterior() || godmode)
|
||||||
break;
|
break;
|
||||||
float time = world->getTimeStamp().getHour();
|
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;
|
float damageScale = 1.f - timeDiff / 7.f;
|
||||||
// When cloudy, the sun damage effect is halved
|
// When cloudy, the sun damage effect is halved
|
||||||
static float fMagicSunBlockedMult = world->getStore().get<ESM::GameSetting>().find("fMagicSunBlockedMult")->mValue.getFloat();
|
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);
|
fortifySkill(target, effect, effect.mMagnitude);
|
||||||
break;
|
break;
|
||||||
case ESM::MagicEffect::FortifyMaximumMagicka:
|
case ESM::MagicEffect::FortifyMaximumMagicka:
|
||||||
target.getClass().getCreatureStats(target).setNeedRecalcDynamicStats(true);
|
recalculateMagicka = true;
|
||||||
break;
|
break;
|
||||||
case ESM::MagicEffect::AbsorbHealth:
|
case ESM::MagicEffect::AbsorbHealth:
|
||||||
case ESM::MagicEffect::AbsorbMagicka:
|
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();
|
const auto world = MWBase::Environment::get().getWorld();
|
||||||
bool invalid = false;
|
bool invalid = false;
|
||||||
bool receivedMagicDamage = false;
|
bool receivedMagicDamage = false;
|
||||||
|
bool recalculateMagicka = false;
|
||||||
if(effect.mEffectId == ESM::MagicEffect::Corprus && spellParams.shouldWorsen())
|
if(effect.mEffectId == ESM::MagicEffect::Corprus && spellParams.shouldWorsen())
|
||||||
{
|
{
|
||||||
spellParams.worsen();
|
spellParams.worsen();
|
||||||
for(auto& otherEffect : spellParams.getEffects())
|
for(auto& otherEffect : spellParams.getEffects())
|
||||||
{
|
{
|
||||||
if(isCorprusEffect(otherEffect))
|
if(isCorprusEffect(otherEffect))
|
||||||
applyMagicEffect(target, caster, spellParams, otherEffect, invalid, receivedMagicDamage);
|
applyMagicEffect(target, caster, spellParams, otherEffect, invalid, receivedMagicDamage, recalculateMagicka);
|
||||||
}
|
}
|
||||||
if(target == getPlayer())
|
if(target == getPlayer())
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}");
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}");
|
||||||
return false;
|
return MagicApplicationResult::APPLIED;
|
||||||
}
|
}
|
||||||
else if(effect.mEffectId == ESM::MagicEffect::Levitate && !world->isLevitationEnabled())
|
else if(effect.mEffectId == ESM::MagicEffect::Levitate && !world->isLevitationEnabled())
|
||||||
{
|
{
|
||||||
if(target == getPlayer())
|
if(target == getPlayer())
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}");
|
MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}");
|
||||||
return true;
|
return MagicApplicationResult::REMOVED;
|
||||||
}
|
}
|
||||||
const auto* magicEffect = world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId);
|
const auto* magicEffect = world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId);
|
||||||
if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
|
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)
|
if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce)
|
||||||
{
|
{
|
||||||
effect.mTimeLeft -= dt;
|
effect.mTimeLeft -= dt;
|
||||||
return false;
|
return MagicApplicationResult::APPLIED;
|
||||||
}
|
}
|
||||||
else if(!dt)
|
else if(!dt)
|
||||||
return false;
|
return MagicApplicationResult::APPLIED;
|
||||||
}
|
}
|
||||||
if(effect.mEffectId == ESM::MagicEffect::Lock)
|
if(effect.mEffectId == ESM::MagicEffect::Lock)
|
||||||
{
|
{
|
||||||
|
@ -770,28 +863,19 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects();
|
auto& stats = target.getClass().getCreatureStats(target);
|
||||||
if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & (ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Ignore_Resistances)))
|
auto& magnitudes = stats.getMagicEffects();
|
||||||
|
if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
|
||||||
{
|
{
|
||||||
const ESM::Spell* spell = nullptr;
|
MagicApplicationResult result = applyProtections(target, caster, spellParams, effect, magicEffect);
|
||||||
if(spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
|
if(result != MagicApplicationResult::APPLIED)
|
||||||
spell = world->getStore().get<ESM::Spell>().search(spellParams.getId());
|
return result;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
float oldMagnitude = 0.f;
|
float oldMagnitude = 0.f;
|
||||||
if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
|
if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
|
||||||
oldMagnitude = effect.mMagnitude;
|
oldMagnitude = effect.mMagnitude;
|
||||||
|
else if(spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
|
||||||
|
playEffects(target, *magicEffect);
|
||||||
float magnitude = roll(effect);
|
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
|
//Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here
|
||||||
effect.mMagnitude = magnitude;
|
effect.mMagnitude = magnitude;
|
||||||
|
@ -809,13 +893,13 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
|
||||||
effect.mMagnitude = oldMagnitude;
|
effect.mMagnitude = oldMagnitude;
|
||||||
effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
|
effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
|
||||||
effect.mTimeLeft -= dt;
|
effect.mTimeLeft -= dt;
|
||||||
return false;
|
return MagicApplicationResult::APPLIED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(effect.mEffectId == ESM::MagicEffect::Corprus)
|
if(effect.mEffectId == ESM::MagicEffect::Corprus)
|
||||||
spellParams.worsen();
|
spellParams.worsen();
|
||||||
else
|
else
|
||||||
applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage);
|
applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage, recalculateMagicka);
|
||||||
effect.mMagnitude = magnitude;
|
effect.mMagnitude = magnitude;
|
||||||
magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(effect.mMagnitude - oldMagnitude));
|
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;
|
effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
|
||||||
if (receivedMagicDamage && target == getPlayer())
|
if (receivedMagicDamage && target == getPlayer())
|
||||||
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
|
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)
|
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);
|
fortifySkill(target, effect, -effect.mMagnitude);
|
||||||
break;
|
break;
|
||||||
case ESM::MagicEffect::FortifyMaximumMagicka:
|
case ESM::MagicEffect::FortifyMaximumMagicka:
|
||||||
target.getClass().getCreatureStats(target).setNeedRecalcDynamicStats(true);
|
target.getClass().getCreatureStats(target).recalculateMagicka();
|
||||||
break;
|
break;
|
||||||
case ESM::MagicEffect::AbsorbAttribute:
|
case ESM::MagicEffect::AbsorbAttribute:
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,8 +10,13 @@
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
enum class MagicApplicationResult
|
||||||
|
{
|
||||||
|
APPLIED, REMOVED, REFLECTED
|
||||||
|
};
|
||||||
|
|
||||||
// Applies a tick of a single effect. Returns true if the effect should be removed immediately
|
// 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
|
// Undoes permanent effects created by ESM::MagicEffect::AppliedOnce
|
||||||
void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spell, const ESM::ActiveEffect& effect);
|
void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spell, const ESM::ActiveEffect& effect);
|
||||||
|
|
|
@ -162,7 +162,10 @@ namespace MWMechanics
|
||||||
float castChance = baseChance + castBonus;
|
float castChance = baseChance + castBonus;
|
||||||
castChance *= stats.getFatigueTerm();
|
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)
|
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.
|
// Take hit chance in account, but do not allow rating become negative.
|
||||||
float chance = getHitChance(actor, enemy, value) / 100.f;
|
rating *= std::clamp(getHitChance(actor, enemy, value) / 100.f, 0.01f, 1.f);
|
||||||
rating *= std::min(1.f, std::max(0.01f, chance));
|
|
||||||
|
|
||||||
if (weapclass != ESM::WeaponType::Ammo)
|
if (weapclass != ESM::WeaponType::Ammo)
|
||||||
rating *= weapon->mData.mSpeed;
|
rating *= weapon->mData.mSpeed;
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include "collisiontype.hpp"
|
#include "collisiontype.hpp"
|
||||||
#include "mtphysics.hpp"
|
#include "mtphysics.hpp"
|
||||||
|
#include "trace.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
@ -21,8 +22,8 @@ namespace MWPhysics
|
||||||
|
|
||||||
Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk)
|
Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk)
|
||||||
: mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false)
|
: mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false)
|
||||||
, mMeshTranslation(shape->mCollisionBox.center), mOriginalHalfExtents(shape->mCollisionBox.extents)
|
, mMeshTranslation(shape->mCollisionBox.mCenter), mOriginalHalfExtents(shape->mCollisionBox.mExtents)
|
||||||
, mVelocity(0,0,0), mStuckFrames(0), mLastStuckPosition{0, 0, 0}
|
, mStuckFrames(0), mLastStuckPosition{0, 0, 0}
|
||||||
, mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
|
, mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
|
||||||
, mInternalCollisionMode(true)
|
, mInternalCollisionMode(true)
|
||||||
, mExternalCollisionMode(true)
|
, mExternalCollisionMode(true)
|
||||||
|
@ -123,7 +124,6 @@ void Actor::updatePosition()
|
||||||
mSimulationPosition = worldPosition;
|
mSimulationPosition = worldPosition;
|
||||||
mPositionOffset = osg::Vec3f();
|
mPositionOffset = osg::Vec3f();
|
||||||
mStandingOnPtr = nullptr;
|
mStandingOnPtr = nullptr;
|
||||||
mSkipCollisions = true;
|
|
||||||
mSkipSimulation = true;
|
mSkipSimulation = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,11 +133,6 @@ void Actor::setSimulationPosition(const osg::Vec3f& position)
|
||||||
mSimulationPosition = position;
|
mSimulationPosition = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Vec3f Actor::getSimulationPosition() const
|
|
||||||
{
|
|
||||||
return mSimulationPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
osg::Vec3f Actor::getScaledMeshTranslation() const
|
osg::Vec3f Actor::getScaledMeshTranslation() const
|
||||||
{
|
{
|
||||||
return mRotation * osg::componentMultiply(mMeshTranslation, mScale);
|
return mRotation * osg::componentMultiply(mMeshTranslation, mScale);
|
||||||
|
@ -167,17 +162,19 @@ bool Actor::setPosition(const osg::Vec3f& position)
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(mPositionMutex);
|
std::scoped_lock lock(mPositionMutex);
|
||||||
applyOffsetChange();
|
applyOffsetChange();
|
||||||
bool hasChanged = mPosition != position || mWorldPositionChanged;
|
bool hasChanged = (mPosition != position && !mSkipSimulation) || mWorldPositionChanged;
|
||||||
mPreviousPosition = mPosition;
|
if (!mSkipSimulation)
|
||||||
mPosition = position;
|
{
|
||||||
|
mPreviousPosition = mPosition;
|
||||||
|
mPosition = position;
|
||||||
|
}
|
||||||
return hasChanged;
|
return hasChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actor::adjustPosition(const osg::Vec3f& offset, bool ignoreCollisions)
|
void Actor::adjustPosition(const osg::Vec3f& offset)
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(mPositionMutex);
|
std::scoped_lock lock(mPositionMutex);
|
||||||
mPositionOffset += offset;
|
mPositionOffset += offset;
|
||||||
mSkipCollisions = mSkipCollisions || ignoreCollisions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actor::applyOffsetChange()
|
void Actor::applyOffsetChange()
|
||||||
|
@ -191,16 +188,6 @@ void Actor::applyOffsetChange()
|
||||||
mWorldPositionChanged = true;
|
mWorldPositionChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Vec3f Actor::getPosition() const
|
|
||||||
{
|
|
||||||
return mPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
osg::Vec3f Actor::getPreviousPosition() const
|
|
||||||
{
|
|
||||||
return mPreviousPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Actor::setRotation(osg::Quat quat)
|
void Actor::setRotation(osg::Quat quat)
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(mPositionMutex);
|
std::scoped_lock lock(mPositionMutex);
|
||||||
|
@ -288,19 +275,15 @@ void Actor::setStandingOnPtr(const MWWorld::Ptr& ptr)
|
||||||
mStandingOnPtr = ptr;
|
mStandingOnPtr = ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Actor::skipCollisions()
|
bool Actor::canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const
|
||||||
{
|
{
|
||||||
return std::exchange(mSkipCollisions, false);
|
const float halfZ = getHalfExtents().z();
|
||||||
}
|
const osg::Vec3f actorPosition = getPosition();
|
||||||
|
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
|
||||||
void Actor::setVelocity(osg::Vec3f velocity)
|
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
|
||||||
{
|
MWPhysics::ActorTracer tracer;
|
||||||
mVelocity = velocity;
|
tracer.doTrace(getCollisionObject(), startingPosition, destinationPosition, world);
|
||||||
}
|
return (tracer.mFraction >= 1.0f);
|
||||||
|
|
||||||
osg::Vec3f Actor::velocity()
|
|
||||||
{
|
|
||||||
return std::exchange(mVelocity, osg::Vec3f());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
class btCollisionShape;
|
class btCollisionShape;
|
||||||
class btCollisionObject;
|
class btCollisionObject;
|
||||||
|
class btCollisionWorld;
|
||||||
class btConvexShape;
|
class btConvexShape;
|
||||||
|
|
||||||
namespace Resource
|
namespace Resource
|
||||||
|
@ -59,7 +60,6 @@ namespace MWPhysics
|
||||||
* to account for e.g. scripted movements
|
* to account for e.g. scripted movements
|
||||||
*/
|
*/
|
||||||
void setSimulationPosition(const osg::Vec3f& position);
|
void setSimulationPosition(const osg::Vec3f& position);
|
||||||
osg::Vec3f getSimulationPosition() const;
|
|
||||||
|
|
||||||
void updateCollisionObjectPosition();
|
void updateCollisionObjectPosition();
|
||||||
|
|
||||||
|
@ -89,15 +89,11 @@ namespace MWPhysics
|
||||||
void updatePosition();
|
void updatePosition();
|
||||||
|
|
||||||
// register a position offset that will be applied during simulation.
|
// 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
|
// apply position offset. Can't be called during simulation
|
||||||
void applyOffsetChange();
|
void applyOffsetChange();
|
||||||
|
|
||||||
osg::Vec3f getPosition() const;
|
|
||||||
|
|
||||||
osg::Vec3f getPreviousPosition() const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the half extents of the collision body (scaled according to rendering scale)
|
* 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,
|
* @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;
|
mLastStuckPosition = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skipCollisions();
|
bool canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const;
|
||||||
|
|
||||||
void setVelocity(osg::Vec3f velocity);
|
|
||||||
osg::Vec3f velocity();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MWWorld::Ptr mStandingOnPtr;
|
MWWorld::Ptr mStandingOnPtr;
|
||||||
|
@ -190,13 +183,8 @@ namespace MWPhysics
|
||||||
osg::Quat mRotation;
|
osg::Quat mRotation;
|
||||||
|
|
||||||
osg::Vec3f mScale;
|
osg::Vec3f mScale;
|
||||||
osg::Vec3f mSimulationPosition;
|
|
||||||
osg::Vec3f mPosition;
|
|
||||||
osg::Vec3f mPreviousPosition;
|
|
||||||
osg::Vec3f mPositionOffset;
|
osg::Vec3f mPositionOffset;
|
||||||
osg::Vec3f mVelocity;
|
|
||||||
bool mWorldPositionChanged;
|
bool mWorldPositionChanged;
|
||||||
bool mSkipCollisions;
|
|
||||||
bool mSkipSimulation;
|
bool mSkipSimulation;
|
||||||
mutable std::mutex mPositionMutex;
|
mutable std::mutex mPositionMutex;
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,9 @@ namespace MWPhysics
|
||||||
const btVector3& position, const btScalar radius)
|
const btVector3& position, const btScalar radius)
|
||||||
{
|
{
|
||||||
const btVector3 nearest(
|
const btVector3 nearest(
|
||||||
std::max(aabbMin.x(), std::min(aabbMax.x(), position.x())),
|
std::clamp(position.x(), aabbMin.x(), aabbMax.x()),
|
||||||
std::max(aabbMin.y(), std::min(aabbMax.y(), position.y())),
|
std::clamp(position.y(), aabbMin.y(), aabbMax.y()),
|
||||||
std::max(aabbMin.z(), std::min(aabbMax.z(), position.z()))
|
std::clamp(position.z(), aabbMin.z(), aabbMax.z())
|
||||||
);
|
);
|
||||||
return nearest.distance(position) < radius;
|
return nearest.distance(position) < radius;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||||
#include <BulletCollision/CollisionShapes/btCollisionShape.h>
|
#include <BulletCollision/CollisionShapes/btCollisionShape.h>
|
||||||
|
#include <BulletCollision/CollisionShapes/btConvexShape.h>
|
||||||
|
|
||||||
#include <components/esm/loadgmst.hpp>
|
#include <components/esm/loadgmst.hpp>
|
||||||
#include <components/misc/convert.hpp>
|
#include <components/misc/convert.hpp>
|
||||||
|
@ -19,6 +20,8 @@
|
||||||
#include "constants.hpp"
|
#include "constants.hpp"
|
||||||
#include "contacttestwrapper.h"
|
#include "contacttestwrapper.h"
|
||||||
#include "physicssystem.hpp"
|
#include "physicssystem.hpp"
|
||||||
|
#include "projectile.hpp"
|
||||||
|
#include "projectileconvexcallback.hpp"
|
||||||
#include "stepper.hpp"
|
#include "stepper.hpp"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
|
|
||||||
|
@ -116,7 +119,7 @@ namespace MWPhysics
|
||||||
}
|
}
|
||||||
|
|
||||||
void MovementSolver::move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld,
|
void MovementSolver::move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld,
|
||||||
WorldFrameData& worldData)
|
const WorldFrameData& worldData)
|
||||||
{
|
{
|
||||||
// Reset per-frame data
|
// Reset per-frame data
|
||||||
actor.mWalkingOnWater = false;
|
actor.mWalkingOnWater = false;
|
||||||
|
@ -203,7 +206,7 @@ namespace MWPhysics
|
||||||
if((newPosition - nextpos).length2() > 0.0001)
|
if((newPosition - nextpos).length2() > 0.0001)
|
||||||
{
|
{
|
||||||
// trace to where character would go if there were no obstructions
|
// 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
|
// check for obstructions
|
||||||
if(tracer.mFraction >= 1.0f)
|
if(tracer.mFraction >= 1.0f)
|
||||||
|
@ -338,7 +341,7 @@ namespace MWPhysics
|
||||||
osg::Vec3f from = newPosition;
|
osg::Vec3f from = newPosition;
|
||||||
auto dropDistance = 2*sGroundOffset + (actor.mIsOnGround ? sStepSizeDown : 0);
|
auto dropDistance = 2*sGroundOffset + (actor.mIsOnGround ? sStepSizeDown : 0);
|
||||||
osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance);
|
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(tracer.mFraction < 1.0f)
|
||||||
{
|
{
|
||||||
if (!isActor(tracer.mHitObject))
|
if (!isActor(tracer.mHitObject))
|
||||||
|
@ -398,6 +401,29 @@ namespace MWPhysics
|
||||||
actor.mPosition.z() -= actor.mHalfExtentsZ; // vanilla-accurate
|
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)
|
btVector3 addMarginToDelta(btVector3 delta)
|
||||||
{
|
{
|
||||||
if(delta.length2() == 0.0)
|
if(delta.length2() == 0.0)
|
||||||
|
|
|
@ -37,13 +37,15 @@ namespace MWPhysics
|
||||||
|
|
||||||
class Actor;
|
class Actor;
|
||||||
struct ActorFrameData;
|
struct ActorFrameData;
|
||||||
|
struct ProjectileFrameData;
|
||||||
struct WorldFrameData;
|
struct WorldFrameData;
|
||||||
|
|
||||||
class MovementSolver
|
class MovementSolver
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight);
|
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);
|
static void unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,10 +105,134 @@ namespace
|
||||||
return actorData.mPosition.z() < actorData.mSwimLevel;
|
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);
|
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
|
namespace Config
|
||||||
|
@ -235,13 +359,12 @@ namespace MWPhysics
|
||||||
return std::make_tuple(numSteps, actualDelta);
|
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.
|
// This function run in the main thread.
|
||||||
// While the mSimulationMutex is held, background physics threads can't run.
|
// While the mSimulationMutex is held, background physics threads can't run.
|
||||||
|
|
||||||
MaybeExclusiveLock lock(mSimulationMutex, mNumThreads);
|
MaybeExclusiveLock lock(mSimulationMutex, mNumThreads);
|
||||||
assert(actors.size() == actorsData.size());
|
|
||||||
|
|
||||||
double timeStart = mTimer->tick();
|
double timeStart = mTimer->tick();
|
||||||
|
|
||||||
|
@ -259,19 +382,19 @@ namespace MWPhysics
|
||||||
timeAccum -= numSteps*newDelta;
|
timeAccum -= numSteps*newDelta;
|
||||||
|
|
||||||
// init
|
// 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;
|
mPrevStepCount = numSteps;
|
||||||
mRemainingSteps = numSteps;
|
mRemainingSteps = numSteps;
|
||||||
mTimeAccum = timeAccum;
|
mTimeAccum = timeAccum;
|
||||||
mPhysicsDt = newDelta;
|
mPhysicsDt = newDelta;
|
||||||
mActors = std::move(actors);
|
mSimulations = std::move(simulations);
|
||||||
mActorsFrameData = std::move(actorsData);
|
|
||||||
mAdvanceSimulation = (mRemainingSteps != 0);
|
mAdvanceSimulation = (mRemainingSteps != 0);
|
||||||
mNewFrame = true;
|
mNewFrame = true;
|
||||||
mNumJobs = mActorsFrameData.size();
|
mNumJobs = mSimulations.size();
|
||||||
mNextLOS.store(0, std::memory_order_relaxed);
|
mNextLOS.store(0, std::memory_order_relaxed);
|
||||||
mNextJob.store(0, std::memory_order_release);
|
mNextJob.store(0, std::memory_order_release);
|
||||||
|
|
||||||
|
@ -301,8 +424,7 @@ namespace MWPhysics
|
||||||
MaybeExclusiveLock lock(mSimulationMutex, mNumThreads);
|
MaybeExclusiveLock lock(mSimulationMutex, mNumThreads);
|
||||||
mBudget.reset(mDefaultPhysicsDt);
|
mBudget.reset(mDefaultPhysicsDt);
|
||||||
mAsyncBudget.reset(0.0f);
|
mAsyncBudget.reset(0.0f);
|
||||||
mActors.clear();
|
mSimulations.clear();
|
||||||
mActorsFrameData.clear();
|
|
||||||
for (const auto& [_, actor] : actors)
|
for (const auto& [_, actor] : actors)
|
||||||
{
|
{
|
||||||
actor->updatePosition();
|
actor->updatePosition();
|
||||||
|
@ -448,7 +570,7 @@ namespace MWPhysics
|
||||||
}
|
}
|
||||||
else if (const auto projectile = std::dynamic_pointer_cast<Projectile>(ptr))
|
else if (const auto projectile = std::dynamic_pointer_cast<Projectile>(ptr))
|
||||||
{
|
{
|
||||||
projectile->commitPositionChange();
|
projectile->updateCollisionObjectPosition();
|
||||||
mCollisionWorld->updateSingleAabb(projectile->getCollisionObject());
|
mCollisionWorld->updateSingleAabb(projectile->getCollisionObject());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -467,47 +589,11 @@ namespace MWPhysics
|
||||||
|
|
||||||
void PhysicsTaskScheduler::updateActorsPositions()
|
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);
|
||||||
{
|
std::visit(vis, sim);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -532,10 +618,11 @@ namespace MWPhysics
|
||||||
{
|
{
|
||||||
mPreStepBarrier->wait([this] { afterPreStep(); });
|
mPreStepBarrier->wait([this] { afterPreStep(); });
|
||||||
int job = 0;
|
int job = 0;
|
||||||
|
const Visitors::Move vis{mPhysicsDt, mCollisionWorld, *mWorldFrameData};
|
||||||
while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
|
while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
|
||||||
{
|
{
|
||||||
MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads);
|
MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads);
|
||||||
MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData);
|
std::visit(vis, mSimulations[job]);
|
||||||
}
|
}
|
||||||
|
|
||||||
mPostStepBarrier->wait([this] { afterPostStep(); });
|
mPostStepBarrier->wait([this] { afterPostStep(); });
|
||||||
|
@ -577,7 +664,7 @@ namespace MWPhysics
|
||||||
void PhysicsTaskScheduler::releaseSharedStates()
|
void PhysicsTaskScheduler::releaseSharedStates()
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(mSimulationMutex, mUpdateAabbMutex);
|
std::scoped_lock lock(mSimulationMutex, mUpdateAabbMutex);
|
||||||
mActors.clear();
|
mSimulations.clear();
|
||||||
mUpdateAabb.clear();
|
mUpdateAabb.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,10 +673,11 @@ namespace MWPhysics
|
||||||
updateAabbs();
|
updateAabbs();
|
||||||
if (!mRemainingSteps)
|
if (!mRemainingSteps)
|
||||||
return;
|
return;
|
||||||
for (size_t i = 0; i < mActors.size(); ++i)
|
const Visitors::PreStep vis{mCollisionWorld};
|
||||||
|
for (auto& sim : mSimulations)
|
||||||
{
|
{
|
||||||
MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads);
|
MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads);
|
||||||
MovementSolver::unstuck(mActorsFrameData[i], mCollisionWorld);
|
std::visit(vis, sim);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -618,7 +706,8 @@ namespace MWPhysics
|
||||||
|
|
||||||
void PhysicsTaskScheduler::syncWithMainThread()
|
void PhysicsTaskScheduler::syncWithMainThread()
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < mActors.size(); ++i)
|
const Visitors::Sync vis{mAdvanceSimulation, mTimeAccum, mPhysicsDt, this};
|
||||||
updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt);
|
for (auto& sim : mSimulations)
|
||||||
|
std::visit(vis, sim);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ namespace MWPhysics
|
||||||
/// @param timeAccum accumulated time from previous run to interpolate movements
|
/// @param timeAccum accumulated time from previous run to interpolate movements
|
||||||
/// @param actorsData per actor data needed to compute new positions
|
/// @param actorsData per actor data needed to compute new positions
|
||||||
/// @return new position of each actor
|
/// @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);
|
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);
|
bool getLineOfSight(const std::shared_ptr<Actor>& actor1, const std::shared_ptr<Actor>& actor2);
|
||||||
void debugDraw();
|
void debugDraw();
|
||||||
void* getUserPointer(const btCollisionObject* object) const;
|
void* getUserPointer(const btCollisionObject* object) const;
|
||||||
|
|
||||||
void releaseSharedStates(); // destroy all objects whose destructor can't be safely called from ~PhysicsTaskScheduler()
|
void releaseSharedStates(); // destroy all objects whose destructor can't be safely called from ~PhysicsTaskScheduler()
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void doSimulation();
|
void doSimulation();
|
||||||
void worker();
|
void worker();
|
||||||
void updateActorsPositions();
|
void updateActorsPositions();
|
||||||
void updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const;
|
|
||||||
bool hasLineOfSight(const Actor* actor1, const Actor* actor2);
|
bool hasLineOfSight(const Actor* actor1, const Actor* actor2);
|
||||||
void refreshLOSCache();
|
void refreshLOSCache();
|
||||||
void updateAabbs();
|
void updateAabbs();
|
||||||
|
@ -77,8 +76,7 @@ namespace MWPhysics
|
||||||
void syncWithMainThread();
|
void syncWithMainThread();
|
||||||
|
|
||||||
std::unique_ptr<WorldFrameData> mWorldFrameData;
|
std::unique_ptr<WorldFrameData> mWorldFrameData;
|
||||||
std::vector<std::shared_ptr<Actor>> mActors;
|
std::vector<Simulation> mSimulations;
|
||||||
std::vector<ActorFrameData> mActorsFrameData;
|
|
||||||
std::unordered_set<const btCollisionObject*> mCollisionObjects;
|
std::unordered_set<const btCollisionObject*> mCollisionObjects;
|
||||||
float mDefaultPhysicsDt;
|
float mDefaultPhysicsDt;
|
||||||
float mPhysicsDt;
|
float mPhysicsDt;
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace MWPhysics
|
||||||
, mTaskScheduler(scheduler)
|
, mTaskScheduler(scheduler)
|
||||||
{
|
{
|
||||||
mPtr = ptr;
|
mPtr = ptr;
|
||||||
mCollisionObject = BulletHelpers::makeCollisionObject(mShapeInstance->getCollisionShape(),
|
mCollisionObject = BulletHelpers::makeCollisionObject(mShapeInstance->mCollisionShape.get(),
|
||||||
Misc::Convert::toBullet(mPosition), Misc::Convert::toBullet(rotation));
|
Misc::Convert::toBullet(mPosition), Misc::Convert::toBullet(rotation));
|
||||||
mCollisionObject->setUserPointer(this);
|
mCollisionObject->setUserPointer(this);
|
||||||
mShapeInstance->setLocalScaling(mScale);
|
mShapeInstance->setLocalScaling(mScale);
|
||||||
|
@ -109,9 +109,9 @@ namespace MWPhysics
|
||||||
if (mShapeInstance->mAnimatedShapes.empty())
|
if (mShapeInstance->mAnimatedShapes.empty())
|
||||||
return false;
|
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)
|
for (const auto& [recIndex, shapeIndex] : mShapeInstance->mAnimatedShapes)
|
||||||
{
|
{
|
||||||
auto nodePathFound = mRecIndexToNodePath.find(recIndex);
|
auto nodePathFound = mRecIndexToNodePath.find(recIndex);
|
||||||
|
|
|
@ -60,19 +60,6 @@
|
||||||
|
|
||||||
namespace
|
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)
|
void handleJump(const MWWorld::Ptr &ptr)
|
||||||
{
|
{
|
||||||
if (!ptr.getClass().isActor())
|
if (!ptr.getClass().isActor())
|
||||||
|
@ -370,6 +357,8 @@ namespace MWPhysics
|
||||||
|
|
||||||
bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const
|
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 it1 = mActors.find(actor1.mRef);
|
||||||
const auto it2 = mActors.find(actor2.mRef);
|
const auto it2 = mActors.find(actor2.mRef);
|
||||||
if (it1 == mActors.end() || it2 == mActors.end())
|
if (it1 == mActors.end() || it2 == mActors.end())
|
||||||
|
@ -386,7 +375,8 @@ namespace MWPhysics
|
||||||
|
|
||||||
bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel)
|
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
|
osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const
|
||||||
|
@ -491,7 +481,7 @@ namespace MWPhysics
|
||||||
if (ptr.mRef->mData.mPhysicsPostponed)
|
if (ptr.mRef->mData.mPhysicsPostponed)
|
||||||
return;
|
return;
|
||||||
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
|
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
|
||||||
if (!shapeInstance || !shapeInstance->getCollisionShape())
|
if (!shapeInstance || !shapeInstance->mCollisionShape)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
assert(!getObject(ptr));
|
assert(!getObject(ptr));
|
||||||
|
@ -526,10 +516,10 @@ namespace MWPhysics
|
||||||
|
|
||||||
void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
|
void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
|
||||||
{
|
{
|
||||||
if (auto found = mObjects.find(old.mRef); found != mObjects.end())
|
if (auto foundObject = mObjects.find(old.mRef); foundObject != mObjects.end())
|
||||||
found->second->updatePtr(updated);
|
foundObject->second->updatePtr(updated);
|
||||||
else if (auto found = mActors.find(old.mRef); found != mActors.end())
|
else if (auto foundActor = mActors.find(old.mRef); foundActor != mActors.end())
|
||||||
found->second->updatePtr(updated);
|
foundActor->second->updatePtr(updated);
|
||||||
|
|
||||||
for (auto& [_, actor] : mActors)
|
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)
|
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate)
|
||||||
{
|
{
|
||||||
if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end())
|
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);
|
osg::ref_ptr<const Resource::BulletShape> shape = mShapeManager->getShape(mesh);
|
||||||
|
|
||||||
// Try to get shape from basic model as fallback for creatures
|
// 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);
|
const std::string fallbackModel = ptr.getClass().getModel(ptr);
|
||||||
if (fallbackModel != mesh)
|
if (fallbackModel != mesh)
|
||||||
|
@ -680,7 +643,7 @@ namespace MWPhysics
|
||||||
{
|
{
|
||||||
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
|
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
|
||||||
assert(shapeInstance);
|
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++;
|
mProjectileId++;
|
||||||
|
|
||||||
|
@ -727,11 +690,10 @@ namespace MWPhysics
|
||||||
actor->setVelocity(osg::Vec3f());
|
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;
|
std::vector<Simulation> simulations;
|
||||||
framedata.first.reserve(mActors.size());
|
simulations.reserve(mActors.size() + mProjectiles.size());
|
||||||
framedata.second.reserve(mActors.size());
|
|
||||||
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||||
for (const auto& [ref, physicActor] : mActors)
|
for (const auto& [ref, physicActor] : mActors)
|
||||||
{
|
{
|
||||||
|
@ -756,18 +718,23 @@ namespace MWPhysics
|
||||||
physicActor->setCanWaterWalk(waterCollision);
|
physicActor->setCanWaterWalk(waterCollision);
|
||||||
|
|
||||||
// Slow fall reduces fall speed by a factor of (effect magnitude / 200)
|
// 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 godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState();
|
||||||
const bool inert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0);
|
const bool inert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0);
|
||||||
|
|
||||||
framedata.first.emplace_back(physicActor);
|
simulations.emplace_back(ActorSimulation{physicActor, ActorFrameData{*physicActor, inert, waterCollision, slowFall, waterlevel}});
|
||||||
framedata.second.emplace_back(*physicActor, inert, waterCollision, slowFall, waterlevel);
|
|
||||||
|
|
||||||
// if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly.
|
// if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly.
|
||||||
if (willSimulate)
|
if (willSimulate)
|
||||||
handleJump(ptr);
|
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)
|
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);
|
mTaskScheduler->resetSimulation(mActors);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto [actors, framedata] = prepareFrameData(mTimeAccum >= mPhysicsDt);
|
auto simulations = prepareSimulation(mTimeAccum >= mPhysicsDt);
|
||||||
// modifies mTimeAccum
|
// 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())
|
, mWasOnGround(actor.getOnGround())
|
||||||
, mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr()))
|
, mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr()))
|
||||||
, mWaterCollision(waterCollision)
|
, 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()
|
WorldFrameData::WorldFrameData()
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
#include <osg/Quat>
|
#include <osg/Quat>
|
||||||
#include <osg/BoundingBox>
|
#include <osg/BoundingBox>
|
||||||
|
@ -75,7 +76,6 @@ namespace MWPhysics
|
||||||
struct ActorFrameData
|
struct ActorFrameData
|
||||||
{
|
{
|
||||||
ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel);
|
ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel);
|
||||||
void updatePosition(Actor& actor, btCollisionWorld* world);
|
|
||||||
osg::Vec3f mPosition;
|
osg::Vec3f mPosition;
|
||||||
osg::Vec3f mInertia;
|
osg::Vec3f mInertia;
|
||||||
const btCollisionObject* mStandingOn;
|
const btCollisionObject* mStandingOn;
|
||||||
|
@ -100,6 +100,16 @@ namespace MWPhysics
|
||||||
const bool mSkipCollisionDetection;
|
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
|
struct WorldFrameData
|
||||||
{
|
{
|
||||||
WorldFrameData();
|
WorldFrameData();
|
||||||
|
@ -107,6 +117,10 @@ namespace MWPhysics
|
||||||
osg::Vec3f mStormDirection;
|
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
|
class PhysicsSystem : public RayCastingInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -124,7 +138,6 @@ namespace MWPhysics
|
||||||
|
|
||||||
int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius);
|
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 setCaster(int projectileId, const MWWorld::Ptr& caster);
|
||||||
void updateProjectile(const int projectileId, const osg::Vec3f &position) const;
|
|
||||||
void removeProjectile(const int projectileId);
|
void removeProjectile(const int projectileId);
|
||||||
|
|
||||||
void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
|
void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
|
||||||
|
@ -253,7 +266,7 @@ namespace MWPhysics
|
||||||
|
|
||||||
void updateWater();
|
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<btBroadphaseInterface> mBroadphase;
|
||||||
std::unique_ptr<btDefaultCollisionConfiguration> mCollisionConfiguration;
|
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->setCollisionShape(mShape.get());
|
||||||
mCollisionObject->setUserPointer(this);
|
mCollisionObject->setUserPointer(this);
|
||||||
|
|
||||||
setPosition(position);
|
mPosition = position;
|
||||||
|
mPreviousPosition = position;
|
||||||
setCaster(caster);
|
setCaster(caster);
|
||||||
|
|
||||||
const int collisionMask = CollisionType_World | CollisionType_HeightMap |
|
const int collisionMask = CollisionType_World | CollisionType_HeightMap |
|
||||||
CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile;
|
CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile;
|
||||||
mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask);
|
mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask);
|
||||||
|
|
||||||
commitPositionChange();
|
updateCollisionObjectPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
Projectile::~Projectile()
|
Projectile::~Projectile()
|
||||||
|
@ -48,29 +49,12 @@ Projectile::~Projectile()
|
||||||
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Projectile::commitPositionChange()
|
void Projectile::updateCollisionObjectPosition()
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(mMutex);
|
std::scoped_lock lock(mMutex);
|
||||||
if (mTransformUpdatePending)
|
auto& trans = mCollisionObject->getWorldTransform();
|
||||||
{
|
trans.setOrigin(Misc::Convert::toBullet(mPosition));
|
||||||
auto& trans = mCollisionObject->getWorldTransform();
|
mCollisionObject->setWorldTransform(trans);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Projectile::hit(const btCollisionObject* target, btVector3 pos, btVector3 normal)
|
void Projectile::hit(const btCollisionObject* target, btVector3 pos, btVector3 normal)
|
||||||
|
|
|
@ -36,10 +36,7 @@ namespace MWPhysics
|
||||||
|
|
||||||
btConvexShape* getConvexShape() const { return mConvexShape; }
|
btConvexShape* getConvexShape() const { return mConvexShape; }
|
||||||
|
|
||||||
void commitPositionChange();
|
void updateCollisionObjectPosition();
|
||||||
|
|
||||||
void setPosition(const osg::Vec3f& position);
|
|
||||||
osg::Vec3f getPosition() const;
|
|
||||||
|
|
||||||
bool isActive() const
|
bool isActive() const
|
||||||
{
|
{
|
||||||
|
@ -80,13 +77,11 @@ namespace MWPhysics
|
||||||
std::unique_ptr<btCollisionShape> mShape;
|
std::unique_ptr<btCollisionShape> mShape;
|
||||||
btConvexShape* mConvexShape;
|
btConvexShape* mConvexShape;
|
||||||
|
|
||||||
bool mTransformUpdatePending;
|
|
||||||
bool mHitWater;
|
bool mHitWater;
|
||||||
std::atomic<bool> mActive;
|
std::atomic<bool> mActive;
|
||||||
MWWorld::Ptr mCaster;
|
MWWorld::Ptr mCaster;
|
||||||
const btCollisionObject* mCasterColObj;
|
const btCollisionObject* mCasterColObj;
|
||||||
const btCollisionObject* mHitTarget;
|
const btCollisionObject* mHitTarget;
|
||||||
osg::Vec3f mPosition;
|
|
||||||
btVector3 mHitPosition;
|
btVector3 mHitPosition;
|
||||||
btVector3 mHitNormal;
|
btVector3 mHitNormal;
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,49 @@ namespace MWPhysics
|
||||||
return mCollisionObject.get();
|
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:
|
protected:
|
||||||
MWWorld::Ptr mPtr;
|
MWWorld::Ptr mPtr;
|
||||||
std::unique_ptr<btCollisionObject> mCollisionObject;
|
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.
|
// 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.
|
// 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;
|
float upDistance = 0;
|
||||||
if(!mUpStepper.mHitObject)
|
if(!mUpStepper.mHitObject)
|
||||||
|
@ -117,7 +117,7 @@ namespace MWPhysics
|
||||||
downStepSize = upDistance;
|
downStepSize = upDistance;
|
||||||
else
|
else
|
||||||
downStepSize = moveDistance + upDistance + sStepSizeDown;
|
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
|
// 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
|
// 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
|
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();
|
const btTransform &trans = actor->getWorldTransform();
|
||||||
btTransform from(trans);
|
btTransform transFrom(trans);
|
||||||
btTransform to(trans);
|
btTransform transTo(trans);
|
||||||
from.setOrigin(btstart);
|
transFrom.setOrigin(from);
|
||||||
to.setOrigin(btend);
|
transTo.setOrigin(to);
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
const btCollisionShape *shape = actor->getCollisionShape();
|
const btCollisionShape *shape = actor->getCollisionShape();
|
||||||
assert(shape->isConvex());
|
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:
|
// Copy the hit data over to our trace results struct:
|
||||||
if(newTraceCallback.hasHit())
|
if(traceCallback.hasHit())
|
||||||
{
|
{
|
||||||
mFraction = newTraceCallback.m_closestHitFraction;
|
mFraction = traceCallback.m_closestHitFraction;
|
||||||
mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld);
|
// 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;
|
mEndPos = (end-start)*mFraction + start;
|
||||||
mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld);
|
mHitPoint = Misc::Convert::toOsg(traceCallback.m_hitPointWorld);
|
||||||
mHitObject = newTraceCallback.m_hitCollisionObject;
|
mHitObject = traceCallback.m_hitCollisionObject;
|
||||||
}
|
}
|
||||||
else
|
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;
|
mEndPos = end;
|
||||||
mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f);
|
mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f);
|
||||||
mFraction = 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)
|
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 auto traceCallback = sweepHelper(actor->getCollisionObject(), Misc::Convert::toBullet(start), Misc::Convert::toBullet(end), world, true);
|
||||||
const btVector3 btend = Misc::Convert::toBullet(end);
|
if(traceCallback.hasHit())
|
||||||
|
|
||||||
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())
|
|
||||||
{
|
{
|
||||||
mFraction = newTraceCallback.m_closestHitFraction;
|
mFraction = traceCallback.m_closestHitFraction;
|
||||||
mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld);
|
mPlaneNormal = Misc::Convert::toOsg(traceCallback.m_hitNormalWorld);
|
||||||
mEndPos = (end-start)*mFraction + start;
|
mEndPos = (end-start)*mFraction + start;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace MWPhysics
|
||||||
|
|
||||||
float mFraction;
|
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);
|
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/resourcesystem.hpp>
|
||||||
#include <components/resource/scenemanager.hpp>
|
#include <components/resource/scenemanager.hpp>
|
||||||
|
|
||||||
|
#include <components/sceneutil/attach.hpp>
|
||||||
#include <components/sceneutil/lightmanager.hpp>
|
#include <components/sceneutil/lightmanager.hpp>
|
||||||
#include <components/sceneutil/lightutil.hpp>
|
#include <components/sceneutil/lightutil.hpp>
|
||||||
#include <components/sceneutil/visitor.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);
|
osg::ref_ptr<osg::Node> instance = mResourceSystem->getSceneManager()->getInstance(model, parent);
|
||||||
|
|
||||||
const NodeMap& nodeMap = getNodeMap();
|
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())
|
if (found == nodeMap.end())
|
||||||
return PartHolderPtr();
|
return PartHolderPtr();
|
||||||
|
|
||||||
|
@ -84,30 +85,58 @@ PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::st
|
||||||
return PartHolderPtr(new PartHolder(instance));
|
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 ESM::Armor *armor = shield.get<ESM::Armor>()->mBase;
|
||||||
const std::vector<ESM::PartReference>& bodyparts = armor->mParts.mParts;
|
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())
|
if (!bodyparts.empty())
|
||||||
{
|
{
|
||||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
|
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)
|
for (const auto& part : bodyparts)
|
||||||
{
|
{
|
||||||
// Assume all creatures use the male mesh.
|
if (part.mPart != ESM::PRT_Shield)
|
||||||
if (part.mPart != ESM::PRT_Shield || part.mMale.empty())
|
|
||||||
continue;
|
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;
|
const ESM::BodyPart *bodypart = partStore.search(bodypartName);
|
||||||
break;
|
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())
|
if (mesh.empty())
|
||||||
return mesh;
|
return mesh;
|
||||||
|
@ -143,7 +172,7 @@ bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const
|
||||||
const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr);
|
const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr);
|
||||||
const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||||
const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
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)
|
if(stats.getDrawState() != MWMechanics::DrawState_Weapon)
|
||||||
return false;
|
return false;
|
||||||
|
@ -201,7 +230,7 @@ void ActorAnimation::updateHolsteredShield(bool showCarriedLeft)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string mesh = getShieldMesh(*shield);
|
std::string mesh = getSheathedShieldMesh(*shield);
|
||||||
if (mesh.empty())
|
if (mesh.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -255,7 +284,7 @@ bool ActorAnimation::useShieldAnimations() const
|
||||||
const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||||
if (weapon != inv.end() && shield != inv.end() &&
|
if (weapon != inv.end() && shield != inv.end() &&
|
||||||
shield->getType() == ESM::Armor::sRecordId &&
|
shield->getType() == ESM::Armor::sRecordId &&
|
||||||
!getShieldMesh(*shield).empty())
|
!getSheathedShieldMesh(*shield).empty())
|
||||||
{
|
{
|
||||||
auto type = weapon->getType();
|
auto type = weapon->getType();
|
||||||
if(type == ESM::Weapon::sRecordId)
|
if(type == ESM::Weapon::sRecordId)
|
||||||
|
|
|
@ -45,7 +45,8 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener
|
||||||
virtual void updateHolsteredWeapon(bool showHolsteredWeapons);
|
virtual void updateHolsteredWeapon(bool showHolsteredWeapons);
|
||||||
virtual void updateHolsteredShield(bool showCarriedLeft);
|
virtual void updateHolsteredShield(bool showCarriedLeft);
|
||||||
virtual void updateQuiver();
|
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 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, bool enchantedGlow, osg::Vec4f* glowColor);
|
||||||
virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename)
|
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);
|
osg::Vec4f stubColor = osg::Vec4f(0,0,0,0);
|
||||||
return attachMesh(model, bonename, false, &stubColor);
|
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 mScabbard;
|
||||||
PartHolderPtr mHolsteredShield;
|
PartHolderPtr mHolsteredShield;
|
||||||
|
|
|
@ -389,11 +389,6 @@ namespace MWRender
|
||||||
mAlpha = alpha;
|
mAlpha = alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLightSource(const osg::ref_ptr<SceneUtil::LightSource>& lightSource)
|
|
||||||
{
|
|
||||||
mLightSource = lightSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void setDefaults(osg::StateSet* stateset) override
|
void setDefaults(osg::StateSet* stateset) override
|
||||||
{
|
{
|
||||||
|
@ -416,13 +411,10 @@ namespace MWRender
|
||||||
{
|
{
|
||||||
osg::Material* material = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
|
osg::Material* material = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
|
||||||
material->setAlpha(osg::Material::FRONT_AND_BACK, mAlpha);
|
material->setAlpha(osg::Material::FRONT_AND_BACK, mAlpha);
|
||||||
if (mLightSource)
|
|
||||||
mLightSource->setActorFade(mAlpha);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
float mAlpha;
|
float mAlpha;
|
||||||
osg::ref_ptr<SceneUtil::LightSource> mLightSource;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Animation::AnimSource
|
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
|
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);
|
osg::Callback* callback = it->second->getAsCallback();
|
||||||
mActiveControllers.emplace_back(node, it->second);
|
node->addUpdateCallback(callback);
|
||||||
|
mActiveControllers.emplace_back(node, callback);
|
||||||
|
|
||||||
if (blendMask == 0 && node == mAccumRoot)
|
if (blendMask == 0 && node == mAccumRoot)
|
||||||
{
|
{
|
||||||
|
@ -1319,10 +1312,10 @@ namespace MWRender
|
||||||
|
|
||||||
cache.insert(std::make_pair(model, created));
|
cache.insert(std::make_pair(model, created));
|
||||||
|
|
||||||
return sceneMgr->createInstance(created);
|
return sceneMgr->getInstance(created);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return sceneMgr->createInstance(found->second);
|
return sceneMgr->getInstance(found->second);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1484,6 +1477,7 @@ namespace MWRender
|
||||||
bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior();
|
bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior();
|
||||||
|
|
||||||
mExtraLightSource = SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior);
|
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)
|
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;
|
parentNode = mInsert;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NodeMap::const_iterator found = getNodeMap().find(Misc::StringUtils::lowerCase(bonename));
|
NodeMap::const_iterator found = getNodeMap().find(bonename);
|
||||||
if (found == getNodeMap().end())
|
if (found == getNodeMap().end())
|
||||||
throw std::runtime_error("Can't find bone " + bonename);
|
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
|
const osg::Node* Animation::getNode(const std::string &name) const
|
||||||
{
|
{
|
||||||
std::string lowerName = Misc::StringUtils::lowerCase(name);
|
NodeMap::const_iterator found = getNodeMap().find(name);
|
||||||
NodeMap::const_iterator found = getNodeMap().find(lowerName);
|
|
||||||
if (found == getNodeMap().end())
|
if (found == getNodeMap().end())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
else
|
else
|
||||||
|
@ -1639,7 +1632,6 @@ namespace MWRender
|
||||||
if (mTransparencyUpdater == nullptr)
|
if (mTransparencyUpdater == nullptr)
|
||||||
{
|
{
|
||||||
mTransparencyUpdater = new TransparencyUpdater(alpha);
|
mTransparencyUpdater = new TransparencyUpdater(alpha);
|
||||||
mTransparencyUpdater->setLightSource(mExtraLightSource);
|
|
||||||
mObjectRoot->addCullCallback(mTransparencyUpdater);
|
mObjectRoot->addCullCallback(mTransparencyUpdater);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1650,6 +1642,8 @@ namespace MWRender
|
||||||
mObjectRoot->removeCullCallback(mTransparencyUpdater);
|
mObjectRoot->removeCullCallback(mTransparencyUpdater);
|
||||||
mTransparencyUpdater = nullptr;
|
mTransparencyUpdater = nullptr;
|
||||||
}
|
}
|
||||||
|
if (mExtraLightSource)
|
||||||
|
mExtraLightSource->setActorFade(alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::setLightEffect(float effect)
|
void Animation::setLightEffect(float effect)
|
||||||
|
|
|
@ -7,8 +7,10 @@
|
||||||
#include <components/sceneutil/textkeymap.hpp>
|
#include <components/sceneutil/textkeymap.hpp>
|
||||||
#include <components/sceneutil/util.hpp>
|
#include <components/sceneutil/util.hpp>
|
||||||
#include <components/sceneutil/nodecallback.hpp>
|
#include <components/sceneutil/nodecallback.hpp>
|
||||||
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
|
@ -157,6 +159,8 @@ public:
|
||||||
|
|
||||||
virtual bool updateCarriedLeftVisible(const int weaptype) const { return false; };
|
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:
|
protected:
|
||||||
class AnimationTime : public SceneUtil::ControllerSource
|
class AnimationTime : public SceneUtil::ControllerSource
|
||||||
{
|
{
|
||||||
|
@ -250,8 +254,6 @@ protected:
|
||||||
|
|
||||||
std::shared_ptr<AnimationTime> mAnimationTimePtr[sNumBlendMasks];
|
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 NodeMap mNodeMap;
|
||||||
mutable bool mNodeMapCreated;
|
mutable bool mNodeMapCreated;
|
||||||
|
|
||||||
|
|
|
@ -236,7 +236,7 @@ namespace MWRender
|
||||||
mTotalMovement += speed * duration;
|
mTotalMovement += speed * duration;
|
||||||
speed /= (1.f + speed / 500.f);
|
speed /= (1.f + speed / 500.f);
|
||||||
float maxDelta = 300.f * duration;
|
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);
|
mMaxNextCameraDistance = mCameraDistance + duration * (100.f + mBaseCameraDistance);
|
||||||
updateStandingPreviewMode();
|
updateStandingPreviewMode();
|
||||||
|
@ -434,7 +434,7 @@ namespace MWRender
|
||||||
{
|
{
|
||||||
const float epsilon = 0.000001f;
|
const float epsilon = 0.000001f;
|
||||||
float limit = static_cast<float>(osg::PI_2) - epsilon;
|
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
|
float Camera::getCameraDistance() const
|
||||||
|
@ -460,7 +460,7 @@ namespace MWRender
|
||||||
}
|
}
|
||||||
|
|
||||||
mIsNearest = mBaseCameraDistance <= mNearest;
|
mIsNearest = mBaseCameraDistance <= mNearest;
|
||||||
mBaseCameraDistance = osg::clampBetween(mBaseCameraDistance, mNearest, mFurthest);
|
mBaseCameraDistance = std::clamp(mBaseCameraDistance, mNearest, mFurthest);
|
||||||
Settings::Manager::setFloat("third person camera distance", "Camera", mBaseCameraDistance);
|
Settings::Manager::setFloat("third person camera distance", "Camera", mBaseCameraDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -190,7 +190,9 @@ namespace MWRender
|
||||||
mTexture->setInternalFormat(GL_RGBA);
|
mTexture->setInternalFormat(GL_RGBA);
|
||||||
mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||||
mTexture->setFilter(osg::Texture::MAG_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;
|
mCamera = new osg::Camera;
|
||||||
// hints that the camera is not relative to the master camera
|
// hints that the camera is not relative to the master camera
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace osg
|
||||||
class Camera;
|
class Camera;
|
||||||
class Group;
|
class Group;
|
||||||
class Viewport;
|
class Viewport;
|
||||||
|
class StateSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWRender
|
namespace MWRender
|
||||||
|
@ -41,6 +42,8 @@ namespace MWRender
|
||||||
void rebuild();
|
void rebuild();
|
||||||
|
|
||||||
osg::ref_ptr<osg::Texture2D> getTexture();
|
osg::ref_ptr<osg::Texture2D> getTexture();
|
||||||
|
/// Get the osg::StateSet required to render the texture correctly, if any.
|
||||||
|
osg::StateSet* getTextureStateSet() { return mTextureStateSet; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CharacterPreview(const CharacterPreview&);
|
CharacterPreview(const CharacterPreview&);
|
||||||
|
@ -54,6 +57,7 @@ namespace MWRender
|
||||||
osg::ref_ptr<osg::Group> mParent;
|
osg::ref_ptr<osg::Group> mParent;
|
||||||
Resource::ResourceSystem* mResourceSystem;
|
Resource::ResourceSystem* mResourceSystem;
|
||||||
osg::ref_ptr<osg::Texture2D> mTexture;
|
osg::ref_ptr<osg::Texture2D> mTexture;
|
||||||
|
osg::ref_ptr<osg::StateSet> mTextureStateSet;
|
||||||
osg::ref_ptr<osg::Camera> mCamera;
|
osg::ref_ptr<osg::Camera> mCamera;
|
||||||
osg::ref_ptr<DrawOnceCallback> mDrawOnceCallback;
|
osg::ref_ptr<DrawOnceCallback> mDrawOnceCallback;
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
||||||
if (bonename != "Weapon Bone")
|
if (bonename != "Weapon Bone")
|
||||||
{
|
{
|
||||||
const NodeMap& nodeMap = getNodeMap();
|
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())
|
if (found == nodeMap.end())
|
||||||
bonename = "Weapon Bone";
|
bonename = "Weapon Bone";
|
||||||
}
|
}
|
||||||
|
@ -139,32 +139,13 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
||||||
bonename = "Shield Bone";
|
bonename = "Shield Bone";
|
||||||
if (item.getType() == ESM::Armor::sRecordId)
|
if (item.getType() == ESM::Armor::sRecordId)
|
||||||
{
|
{
|
||||||
// Shield body part model should be used if possible.
|
itemModel = getShieldMesh(item, false);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
osg::ref_ptr<const osg::Node> node = mResourceSystem->getSceneManager()->getTemplate(itemModel);
|
osg::ref_ptr<osg::Node> attached = attach(itemModel, bonename, bonename, item.getType() == ESM::Light::sRecordId);
|
||||||
|
|
||||||
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());
|
|
||||||
|
|
||||||
scene.reset(new PartHolder(attached));
|
scene.reset(new PartHolder(attached));
|
||||||
|
|
||||||
|
|
|
@ -562,8 +562,8 @@ bool LocalMap::isPositionExplored (float nX, float nY, int x, int y)
|
||||||
if (!segment.mFogOfWarImage)
|
if (!segment.mFogOfWarImage)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
nX = std::max(0.f, std::min(1.f, nX));
|
nX = std::clamp(nX, 0.f, 1.f);
|
||||||
nY = std::max(0.f, std::min(1.f, nY));
|
nY = std::clamp(nY, 0.f, 1.f);
|
||||||
|
|
||||||
int texU = static_cast<int>((sFogOfWarResolution - 1) * nX);
|
int texU = static_cast<int>((sFogOfWarResolution - 1) * nX);
|
||||||
int texV = static_cast<int>((sFogOfWarResolution - 1) * nY);
|
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;
|
uint32_t clr = *(uint32_t*)data;
|
||||||
uint8_t alpha = (clr >> 24);
|
uint8_t alpha = (clr >> 24);
|
||||||
|
|
||||||
alpha = std::min( alpha, (uint8_t) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) );
|
alpha = std::min( alpha, (uint8_t) (std::clamp((sqrDist/sqrExploreRadius)*255, 0.f, 1.f)));
|
||||||
uint32_t val = (uint32_t) (alpha << 24);
|
uint32_t val = (uint32_t) (alpha << 24);
|
||||||
if ( *data != val)
|
if ( *data != val)
|
||||||
{
|
{
|
||||||
|
|
|
@ -82,34 +82,6 @@ std::string getVampireHead(const std::string& race, bool female)
|
||||||
return "meshes\\" + bodyPart->mModel;
|
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();
|
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);
|
std::string mesh = getShieldMesh(shield, !mNpc->isMale());
|
||||||
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());
|
|
||||||
|
|
||||||
if (mesh.empty())
|
if (mesh.empty())
|
||||||
return std::string();
|
return std::string();
|
||||||
|
@ -678,7 +645,7 @@ void NpcAnimation::updateParts()
|
||||||
{
|
{
|
||||||
const ESM::Light *light = part.get<ESM::Light>()->mBase;
|
const ESM::Light *light = part.get<ESM::Light>()->mBase;
|
||||||
addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft,
|
addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft,
|
||||||
1, "meshes\\"+light->mModel);
|
1, "meshes\\"+light->mModel, false, nullptr, true);
|
||||||
if (mObjectParts[ESM::PRT_Shield])
|
if (mObjectParts[ESM::PRT_Shield])
|
||||||
addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), light);
|
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);
|
osg::ref_ptr<osg::Node> attached = attach(model, bonename, bonefilter, isLight);
|
||||||
|
|
||||||
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);
|
|
||||||
if (enchantedGlow)
|
if (enchantedGlow)
|
||||||
mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor);
|
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;
|
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])
|
if(priority <= mPartPriorities[type])
|
||||||
return false;
|
return false;
|
||||||
|
@ -813,7 +773,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
|
||||||
if (weaponBonename != bonename)
|
if (weaponBonename != bonename)
|
||||||
{
|
{
|
||||||
const NodeMap& nodeMap = getNodeMap();
|
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())
|
if (found != nodeMap.end())
|
||||||
bonename = weaponBonename;
|
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
|
// 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;
|
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)
|
catch (std::exception& e)
|
||||||
{
|
{
|
||||||
|
@ -1011,13 +971,10 @@ void NpcAnimation::showCarriedLeft(bool show)
|
||||||
// For shields we must try to use the body part model
|
// For shields we must try to use the body part model
|
||||||
if (iter->getType() == ESM::Armor::sRecordId)
|
if (iter->getType() == ESM::Armor::sRecordId)
|
||||||
{
|
{
|
||||||
const ESM::Armor *armor = iter->get<ESM::Armor>()->mBase;
|
mesh = getShieldMesh(*iter, !mNpc->isMale());
|
||||||
const std::vector<ESM::PartReference>& bodyparts = armor->mParts.mParts;
|
|
||||||
if (!bodyparts.empty())
|
|
||||||
mesh = getShieldBodypartMesh(bodyparts, !mNpc->isMale());
|
|
||||||
}
|
}
|
||||||
if (mesh.empty() || addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1,
|
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())
|
if (mesh.empty())
|
||||||
reserveIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1);
|
reserveIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1);
|
||||||
|
|
|
@ -83,13 +83,13 @@ private:
|
||||||
NpcType getNpcType() const;
|
NpcType getNpcType() const;
|
||||||
|
|
||||||
PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename,
|
PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename,
|
||||||
const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor=nullptr);
|
const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight);
|
||||||
|
|
||||||
void removeIndividualPart(ESM::PartReferenceType type);
|
void removeIndividualPart(ESM::PartReferenceType type);
|
||||||
void reserveIndividualPart(ESM::PartReferenceType type, int group, int priority);
|
void reserveIndividualPart(ESM::PartReferenceType type, int group, int priority);
|
||||||
|
|
||||||
bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh,
|
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 removePartGroup(int group);
|
||||||
void addPartGroup(int group, int priority, const std::vector<ESM::PartReference> &parts,
|
void addPartGroup(int group, int priority, const std::vector<ESM::PartReference> &parts,
|
||||||
bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr);
|
bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr);
|
||||||
|
@ -105,7 +105,7 @@ private:
|
||||||
protected:
|
protected:
|
||||||
void addControllers() override;
|
void addControllers() override;
|
||||||
bool isArrowAttached() const override;
|
bool isArrowAttached() const override;
|
||||||
std::string getShieldMesh(const MWWorld::ConstPtr& shield) const override;
|
std::string getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue