mirror of
https://github.com/OpenMW/openmw.git
synced 2025-04-10 01:36:42 +00:00
Merge remote-tracking branch 'upstream/master' into detain-hash
Tests which no longer work are commented out. Some of these don't work because they're effectively testing for the presence of bugs in the old implementation. Others don't work because we're no longer accidentally disabling the boost::program_options feature where it generates an error if only part of a token gets consumed. These will be fixed by later commits.
This commit is contained in:
commit
1b83b08d80
313 changed files with 5815 additions and 5055 deletions
50
.github/workflows/cmake.yml
vendored
Normal file
50
.github/workflows/cmake.yml
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
name: CMake
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
BUILD_TYPE: RelWithDebInfo
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Add OpenMW PPA Dependancies
|
||||||
|
run: sudo add-apt-repository ppa:openmw/openmw; sudo apt-get update
|
||||||
|
|
||||||
|
- name: Install Building Dependancies
|
||||||
|
run: sudo CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
|
||||||
|
|
||||||
|
- name: Prime ccache
|
||||||
|
uses: hendrikmuhs/ccache-action@v1
|
||||||
|
with:
|
||||||
|
key: ${{ matrix.os }}-${{ env.BUILD_TYPE }}
|
||||||
|
max-size: 1000M
|
||||||
|
|
||||||
|
- name: Configure
|
||||||
|
run: cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_FLAGS='-Werror' -DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy"
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
shell: bash
|
||||||
|
run: cmake --install .
|
||||||
|
|
||||||
|
- name: Create Artifact
|
||||||
|
shell: bash
|
||||||
|
working-directory: install
|
||||||
|
run: |
|
||||||
|
ls -laR
|
||||||
|
7z a ../build_artifact.7z .
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
path: ./build_artifact.7z
|
||||||
|
name: build_artifact.7z
|
|
@ -80,6 +80,7 @@ Programmers
|
||||||
Federico Guerra (FedeWar)
|
Federico Guerra (FedeWar)
|
||||||
Fil Krynicki (filkry)
|
Fil Krynicki (filkry)
|
||||||
Finbar Crago (finbar-crago)
|
Finbar Crago (finbar-crago)
|
||||||
|
Florent Teppe (Tetramir)
|
||||||
Florian Weber (Florianjw)
|
Florian Weber (Florianjw)
|
||||||
Frédéric Chardon (fr3dz10)
|
Frédéric Chardon (fr3dz10)
|
||||||
Gaëtan Dezeiraud (Brouilles)
|
Gaëtan Dezeiraud (Brouilles)
|
||||||
|
@ -228,6 +229,7 @@ Programmers
|
||||||
Yuri Krupenin
|
Yuri Krupenin
|
||||||
zelurker
|
zelurker
|
||||||
Noah Gooder
|
Noah Gooder
|
||||||
|
Andrew Appuhamy (andrew-app)
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
|
|
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -1,6 +1,7 @@
|
||||||
0.48.0
|
0.48.0
|
||||||
------
|
------
|
||||||
|
|
||||||
|
Bug #1751: Birthsign abilities increase modified attribute values instead of base ones
|
||||||
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)
|
||||||
|
@ -10,20 +11,25 @@
|
||||||
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 #4752: UpdateCellCommand doesn't undo properly
|
|
||||||
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 #5379: Wandering NPCs falling through cantons
|
Bug #5379: Wandering NPCs falling through cantons
|
||||||
Bug #5453: Magic effect VFX are offset for creatures
|
Bug #5453: Magic effect VFX are offset for creatures
|
||||||
Bug #5483: AutoCalc flag is not used to calculate spells cost
|
Bug #5483: AutoCalc flag is not used to calculate spells cost
|
||||||
Bug #5508: Engine binary links to Qt without using it
|
Bug #5508: Engine binary links to Qt without using it
|
||||||
Bug #5755: Active grid object paging - disappearing textures
|
Bug #5596: Effects in constant spells should not be merged
|
||||||
|
Bug #5621: Drained stats cannot be restored
|
||||||
|
Bug #5766: Active grid object paging - disappearing textures
|
||||||
Bug #5788: Texture editing parses the selected indexes wrongly
|
Bug #5788: Texture editing parses the selected indexes wrongly
|
||||||
|
Bug #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention
|
||||||
Bug #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 #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
|
||||||
Bug #6067: esp loader fails in for certain subrecord orders
|
Bug #6067: esp loader fails in for certain subrecord orders
|
||||||
|
Bug #6087: Bound items added directly to the inventory disappear if their corresponding spell effect ends
|
||||||
Bug #6101: Disarming trapped unlocked owned objects isn't considered a crime
|
Bug #6101: Disarming trapped unlocked owned objects isn't considered a crime
|
||||||
Bug #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed
|
Bug #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed
|
||||||
Bug #6115: Showmap overzealous matching
|
Bug #6115: Showmap overzealous matching
|
||||||
|
@ -34,23 +40,41 @@
|
||||||
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 #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 #6273: Respawning NPCs rotation is inconsistent
|
||||||
|
Bug #6282: Laura craft doesn't follow the player character
|
||||||
|
Bug #6283: Avis Dorsey follows you after her death
|
||||||
|
Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters
|
||||||
|
Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod
|
||||||
|
Bug #6302: Teleporting disabled actor breaks its disabled state
|
||||||
|
Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken
|
||||||
|
Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house
|
||||||
|
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
|
||||||
Feature #3616: Allow Zoom levels on the World Map
|
Feature #3616: Allow Zoom levels on the World Map
|
||||||
|
Feature #4297: Implement APPLIED_ONCE flag for magic effects
|
||||||
|
Feature #4414: Handle duration of EXTRA SPELL magic effect
|
||||||
Feature #4595: Unique object identifier
|
Feature #4595: Unique object identifier
|
||||||
Feature #4737: Handle instance move from one cell to another
|
Feature #5198: Implement "Magic effect expired" event
|
||||||
|
Feature #5454: Clear active spells from actor when he disappears from scene
|
||||||
Feature #5489: MCP: Telekinesis fix for activators
|
Feature #5489: MCP: Telekinesis fix for activators
|
||||||
|
Feature #5737: Handle instance move from one cell to another
|
||||||
Feature #5996: Support Lua scripts in OpenMW
|
Feature #5996: Support Lua scripts in OpenMW
|
||||||
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 #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 #6251: OpenMW-CS: Set instance movement based on camera zoom
|
Feature #6251: OpenMW-CS: Set instance movement based on camera zoom
|
||||||
|
Feature #6288: Preserve the "blocked" record flag for referenceable objects.
|
||||||
|
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
|
||||||
------
|
------
|
||||||
|
|
||||||
|
@ -184,6 +208,7 @@
|
||||||
Bug #6043: Actor can have torch missing when torch animation is played
|
Bug #6043: Actor can have torch missing when torch animation is played
|
||||||
Bug #6047: Mouse bindings can be triggered during save loading
|
Bug #6047: Mouse bindings can be triggered during save loading
|
||||||
Bug #6136: Game freezes when NPCs try to open doors that are about to be closed
|
Bug #6136: Game freezes when NPCs try to open doors that are about to be closed
|
||||||
|
Bug #6294: Game crashes with empty pathgrid
|
||||||
Feature #390: 3rd person look "over the shoulder"
|
Feature #390: 3rd person look "over the shoulder"
|
||||||
Feature #832: OpenMW-CS: Handle deleted references
|
Feature #832: OpenMW-CS: Handle deleted references
|
||||||
Feature #1536: Show more information about level on menu
|
Feature #1536: Show more information about level on menu
|
||||||
|
@ -213,6 +238,7 @@
|
||||||
Feature #5519: Code Patch tab in launcher
|
Feature #5519: Code Patch tab in launcher
|
||||||
Feature #5524: Resume failed script execution after reload
|
Feature #5524: Resume failed script execution after reload
|
||||||
Feature #5545: Option to allow stealing from an unconscious NPC during combat
|
Feature #5545: Option to allow stealing from an unconscious NPC during combat
|
||||||
|
Feature #5551: Do not reboot PC after OpenMW installation on Windows
|
||||||
Feature #5563: Run physics update in background thread
|
Feature #5563: Run physics update in background thread
|
||||||
Feature #5579: MCP SetAngle enhancement
|
Feature #5579: MCP SetAngle enhancement
|
||||||
Feature #5580: Service refusal filtering
|
Feature #5580: Service refusal filtering
|
||||||
|
@ -227,6 +253,7 @@
|
||||||
Feature #5814: Bsatool should be able to create BSA archives, not only to extract it
|
Feature #5814: Bsatool should be able to create BSA archives, not only to extract it
|
||||||
Feature #5828: Support more than 8 lights
|
Feature #5828: Support more than 8 lights
|
||||||
Feature #5910: Fall back to delta time when physics can't keep up
|
Feature #5910: Fall back to delta time when physics can't keep up
|
||||||
|
Feature #5980: Support Bullet with double precision instead of one with single precision
|
||||||
Feature #6024: OpenMW-CS: Selecting terrain in "Terrain land editing" should support "Add to selection" and "Remove from selection" modes
|
Feature #6024: OpenMW-CS: Selecting terrain in "Terrain land editing" should support "Add to selection" and "Remove from selection" modes
|
||||||
Feature #6033: Include pathgrid to navigation mesh
|
Feature #6033: Include pathgrid to navigation mesh
|
||||||
Feature #6034: Find path based on area cost depending on NPC stats
|
Feature #6034: Find path based on area cost depending on NPC stats
|
||||||
|
|
|
@ -32,6 +32,7 @@ declare -a CMAKE_CONF_OPTS=(
|
||||||
-DCMAKE_INSTALL_PREFIX=install
|
-DCMAKE_INSTALL_PREFIX=install
|
||||||
-DCMAKE_C_FLAGS='-Werror'
|
-DCMAKE_C_FLAGS='-Werror'
|
||||||
-DCMAKE_CXX_FLAGS="${CXX_FLAGS}"
|
-DCMAKE_CXX_FLAGS="${CXX_FLAGS}"
|
||||||
|
-DOPENMW_CXX_FLAGS="-Werror=implicit-fallthrough"
|
||||||
)
|
)
|
||||||
|
|
||||||
if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then
|
if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then
|
||||||
|
|
|
@ -13,6 +13,11 @@ if(POLICY CMP0083)
|
||||||
cmake_policy(SET CMP0083 NEW)
|
cmake_policy(SET CMP0083 NEW)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# to link with freetype library
|
||||||
|
if(POLICY CMP0079)
|
||||||
|
cmake_policy(SET CMP0079 NEW)
|
||||||
|
endif()
|
||||||
|
|
||||||
option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF)
|
option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF)
|
||||||
if(OPENMW_GL4ES_MANUAL_INIT)
|
if(OPENMW_GL4ES_MANUAL_INIT)
|
||||||
add_definitions(-DOPENMW_GL4ES_MANUAL_INIT)
|
add_definitions(-DOPENMW_GL4ES_MANUAL_INIT)
|
||||||
|
@ -202,8 +207,6 @@ if (USE_QT)
|
||||||
find_package(Qt5Widgets REQUIRED)
|
find_package(Qt5Widgets REQUIRED)
|
||||||
find_package(Qt5Network REQUIRED)
|
find_package(Qt5Network REQUIRED)
|
||||||
find_package(Qt5OpenGL REQUIRED)
|
find_package(Qt5OpenGL REQUIRED)
|
||||||
# Instruct CMake to run moc automatically when needed.
|
|
||||||
#set(CMAKE_AUTOMOC ON)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(USED_OSG_COMPONENTS
|
set(USED_OSG_COMPONENTS
|
||||||
|
@ -540,6 +543,10 @@ if (BUILD_OPENCS)
|
||||||
add_subdirectory (extern/osgQt)
|
add_subdirectory (extern/osgQt)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (OPENMW_CXX_FLAGS)
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPENMW_CXX_FLAGS}")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Components
|
# Components
|
||||||
add_subdirectory (components)
|
add_subdirectory (components)
|
||||||
|
|
||||||
|
|
|
@ -124,11 +124,9 @@ public:
|
||||||
{
|
{
|
||||||
mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt.mLevel;
|
mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt.mLevel;
|
||||||
mContext->mPlayerBase = npc;
|
mContext->mPlayerBase = npc;
|
||||||
ESM::SpellState::SpellParams empty;
|
|
||||||
// FIXME: player start spells and birthsign spells aren't listed here,
|
// FIXME: player start spells and birthsign spells aren't listed here,
|
||||||
// need to fix openmw to account for this
|
// need to fix openmw to account for this
|
||||||
for (const auto & spell : npc.mSpells.mList)
|
mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells = npc.mSpells.mList;
|
||||||
mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[spell] = empty;
|
|
||||||
|
|
||||||
// Clear the list now that we've written it, this prevents issues cropping up with
|
// Clear the list now that we've written it, this prevents issues cropping up with
|
||||||
// ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal.
|
// ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal.
|
||||||
|
|
|
@ -36,23 +36,6 @@ set(LAUNCHER_HEADER
|
||||||
)
|
)
|
||||||
|
|
||||||
# Headers that must be pre-processed
|
# Headers that must be pre-processed
|
||||||
set(LAUNCHER_HEADER_MOC
|
|
||||||
datafilespage.hpp
|
|
||||||
graphicspage.hpp
|
|
||||||
maindialog.hpp
|
|
||||||
playpage.hpp
|
|
||||||
textslotmsgbox.hpp
|
|
||||||
settingspage.hpp
|
|
||||||
advancedpage.hpp
|
|
||||||
|
|
||||||
utils/cellnameloader.hpp
|
|
||||||
utils/textinputdialog.hpp
|
|
||||||
utils/profilescombobox.hpp
|
|
||||||
utils/lineedit.hpp
|
|
||||||
utils/openalutil.hpp
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
set(LAUNCHER_UI
|
set(LAUNCHER_UI
|
||||||
${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui
|
${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui
|
||||||
${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui
|
${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui
|
||||||
|
@ -74,7 +57,6 @@ if(WIN32)
|
||||||
endif(WIN32)
|
endif(WIN32)
|
||||||
|
|
||||||
QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc)
|
QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc)
|
||||||
QT5_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC})
|
|
||||||
QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI})
|
QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI})
|
||||||
|
|
||||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
@ -109,4 +91,7 @@ if (BUILD_WITH_CODE_COVERAGE)
|
||||||
target_link_libraries(openmw-launcher gcov)
|
target_link_libraries(openmw-launcher gcov)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(USE_QT)
|
||||||
|
set_property(TARGET openmw-launcher PROPERTY AUTOMOC ON)
|
||||||
|
endif(USE_QT)
|
||||||
|
|
||||||
|
|
|
@ -52,11 +52,8 @@ void readVFS(VFS::Archive* anArchive,std::string archivePath = "")
|
||||||
myManager.addArchive(anArchive);
|
myManager.addArchive(anArchive);
|
||||||
myManager.buildIndex();
|
myManager.buildIndex();
|
||||||
|
|
||||||
std::map<std::string, VFS::File*> files=myManager.getIndex();
|
for(const auto& name : myManager.getRecursiveDirectoryIterator(""))
|
||||||
for(auto it=files.begin(); it!=files.end(); ++it)
|
|
||||||
{
|
{
|
||||||
std::string name = it->first;
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
if(isNIF(name))
|
if(isNIF(name))
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,11 +8,11 @@ opencs_units (model/doc
|
||||||
document operation saving documentmanager loader runner operationholder
|
document operation saving documentmanager loader runner operationholder
|
||||||
)
|
)
|
||||||
|
|
||||||
opencs_units_noqt (model/doc
|
opencs_units (model/doc
|
||||||
stage savingstate savingstages blacklist messages
|
stage savingstate savingstages blacklist messages
|
||||||
)
|
)
|
||||||
|
|
||||||
opencs_hdrs_noqt (model/doc
|
opencs_hdrs (model/doc
|
||||||
state
|
state
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,14 +23,14 @@ opencs_units (model/world
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
opencs_units_noqt (model/world
|
opencs_units (model/world
|
||||||
universalid record commands columnbase columnimp scriptcontext cell refidcollection
|
universalid record commands columnbase columnimp scriptcontext cell refidcollection
|
||||||
refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope
|
refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope
|
||||||
pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection
|
pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection
|
||||||
idcompletionmanager metadata defaultgmsts infoselectwrapper commandmacro
|
idcompletionmanager metadata defaultgmsts infoselectwrapper commandmacro
|
||||||
)
|
)
|
||||||
|
|
||||||
opencs_hdrs_noqt (model/world
|
opencs_hdrs (model/world
|
||||||
columnimp idcollection collection info subcellcollection
|
columnimp idcollection collection info subcellcollection
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -39,14 +39,14 @@ opencs_units (model/tools
|
||||||
tools reportmodel mergeoperation
|
tools reportmodel mergeoperation
|
||||||
)
|
)
|
||||||
|
|
||||||
opencs_units_noqt (model/tools
|
opencs_units (model/tools
|
||||||
mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
|
mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
|
||||||
birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck
|
birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck
|
||||||
startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck
|
startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck
|
||||||
mergestages gmstcheck topicinfocheck journalcheck enchantmentcheck
|
mergestages gmstcheck topicinfocheck journalcheck enchantmentcheck
|
||||||
)
|
)
|
||||||
|
|
||||||
opencs_hdrs_noqt (model/tools
|
opencs_hdrs (model/tools
|
||||||
mergestate
|
mergestate
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,11 +57,11 @@ opencs_units (view/doc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
opencs_units_noqt (view/doc
|
opencs_units (view/doc
|
||||||
subviewfactory
|
subviewfactory
|
||||||
)
|
)
|
||||||
|
|
||||||
opencs_hdrs_noqt (view/doc
|
opencs_hdrs (view/doc
|
||||||
subviewfactoryimp
|
subviewfactoryimp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,10 +71,10 @@ opencs_units (view/world
|
||||||
cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview
|
cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview
|
||||||
infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable
|
infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable
|
||||||
dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator
|
dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator
|
||||||
bodypartcreator landtexturecreator landcreator
|
bodypartcreator landtexturecreator landcreator tableheadermouseeventhandler
|
||||||
)
|
)
|
||||||
|
|
||||||
opencs_units_noqt (view/world
|
opencs_units (view/world
|
||||||
subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate
|
subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate
|
||||||
scripthighlighter idvalidator dialoguecreator idcompletiondelegate
|
scripthighlighter idvalidator dialoguecreator idcompletiondelegate
|
||||||
colordelegate dragdroputils
|
colordelegate dragdroputils
|
||||||
|
@ -92,12 +92,12 @@ opencs_units (view/render
|
||||||
cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands
|
cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands
|
||||||
)
|
)
|
||||||
|
|
||||||
opencs_units_noqt (view/render
|
opencs_units (view/render
|
||||||
lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase
|
lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase
|
||||||
cellarrow cellmarker cellborder pathgrid
|
cellarrow cellmarker cellborder pathgrid
|
||||||
)
|
)
|
||||||
|
|
||||||
opencs_hdrs_noqt (view/render
|
opencs_hdrs (view/render
|
||||||
mask
|
mask
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ opencs_units (view/tools
|
||||||
reportsubview reporttable searchsubview searchbox merge
|
reportsubview reporttable searchsubview searchbox merge
|
||||||
)
|
)
|
||||||
|
|
||||||
opencs_units_noqt (view/tools
|
opencs_units (view/tools
|
||||||
subviews
|
subviews
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -119,11 +119,11 @@ opencs_units (model/prefs
|
||||||
shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting
|
shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting
|
||||||
)
|
)
|
||||||
|
|
||||||
opencs_units_noqt (model/prefs
|
opencs_units (model/prefs
|
||||||
category
|
category
|
||||||
)
|
)
|
||||||
|
|
||||||
opencs_units_noqt (model/filter
|
opencs_units (model/filter
|
||||||
node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode
|
node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -150,7 +150,6 @@ if(WIN32)
|
||||||
endif(WIN32)
|
endif(WIN32)
|
||||||
|
|
||||||
qt5_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI})
|
qt5_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI})
|
||||||
qt5_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT})
|
|
||||||
qt5_add_resources(OPENCS_RES_SRC ${OPENCS_RES})
|
qt5_add_resources(OPENCS_RES_SRC ${OPENCS_RES})
|
||||||
|
|
||||||
# for compiled .ui files
|
# for compiled .ui files
|
||||||
|
@ -259,3 +258,7 @@ endif (MSVC)
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
INSTALL(TARGETS openmw-cs BUNDLE DESTINATION "." COMPONENT Bundle)
|
INSTALL(TARGETS openmw-cs BUNDLE DESTINATION "." COMPONENT Bundle)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(USE_QT)
|
||||||
|
set_property(TARGET openmw-cs PROPERTY AUTOMOC ON)
|
||||||
|
endif(USE_QT)
|
||||||
|
|
|
@ -285,8 +285,7 @@ void CSMDoc::WriteCellCollectionStage::writeReferences (const std::deque<int>& r
|
||||||
{
|
{
|
||||||
refRecord.mRefNum.mIndex = newRefNum++;
|
refRecord.mRefNum.mIndex = newRefNum++;
|
||||||
}
|
}
|
||||||
|
else if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell)
|
||||||
if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell)
|
|
||||||
!= stream.str() && !interior)
|
!= stream.str() && !interior)
|
||||||
{
|
{
|
||||||
// An empty mOriginalCell is meant to indicate that it is the same as
|
// An empty mOriginalCell is meant to indicate that it is the same as
|
||||||
|
|
|
@ -108,7 +108,7 @@ namespace CSMDoc
|
||||||
state == CSMWorld::RecordBase::State_ModifiedOnly ||
|
state == CSMWorld::RecordBase::State_ModifiedOnly ||
|
||||||
state == CSMWorld::RecordBase::State_Deleted)
|
state == CSMWorld::RecordBase::State_Deleted)
|
||||||
{
|
{
|
||||||
writer.startRecord (record.sRecordId);
|
writer.startRecord (record.sRecordId, record.mRecordFlags);
|
||||||
record.save (writer, state == CSMWorld::RecordBase::State_Deleted);
|
record.save (writer, state == CSMWorld::RecordBase::State_Deleted);
|
||||||
writer.endRecord (record.sRecordId);
|
writer.endRecord (record.sRecordId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -371,6 +371,7 @@ namespace CSMWorld
|
||||||
{ ColumnId_Skill7, "Skill 7" },
|
{ ColumnId_Skill7, "Skill 7" },
|
||||||
|
|
||||||
{ ColumnId_Persistent, "Persistent" },
|
{ ColumnId_Persistent, "Persistent" },
|
||||||
|
{ ColumnId_Blocked, "Blocked" },
|
||||||
|
|
||||||
{ -1, 0 } // end marker
|
{ -1, 0 } // end marker
|
||||||
};
|
};
|
||||||
|
|
|
@ -344,6 +344,7 @@ namespace CSMWorld
|
||||||
ColumnId_FactionAttrib2 = 312,
|
ColumnId_FactionAttrib2 = 312,
|
||||||
|
|
||||||
ColumnId_Persistent = 313,
|
ColumnId_Persistent = 313,
|
||||||
|
ColumnId_Blocked = 314,
|
||||||
|
|
||||||
// Allocated to a separate value range, so we don't get a collision should we ever need
|
// Allocated to a separate value range, so we don't get a collision should we ever need
|
||||||
// to extend the number of use values.
|
// to extend the number of use values.
|
||||||
|
|
|
@ -3,9 +3,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <QAbstractItemModel>
|
|
||||||
#include <QAbstractProxyModel>
|
|
||||||
|
|
||||||
#include <components/misc/stringops.hpp>
|
#include <components/misc/stringops.hpp>
|
||||||
#include <components/misc/constants.hpp>
|
#include <components/misc/constants.hpp>
|
||||||
|
|
||||||
|
@ -142,31 +139,12 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons
|
||||||
if (mLocked)
|
if (mLocked)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::unique_ptr<CSMWorld::CloneCommand> clonedData;
|
|
||||||
std::unique_ptr<CSMWorld::DeleteCommand> deleteData;
|
|
||||||
|
|
||||||
std::string newId;
|
|
||||||
|
|
||||||
std::unique_ptr<CSMWorld::ModifyCommand> modifyData;
|
|
||||||
std::unique_ptr<CSMWorld::UpdateCellCommand> modifyCell;
|
std::unique_ptr<CSMWorld::UpdateCellCommand> modifyCell;
|
||||||
|
|
||||||
QAbstractItemModel* sourceModel = model;
|
|
||||||
if (IdTableProxyModel* proxy = dynamic_cast<IdTableProxyModel*> (model))
|
|
||||||
sourceModel = proxy->sourceModel();
|
|
||||||
|
|
||||||
CSMWorld::IdTable& table = dynamic_cast<CSMWorld::IdTable&>(*sourceModel); // for getId()
|
|
||||||
int stateColumn = table.findColumnIndex(Columns::ColumnId_Modification);
|
|
||||||
QModelIndex stateIndex = table.getModelIndex(table.getId(index.row()), stateColumn);
|
|
||||||
RecordBase::State state = static_cast<RecordBase::State> (sourceModel->data(stateIndex).toInt());
|
|
||||||
|
|
||||||
int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt();
|
int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt();
|
||||||
|
|
||||||
// This is not guaranteed to be the same as \a model, since a proxy could be used.
|
if (columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos)
|
||||||
IdTable& model2 = dynamic_cast<IdTable&> (*mDocument.getData().getTableModel(mId));
|
|
||||||
|
|
||||||
// DeleteCommand triggers a signal to the whole row from IdTable::setData(), so ignore the second call
|
|
||||||
if (state != RecordBase::State_Deleted &&
|
|
||||||
(columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos))
|
|
||||||
{
|
{
|
||||||
const float oldPosition = model->data (index).toFloat();
|
const float oldPosition = model->data (index).toFloat();
|
||||||
|
|
||||||
|
@ -178,9 +156,12 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons
|
||||||
|
|
||||||
int row = proxy ? proxy->mapToSource (index).row() : index.row();
|
int row = proxy ? proxy->mapToSource (index).row() : index.row();
|
||||||
|
|
||||||
|
// This is not guaranteed to be the same as \a model, since a proxy could be used.
|
||||||
|
IdTable& model2 = dynamic_cast<IdTable&> (*mDocument.getData().getTableModel (mId));
|
||||||
|
|
||||||
int cellColumn = model2.searchColumnIndex (Columns::ColumnId_Cell);
|
int cellColumn = model2.searchColumnIndex (Columns::ColumnId_Cell);
|
||||||
|
|
||||||
if (cellColumn != -1)
|
if (cellColumn!=-1)
|
||||||
{
|
{
|
||||||
QModelIndex cellIndex = model2.index (row, cellColumn);
|
QModelIndex cellIndex = model2.index (row, cellColumn);
|
||||||
|
|
||||||
|
@ -188,46 +169,23 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons
|
||||||
|
|
||||||
if (cellId.find ('#')!=std::string::npos)
|
if (cellId.find ('#')!=std::string::npos)
|
||||||
{
|
{
|
||||||
RefCollection& collection = mDocument.getData().getReferences();
|
// Need to recalculate the cell
|
||||||
newId = collection.getNewId();
|
modifyCell.reset (new UpdateCellCommand (model2, row));
|
||||||
|
|
||||||
clonedData.reset(new CloneCommand(table,
|
|
||||||
table.getId(row),
|
|
||||||
newId,
|
|
||||||
CSMWorld::UniversalId::Type_Reference));
|
|
||||||
|
|
||||||
deleteData.reset(new DeleteCommand(table,
|
|
||||||
table.getId(row),
|
|
||||||
CSMWorld::UniversalId::Type_Reference));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!clonedData.get())
|
std::unique_ptr<CSMWorld::ModifyCommand> modifyData (
|
||||||
{
|
new CSMWorld::ModifyCommand (*model, index, new_));
|
||||||
// DeleteCommand will trigger executeModify after setting the state to State_Deleted
|
|
||||||
// from CommandDelegate::setModelDataImp() - ignore
|
|
||||||
if (state != RecordBase::State_Deleted)
|
|
||||||
modifyData.reset(new CSMWorld::ModifyCommand(*model, index, new_));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clonedData.get())
|
if (modifyCell.get())
|
||||||
{
|
{
|
||||||
CommandMacro macro (mDocument.getUndoStack());
|
CommandMacro macro (mDocument.getUndoStack());
|
||||||
macro.push(clonedData.release());
|
macro.push (modifyData.release());
|
||||||
macro.push(deleteData.release());
|
macro.push (modifyCell.release());
|
||||||
|
|
||||||
// cannot do these earlier because newIndex is not available until CloneCommand is executed
|
|
||||||
QModelIndex newIndex = model2.getModelIndex (newId, index.column());
|
|
||||||
modifyData.reset (new CSMWorld::ModifyCommand (*model, newIndex, new_));
|
|
||||||
macro.push(modifyData.release());
|
|
||||||
|
|
||||||
// once the data is updated update the cell location
|
|
||||||
modifyCell.reset(new UpdateCellCommand(model2, newIndex.row()));
|
|
||||||
macro.push(modifyCell.release());
|
|
||||||
}
|
}
|
||||||
else if (!clonedData.get() && modifyData.get())
|
else
|
||||||
mDocument.getUndoStack().push (modifyData.release());
|
mDocument.getUndoStack().push (modifyData.release());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,14 @@ Qt::ItemFlags CSMWorld::IdTable::flags (const QModelIndex & index) const
|
||||||
if (mIdCollection->getColumn (index.column()).isUserEditable())
|
if (mIdCollection->getColumn (index.column()).isUserEditable())
|
||||||
flags |= Qt::ItemIsEditable;
|
flags |= Qt::ItemIsEditable;
|
||||||
|
|
||||||
|
int blockedColumn = searchColumnIndex(Columns::ColumnId_Blocked);
|
||||||
|
if (blockedColumn != -1 && blockedColumn != index.column())
|
||||||
|
{
|
||||||
|
bool isBlocked = mIdCollection->getData(index.row(), blockedColumn).toInt();
|
||||||
|
if (isBlocked)
|
||||||
|
flags = Qt::ItemIsSelectable; // not enabled (to grey out)
|
||||||
|
}
|
||||||
|
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,12 +91,48 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
|
||||||
else
|
else
|
||||||
ref.mCell = cell2.mId;
|
ref.mCell = cell2.mId;
|
||||||
|
|
||||||
|
if (ref.mRefNum.mContentFile != -1 && !base)
|
||||||
|
{
|
||||||
|
ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24;
|
||||||
|
ref.mRefNum.mIndex &= 0x00ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned int refNum = (ref.mRefNum.mIndex & 0x00ffffff) |
|
unsigned int refNum = (ref.mRefNum.mIndex & 0x00ffffff) |
|
||||||
(ref.mRefNum.hasContentFile() ? ref.mRefNum.mContentFile : 0xff) << 24;
|
(ref.mRefNum.hasContentFile() ? ref.mRefNum.mContentFile : 0xff) << 24;
|
||||||
|
|
||||||
std::map<unsigned int, unsigned int>::iterator iter = cache.find(refNum);
|
std::map<unsigned int, unsigned int>::iterator iter = cache.find(refNum);
|
||||||
|
|
||||||
if (ref.mRefNum.mContentFile != -1 && !base) ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24;
|
if (isMoved)
|
||||||
|
{
|
||||||
|
if (iter == cache.end())
|
||||||
|
{
|
||||||
|
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell,
|
||||||
|
mCells.getId(cellIndex));
|
||||||
|
|
||||||
|
messages.add(id, "Attempt to move a non-existent reference - RefNum index "
|
||||||
|
+ std::to_string(ref.mRefNum.mIndex) + ", refID " + ref.mRefID + ", content file index "
|
||||||
|
+ std::to_string(ref.mRefNum.mContentFile),
|
||||||
|
/*hint*/"",
|
||||||
|
CSMDoc::Message::Severity_Warning);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = getIntIndex(iter->second);
|
||||||
|
|
||||||
|
// ensure we have the same record id for setRecord()
|
||||||
|
ref.mId = getRecord(index).get().mId;
|
||||||
|
ref.mIdNum = extractIdNum(ref.mId);
|
||||||
|
|
||||||
|
std::unique_ptr<Record<CellRef> > record(new Record<CellRef>);
|
||||||
|
// TODO: check whether a base record be moved
|
||||||
|
record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
|
||||||
|
(base ? record->mBase : record->mModified) = std::move(ref);
|
||||||
|
|
||||||
|
// overwrite original record
|
||||||
|
setRecord(index, std::move(record));
|
||||||
|
|
||||||
|
continue; // NOTE: assumed moved references are not deleted at the same time
|
||||||
|
}
|
||||||
|
|
||||||
if (isDeleted)
|
if (isDeleted)
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace CSMWorld
|
||||||
class RefCollection : public Collection<CellRef>
|
class RefCollection : public Collection<CellRef>
|
||||||
{
|
{
|
||||||
Collection<Cell>& mCells;
|
Collection<Cell>& mCells;
|
||||||
std::map<unsigned int, int> mRefIndex;
|
std::map<unsigned int, int> mRefIndex; // CellRef index keyed by CSMWorld::CellRef::mIdNum
|
||||||
|
|
||||||
int mNextId;
|
int mNextId;
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,9 @@ namespace CSMWorld
|
||||||
const RefIdColumn *mId;
|
const RefIdColumn *mId;
|
||||||
const RefIdColumn *mModified;
|
const RefIdColumn *mModified;
|
||||||
const RefIdColumn *mType;
|
const RefIdColumn *mType;
|
||||||
|
const RefIdColumn *mBlocked;
|
||||||
|
|
||||||
|
BaseColumns () : mBlocked(nullptr) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// \brief Base adapter for all refereceable record types
|
/// \brief Base adapter for all refereceable record types
|
||||||
|
@ -90,6 +93,9 @@ namespace CSMWorld
|
||||||
if (column==mBase.mType)
|
if (column==mBase.mType)
|
||||||
return static_cast<int> (mType);
|
return static_cast<int> (mType);
|
||||||
|
|
||||||
|
if (column==mBase.mBlocked)
|
||||||
|
return (record.get().mRecordFlags & ESM::FLAG_Blocked) != 0;
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +108,17 @@ namespace CSMWorld
|
||||||
|
|
||||||
if (column==mBase.mModified)
|
if (column==mBase.mModified)
|
||||||
record.mState = static_cast<RecordBase::State> (value.toInt());
|
record.mState = static_cast<RecordBase::State> (value.toInt());
|
||||||
|
else if (column==mBase.mBlocked)
|
||||||
|
{
|
||||||
|
RecordT record2 = record.get();
|
||||||
|
|
||||||
|
if (value.toInt() != 0)
|
||||||
|
record2.mRecordFlags |= ESM::FLAG_Blocked;
|
||||||
|
else
|
||||||
|
record2.mRecordFlags &= ~ESM::FLAG_Blocked;
|
||||||
|
|
||||||
|
record.setModified(record2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename RecordT>
|
template<typename RecordT>
|
||||||
|
@ -110,6 +127,14 @@ namespace CSMWorld
|
||||||
return mType;
|
return mType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: Body Part should not have persistence (but BodyPart is not listed in the Objects
|
||||||
|
// table at the moment).
|
||||||
|
//
|
||||||
|
// Spellmaking - not persistent - currently not part of objects table
|
||||||
|
// Enchanting - not persistent - currently not part of objects table
|
||||||
|
//
|
||||||
|
// Leveled Creature - no model, so not persistent
|
||||||
|
// Leveled Item - no model, so not persistent
|
||||||
|
|
||||||
struct ModelColumns : public BaseColumns
|
struct ModelColumns : public BaseColumns
|
||||||
{
|
{
|
||||||
|
@ -1845,6 +1870,7 @@ namespace CSMWorld
|
||||||
content.mWander.mDuration = static_cast<short>(value.toInt());
|
content.mWander.mDuration = static_cast<short>(value.toInt());
|
||||||
else
|
else
|
||||||
return; // return without saving
|
return; // return without saving
|
||||||
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
if (content.mType == ESM::AI_Wander)
|
if (content.mType == ESM::AI_Wander)
|
||||||
content.mWander.mTimeOfDay = static_cast<unsigned char>(value.toInt());
|
content.mWander.mTimeOfDay = static_cast<unsigned char>(value.toInt());
|
||||||
|
|
|
@ -50,13 +50,16 @@ CSMWorld::RefIdCollection::RefIdCollection()
|
||||||
mColumns.emplace_back(Columns::ColumnId_RecordType, ColumnBase::Display_RefRecordType,
|
mColumns.emplace_back(Columns::ColumnId_RecordType, ColumnBase::Display_RefRecordType,
|
||||||
ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false);
|
ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false);
|
||||||
baseColumns.mType = &mColumns.back();
|
baseColumns.mType = &mColumns.back();
|
||||||
|
mColumns.emplace_back(Columns::ColumnId_Blocked, ColumnBase::Display_Boolean,
|
||||||
|
ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh);
|
||||||
|
baseColumns.mBlocked = &mColumns.back();
|
||||||
|
|
||||||
ModelColumns modelColumns (baseColumns);
|
ModelColumns modelColumns (baseColumns);
|
||||||
|
|
||||||
mColumns.emplace_back(Columns::ColumnId_Model, ColumnBase::Display_Mesh);
|
|
||||||
modelColumns.mModel = &mColumns.back();
|
|
||||||
mColumns.emplace_back(Columns::ColumnId_Persistent, ColumnBase::Display_Boolean);
|
mColumns.emplace_back(Columns::ColumnId_Persistent, ColumnBase::Display_Boolean);
|
||||||
modelColumns.mPersistence = &mColumns.back();
|
modelColumns.mPersistence = &mColumns.back();
|
||||||
|
mColumns.emplace_back(Columns::ColumnId_Model, ColumnBase::Display_Mesh);
|
||||||
|
modelColumns.mModel = &mColumns.back();
|
||||||
|
|
||||||
NameColumns nameColumns (modelColumns);
|
NameColumns nameColumns (modelColumns);
|
||||||
|
|
||||||
|
|
|
@ -23,10 +23,8 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char * const *
|
||||||
|
|
||||||
size_t baseSize = mBaseDirectory.size();
|
size_t baseSize = mBaseDirectory.size();
|
||||||
|
|
||||||
const std::map<std::string, VFS::File*>& index = vfs->getIndex();
|
for (const auto& filepath : vfs->getRecursiveDirectoryIterator(""))
|
||||||
for (std::map<std::string, VFS::File*>::const_iterator it = index.begin(); it != index.end(); ++it)
|
|
||||||
{
|
{
|
||||||
std::string filepath = it->first;
|
|
||||||
if (filepath.size()<baseSize+1 ||
|
if (filepath.size()<baseSize+1 ||
|
||||||
filepath.substr (0, baseSize)!=mBaseDirectory ||
|
filepath.substr (0, baseSize)!=mBaseDirectory ||
|
||||||
(filepath[baseSize]!='/' && filepath[baseSize]!='\\'))
|
(filepath[baseSize]!='/' && filepath[baseSize]!='\\'))
|
||||||
|
|
|
@ -161,7 +161,7 @@ void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool)
|
||||||
bool isNew = (mAction == ContentAction_New);
|
bool isNew = (mAction == ContentAction_New);
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
success = success && !(name.isEmpty());
|
success = !name.isEmpty();
|
||||||
else if (success)
|
else if (success)
|
||||||
{
|
{
|
||||||
ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back();
|
ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back();
|
||||||
|
|
|
@ -65,6 +65,8 @@ void CSVDoc::View::setupFileMenu()
|
||||||
QAction* save = createMenuEntry("Save", ":./menu-save.png", file, "document-file-save");
|
QAction* save = createMenuEntry("Save", ":./menu-save.png", file, "document-file-save");
|
||||||
connect (save, SIGNAL (triggered()), this, SLOT (save()));
|
connect (save, SIGNAL (triggered()), this, SLOT (save()));
|
||||||
mSave = save;
|
mSave = save;
|
||||||
|
|
||||||
|
file->addSeparator();
|
||||||
|
|
||||||
QAction* verify = createMenuEntry("Verify", ":./menu-verify.png", file, "document-file-verify");
|
QAction* verify = createMenuEntry("Verify", ":./menu-verify.png", file, "document-file-verify");
|
||||||
connect (verify, SIGNAL (triggered()), this, SLOT (verify()));
|
connect (verify, SIGNAL (triggered()), this, SLOT (verify()));
|
||||||
|
@ -80,6 +82,8 @@ void CSVDoc::View::setupFileMenu()
|
||||||
QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata");
|
QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata");
|
||||||
connect (meta, SIGNAL (triggered()), this, SLOT (addMetaDataSubView()));
|
connect (meta, SIGNAL (triggered()), this, SLOT (addMetaDataSubView()));
|
||||||
|
|
||||||
|
file->addSeparator();
|
||||||
|
|
||||||
QAction* close = createMenuEntry("Close", ":./menu-close.png", file, "document-file-close");
|
QAction* close = createMenuEntry("Close", ":./menu-close.png", file, "document-file-close");
|
||||||
connect (close, SIGNAL (triggered()), this, SLOT (close()));
|
connect (close, SIGNAL (triggered()), this, SLOT (close()));
|
||||||
|
|
||||||
|
@ -156,17 +160,16 @@ void CSVDoc::View::setupWorldMenu()
|
||||||
{
|
{
|
||||||
QMenu *world = menuBar()->addMenu (tr ("World"));
|
QMenu *world = menuBar()->addMenu (tr ("World"));
|
||||||
|
|
||||||
QAction* regions = createMenuEntry(CSMWorld::UniversalId::Type_Regions, world, "document-world-regions");
|
|
||||||
connect (regions, SIGNAL (triggered()), this, SLOT (addRegionsSubView()));
|
|
||||||
|
|
||||||
QAction* cells = createMenuEntry(CSMWorld::UniversalId::Type_Cells, world, "document-world-cells");
|
|
||||||
connect (cells, SIGNAL (triggered()), this, SLOT (addCellsSubView()));
|
|
||||||
|
|
||||||
QAction* referenceables = createMenuEntry(CSMWorld::UniversalId::Type_Referenceables, world, "document-world-referencables");
|
QAction* referenceables = createMenuEntry(CSMWorld::UniversalId::Type_Referenceables, world, "document-world-referencables");
|
||||||
connect (referenceables, SIGNAL (triggered()), this, SLOT (addReferenceablesSubView()));
|
connect (referenceables, SIGNAL (triggered()), this, SLOT (addReferenceablesSubView()));
|
||||||
|
|
||||||
QAction* references = createMenuEntry(CSMWorld::UniversalId::Type_References, world, "document-world-references");
|
QAction* references = createMenuEntry(CSMWorld::UniversalId::Type_References, world, "document-world-references");
|
||||||
connect (references, SIGNAL (triggered()), this, SLOT (addReferencesSubView()));
|
connect (references, SIGNAL (triggered()), this, SLOT (addReferencesSubView()));
|
||||||
|
|
||||||
|
world->addSeparator();
|
||||||
|
|
||||||
|
QAction* cells = createMenuEntry(CSMWorld::UniversalId::Type_Cells, world, "document-world-cells");
|
||||||
|
connect (cells, SIGNAL (triggered()), this, SLOT (addCellsSubView()));
|
||||||
|
|
||||||
QAction *lands = createMenuEntry(CSMWorld::UniversalId::Type_Lands, world, "document-world-lands");
|
QAction *lands = createMenuEntry(CSMWorld::UniversalId::Type_Lands, world, "document-world-lands");
|
||||||
connect (lands, SIGNAL (triggered()), this, SLOT (addLandsSubView()));
|
connect (lands, SIGNAL (triggered()), this, SLOT (addLandsSubView()));
|
||||||
|
@ -177,7 +180,10 @@ void CSVDoc::View::setupWorldMenu()
|
||||||
QAction *grid = createMenuEntry(CSMWorld::UniversalId::Type_Pathgrids, world, "document-world-pathgrid");
|
QAction *grid = createMenuEntry(CSMWorld::UniversalId::Type_Pathgrids, world, "document-world-pathgrid");
|
||||||
connect (grid, SIGNAL (triggered()), this, SLOT (addPathgridSubView()));
|
connect (grid, SIGNAL (triggered()), this, SLOT (addPathgridSubView()));
|
||||||
|
|
||||||
world->addSeparator(); // items that don't represent single record lists follow here
|
world->addSeparator();
|
||||||
|
|
||||||
|
QAction* regions = createMenuEntry(CSMWorld::UniversalId::Type_Regions, world, "document-world-regions");
|
||||||
|
connect (regions, SIGNAL (triggered()), this, SLOT (addRegionsSubView()));
|
||||||
|
|
||||||
QAction *regionMap = createMenuEntry(CSMWorld::UniversalId::Type_RegionMap, world, "document-world-regionmap");
|
QAction *regionMap = createMenuEntry(CSMWorld::UniversalId::Type_RegionMap, world, "document-world-regionmap");
|
||||||
connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView()));
|
connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView()));
|
||||||
|
@ -187,14 +193,19 @@ void CSVDoc::View::setupMechanicsMenu()
|
||||||
{
|
{
|
||||||
QMenu *mechanics = menuBar()->addMenu (tr ("Mechanics"));
|
QMenu *mechanics = menuBar()->addMenu (tr ("Mechanics"));
|
||||||
|
|
||||||
|
QAction* scripts = createMenuEntry(CSMWorld::UniversalId::Type_Scripts, mechanics, "document-mechanics-scripts");
|
||||||
|
connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView()));
|
||||||
|
|
||||||
|
QAction* startScripts = createMenuEntry(CSMWorld::UniversalId::Type_StartScripts, mechanics, "document-mechanics-startscripts");
|
||||||
|
connect (startScripts, SIGNAL (triggered()), this, SLOT (addStartScriptsSubView()));
|
||||||
|
|
||||||
QAction* globals = createMenuEntry(CSMWorld::UniversalId::Type_Globals, mechanics, "document-mechanics-globals");
|
QAction* globals = createMenuEntry(CSMWorld::UniversalId::Type_Globals, mechanics, "document-mechanics-globals");
|
||||||
connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView()));
|
connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView()));
|
||||||
|
|
||||||
QAction* gmsts = createMenuEntry(CSMWorld::UniversalId::Type_Gmsts, mechanics, "document-mechanics-gamesettings");
|
QAction* gmsts = createMenuEntry(CSMWorld::UniversalId::Type_Gmsts, mechanics, "document-mechanics-gamesettings");
|
||||||
connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView()));
|
connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView()));
|
||||||
|
|
||||||
QAction* scripts = createMenuEntry(CSMWorld::UniversalId::Type_Scripts, mechanics, "document-mechanics-scripts");
|
mechanics->addSeparator();
|
||||||
connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView()));
|
|
||||||
|
|
||||||
QAction* spells = createMenuEntry(CSMWorld::UniversalId::Type_Spells, mechanics, "document-mechanics-spells");
|
QAction* spells = createMenuEntry(CSMWorld::UniversalId::Type_Spells, mechanics, "document-mechanics-spells");
|
||||||
connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView()));
|
connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView()));
|
||||||
|
@ -204,9 +215,6 @@ void CSVDoc::View::setupMechanicsMenu()
|
||||||
|
|
||||||
QAction* magicEffects = createMenuEntry(CSMWorld::UniversalId::Type_MagicEffects, mechanics, "document-mechanics-magiceffects");
|
QAction* magicEffects = createMenuEntry(CSMWorld::UniversalId::Type_MagicEffects, mechanics, "document-mechanics-magiceffects");
|
||||||
connect (magicEffects, SIGNAL (triggered()), this, SLOT (addMagicEffectsSubView()));
|
connect (magicEffects, SIGNAL (triggered()), this, SLOT (addMagicEffectsSubView()));
|
||||||
|
|
||||||
QAction* startScripts = createMenuEntry(CSMWorld::UniversalId::Type_StartScripts, mechanics, "document-mechanics-startscripts");
|
|
||||||
connect (startScripts, SIGNAL (triggered()), this, SLOT (addStartScriptsSubView()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVDoc::View::setupCharacterMenu()
|
void CSVDoc::View::setupCharacterMenu()
|
||||||
|
@ -227,21 +235,25 @@ void CSVDoc::View::setupCharacterMenu()
|
||||||
|
|
||||||
QAction* birthsigns = createMenuEntry(CSMWorld::UniversalId::Type_Birthsigns, characters, "document-character-birthsigns");
|
QAction* birthsigns = createMenuEntry(CSMWorld::UniversalId::Type_Birthsigns, characters, "document-character-birthsigns");
|
||||||
connect (birthsigns, SIGNAL (triggered()), this, SLOT (addBirthsignsSubView()));
|
connect (birthsigns, SIGNAL (triggered()), this, SLOT (addBirthsignsSubView()));
|
||||||
|
|
||||||
|
QAction* bodyParts = createMenuEntry(CSMWorld::UniversalId::Type_BodyParts, characters, "document-character-bodyparts");
|
||||||
|
connect (bodyParts, SIGNAL (triggered()), this, SLOT (addBodyPartsSubView()));
|
||||||
|
|
||||||
|
characters->addSeparator();
|
||||||
|
|
||||||
QAction* topics = createMenuEntry(CSMWorld::UniversalId::Type_Topics, characters, "document-character-topics");
|
QAction* topics = createMenuEntry(CSMWorld::UniversalId::Type_Topics, characters, "document-character-topics");
|
||||||
connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView()));
|
connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView()));
|
||||||
|
|
||||||
|
QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos");
|
||||||
|
connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView()));
|
||||||
|
|
||||||
|
characters->addSeparator();
|
||||||
|
|
||||||
QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals");
|
QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals");
|
||||||
connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView()));
|
connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView()));
|
||||||
|
|
||||||
QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos");
|
|
||||||
connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView()));
|
|
||||||
|
|
||||||
QAction* journalInfos = createMenuEntry(CSMWorld::UniversalId::Type_JournalInfos, characters, "document-character-journalinfos");
|
QAction* journalInfos = createMenuEntry(CSMWorld::UniversalId::Type_JournalInfos, characters, "document-character-journalinfos");
|
||||||
connect (journalInfos, SIGNAL (triggered()), this, SLOT (addJournalInfosSubView()));
|
connect (journalInfos, SIGNAL (triggered()), this, SLOT (addJournalInfosSubView()));
|
||||||
|
|
||||||
QAction* bodyParts = createMenuEntry(CSMWorld::UniversalId::Type_BodyParts, characters, "document-character-bodyparts");
|
|
||||||
connect (bodyParts, SIGNAL (triggered()), this, SLOT (addBodyPartsSubView()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVDoc::View::setupAssetsMenu()
|
void CSVDoc::View::setupAssetsMenu()
|
||||||
|
|
|
@ -18,6 +18,8 @@ CSVRender::BrushDraw::BrushDraw(osg::ref_ptr<osg::Group> parentNode, bool textur
|
||||||
mBrushDrawNode = new osg::Group();
|
mBrushDrawNode = new osg::Group();
|
||||||
mGeometry = new osg::Geometry();
|
mGeometry = new osg::Geometry();
|
||||||
mBrushDrawNode->addChild(mGeometry);
|
mBrushDrawNode->addChild(mGeometry);
|
||||||
|
mBrushDrawNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
|
||||||
|
mBrushDrawNode->getOrCreateStateSet()->setRenderBinDetails(11, "RenderBin");
|
||||||
mParentNode->addChild(mBrushDrawNode);
|
mParentNode->addChild(mBrushDrawNode);
|
||||||
if (mTextureMode)
|
if (mTextureMode)
|
||||||
mLandSizeFactor = static_cast<float>(ESM::Land::REAL_SIZE) / static_cast<float>(ESM::Land::LAND_TEXTURE_SIZE);
|
mLandSizeFactor = static_cast<float>(ESM::Land::REAL_SIZE) / static_cast<float>(ESM::Land::LAND_TEXTURE_SIZE);
|
||||||
|
@ -122,7 +124,14 @@ void CSVRender::BrushDraw::buildSquareGeometry(const float& radius, const osg::V
|
||||||
const float brushOutlineHeight (1.0f);
|
const float brushOutlineHeight (1.0f);
|
||||||
float diameter = radius * 2;
|
float diameter = radius * 2;
|
||||||
int resolution = static_cast<int>(2.f * diameter / mLandSizeFactor); //half a vertex resolution
|
int resolution = static_cast<int>(2.f * diameter / mLandSizeFactor); //half a vertex resolution
|
||||||
float resAdjustedLandSizeFactor = mLandSizeFactor / 2;
|
float resAdjustedLandSizeFactor = mLandSizeFactor / 2; //128
|
||||||
|
|
||||||
|
if (resolution > 128) // limit accuracy for performance
|
||||||
|
{
|
||||||
|
resolution = 128;
|
||||||
|
resAdjustedLandSizeFactor = diameter / resolution;
|
||||||
|
}
|
||||||
|
|
||||||
osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f);
|
osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f);
|
||||||
|
|
||||||
for (int i = 0; i < resolution; i++)
|
for (int i = 0; i < resolution; i++)
|
||||||
|
@ -215,7 +224,8 @@ void CSVRender::BrushDraw::buildCircleGeometry(const float& radius, const osg::V
|
||||||
osg::ref_ptr<osg::Geometry> geom (new osg::Geometry());
|
osg::ref_ptr<osg::Geometry> geom (new osg::Geometry());
|
||||||
osg::ref_ptr<osg::Vec3Array> vertices (new osg::Vec3Array());
|
osg::ref_ptr<osg::Vec3Array> vertices (new osg::Vec3Array());
|
||||||
osg::ref_ptr<osg::Vec4Array> colors (new osg::Vec4Array());
|
osg::ref_ptr<osg::Vec4Array> colors (new osg::Vec4Array());
|
||||||
const int amountOfPoints = (osg::PI * 2.0f) * radius / 20;
|
|
||||||
|
const int amountOfPoints = 128;
|
||||||
const float step ((osg::PI * 2.0f) / static_cast<float>(amountOfPoints));
|
const float step ((osg::PI * 2.0f) / static_cast<float>(amountOfPoints));
|
||||||
const float brushOutlineHeight (1.0f);
|
const float brushOutlineHeight (1.0f);
|
||||||
osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f);
|
osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f);
|
||||||
|
|
|
@ -60,7 +60,7 @@ void CSVRender::CellArrow::adjustTransform()
|
||||||
{
|
{
|
||||||
// position
|
// position
|
||||||
const int cellSize = Constants::CellSizeInUnits;
|
const int cellSize = Constants::CellSizeInUnits;
|
||||||
const int offset = cellSize / 2 + 800;
|
const int offset = cellSize / 2 + 600;
|
||||||
|
|
||||||
int x = mCoordinates.getX()*cellSize + cellSize/2;
|
int x = mCoordinates.getX()*cellSize + cellSize/2;
|
||||||
int y = mCoordinates.getY()*cellSize + cellSize/2;
|
int y = mCoordinates.getY()*cellSize + cellSize/2;
|
||||||
|
@ -92,9 +92,9 @@ void CSVRender::CellArrow::buildShape()
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
|
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
|
||||||
|
|
||||||
const int arrowWidth = 4000;
|
const int arrowWidth = 2700;
|
||||||
const int arrowLength = 1500;
|
const int arrowLength = 1350;
|
||||||
const int arrowHeight = 500;
|
const int arrowHeight = 300;
|
||||||
|
|
||||||
osg::Vec3Array *vertices = new osg::Vec3Array;
|
osg::Vec3Array *vertices = new osg::Vec3Array;
|
||||||
for (int i2=0; i2<2; ++i2)
|
for (int i2=0; i2<2; ++i2)
|
||||||
|
|
|
@ -682,18 +682,20 @@ void CSVRender::Object::apply (CSMWorld::CommandMacro& commands)
|
||||||
|
|
||||||
int cellColumn = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (
|
int cellColumn = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (
|
||||||
CSMWorld::Columns::ColumnId_Cell));
|
CSMWorld::Columns::ColumnId_Cell));
|
||||||
int refNumColumn = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (
|
int origCellColumn = collection.findColumnIndex(static_cast<CSMWorld::Columns::ColumnId> (
|
||||||
CSMWorld::Columns::ColumnId_RefNum));
|
CSMWorld::Columns::ColumnId_OriginalCell));
|
||||||
|
|
||||||
if (cellIndex != originalIndex)
|
if (cellIndex != originalIndex)
|
||||||
{
|
{
|
||||||
/// \todo figure out worldspace (not important until multiple worldspaces are supported)
|
/// \todo figure out worldspace (not important until multiple worldspaces are supported)
|
||||||
|
std::string origCellId = CSMWorld::CellCoordinates(originalIndex).getId("");
|
||||||
std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId ("");
|
std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId ("");
|
||||||
|
|
||||||
commands.push (new CSMWorld::ModifyCommand (*model,
|
commands.push (new CSMWorld::ModifyCommand (*model,
|
||||||
model->index (recordIndex, cellColumn), QString::fromUtf8 (cellId.c_str())));
|
model->index (recordIndex, origCellColumn), QString::fromUtf8 (origCellId.c_str())));
|
||||||
commands.push (new CSMWorld::ModifyCommand( *model,
|
commands.push(new CSMWorld::ModifyCommand(*model,
|
||||||
model->index (recordIndex, refNumColumn), 0));
|
model->index(recordIndex, cellColumn), QString::fromUtf8(cellId.c_str())));
|
||||||
|
// NOTE: refnum is not modified for moving a reference to another cell
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef CSV_TOOLS_REPORTTABLE_H
|
#ifndef CSV_TOOLS_MERGE_H
|
||||||
#define CSV_TOOLS_REPORTTABLE_H
|
#define CSV_TOOLS_MERGE_H
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
|
|
@ -538,6 +538,9 @@ void CSVWorld::EditWidget::remake(int row)
|
||||||
mainLayout->addLayout(tablesLayout, QSizePolicy::Preferred);
|
mainLayout->addLayout(tablesLayout, QSizePolicy::Preferred);
|
||||||
mainLayout->addStretch(1);
|
mainLayout->addStretch(1);
|
||||||
|
|
||||||
|
int blockedColumn = mTable->searchColumnIndex(CSMWorld::Columns::ColumnId_Blocked);
|
||||||
|
bool isBlocked = mTable->data(mTable->index(row, blockedColumn)).toInt();
|
||||||
|
|
||||||
int unlocked = 0;
|
int unlocked = 0;
|
||||||
int locked = 0;
|
int locked = 0;
|
||||||
const int columns = mTable->columnCount();
|
const int columns = mTable->columnCount();
|
||||||
|
@ -583,6 +586,8 @@ void CSVWorld::EditWidget::remake(int row)
|
||||||
NestedTable* table =
|
NestedTable* table =
|
||||||
new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows);
|
new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows);
|
||||||
table->resizeColumnsToContents();
|
table->resizeColumnsToContents();
|
||||||
|
if (isBlocked)
|
||||||
|
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||||
|
|
||||||
int rows = mTable->rowCount(mTable->index(row, i));
|
int rows = mTable->rowCount(mTable->index(row, i));
|
||||||
int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0);
|
int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0);
|
||||||
|
@ -617,7 +622,9 @@ void CSVWorld::EditWidget::remake(int row)
|
||||||
label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed);
|
label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
editor->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
|
editor->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
|
||||||
|
|
||||||
if (! (mTable->flags (mTable->index (row, i)) & Qt::ItemIsEditable))
|
// HACK: the blocked checkbox needs to keep the same position
|
||||||
|
// FIXME: unfortunately blocked record displays a little differently to unblocked one
|
||||||
|
if (!(mTable->flags (mTable->index (row, i)) & Qt::ItemIsEditable) || i == blockedColumn)
|
||||||
{
|
{
|
||||||
lockedLayout->addWidget (label, locked, 0);
|
lockedLayout->addWidget (label, locked, 0);
|
||||||
lockedLayout->addWidget (editor, locked, 1);
|
lockedLayout->addWidget (editor, locked, 1);
|
||||||
|
@ -639,7 +646,7 @@ void CSVWorld::EditWidget::remake(int row)
|
||||||
createEditorContextMenu(editor, display, row);
|
createEditorContextMenu(editor, display, row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else // Flag_Dialogue_List
|
||||||
{
|
{
|
||||||
CSMWorld::IdTree *tree = static_cast<CSMWorld::IdTree *>(mTable);
|
CSMWorld::IdTree *tree = static_cast<CSMWorld::IdTree *>(mTable);
|
||||||
mNestedTableMapper = new QDataWidgetMapper (this);
|
mNestedTableMapper = new QDataWidgetMapper (this);
|
||||||
|
@ -686,7 +693,10 @@ void CSVWorld::EditWidget::remake(int row)
|
||||||
label->setEnabled(false);
|
label->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
createEditorContextMenu(editor, display, row);
|
if (!isBlocked)
|
||||||
|
createEditorContextMenu(editor, display, row);
|
||||||
|
else
|
||||||
|
editor->setEnabled(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mNestedTableMapper->setCurrentModelIndex(tree->index(0, 0, tree->index(row, i)));
|
mNestedTableMapper->setCurrentModelIndex(tree->index(0, 0, tree->index(row, i)));
|
||||||
|
|
|
@ -78,6 +78,8 @@ QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptio
|
||||||
for (std::vector<std::pair<int, QString> >::const_iterator iter (mValues.begin());
|
for (std::vector<std::pair<int, QString> >::const_iterator iter (mValues.begin());
|
||||||
iter!=mValues.end(); ++iter)
|
iter!=mValues.end(); ++iter)
|
||||||
comboBox->addItem (iter->second);
|
comboBox->addItem (iter->second);
|
||||||
|
|
||||||
|
comboBox->setMaxVisibleItems(20);
|
||||||
|
|
||||||
return comboBox;
|
return comboBox;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,8 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd
|
||||||
std::vector<CSMWorld::UniversalId::Type> types = CSMWorld::UniversalId::listReferenceableTypes();
|
std::vector<CSMWorld::UniversalId::Type> types = CSMWorld::UniversalId::listReferenceableTypes();
|
||||||
|
|
||||||
mType = new QComboBox (this);
|
mType = new QComboBox (this);
|
||||||
|
mType->setMaxVisibleItems(20);
|
||||||
|
|
||||||
for (std::vector<CSMWorld::UniversalId::Type>::const_iterator iter (types.begin());
|
for (std::vector<CSMWorld::UniversalId::Type>::const_iterator iter (types.begin());
|
||||||
iter!=types.end(); ++iter)
|
iter!=types.end(); ++iter)
|
||||||
{
|
{
|
||||||
|
@ -31,7 +32,9 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd
|
||||||
mType->addItem (QIcon (id2.getIcon().c_str()), id2.getTypeName().c_str(),
|
mType->addItem (QIcon (id2.getIcon().c_str()), id2.getTypeName().c_str(),
|
||||||
static_cast<int> (id2.getType()));
|
static_cast<int> (id2.getType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mType->model()->sort(0);
|
||||||
|
|
||||||
insertBeforeButtons (mType, false);
|
insertBeforeButtons (mType, false);
|
||||||
|
|
||||||
connect (mType, SIGNAL (currentIndexChanged (int)), this, SLOT (setType (int)));
|
connect (mType, SIGNAL (currentIndexChanged (int)), this, SLOT (setType (int)));
|
||||||
|
|
|
@ -32,7 +32,6 @@ namespace CSVWidget
|
||||||
|
|
||||||
namespace CSVWorld
|
namespace CSVWorld
|
||||||
{
|
{
|
||||||
class Table;
|
|
||||||
class TableBottomBox;
|
class TableBottomBox;
|
||||||
class CreatorFactoryBase;
|
class CreatorFactoryBase;
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include "../../model/prefs/shortcut.hpp"
|
#include "../../model/prefs/shortcut.hpp"
|
||||||
|
|
||||||
#include "tableeditidaction.hpp"
|
#include "tableeditidaction.hpp"
|
||||||
|
#include "tableheadermouseeventhandler.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
|
||||||
void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event)
|
void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event)
|
||||||
|
@ -422,6 +423,8 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
||||||
connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)),
|
connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)),
|
||||||
this, SLOT (settingChanged (const CSMPrefs::Setting *)));
|
this, SLOT (settingChanged (const CSMPrefs::Setting *)));
|
||||||
CSMPrefs::get()["ID Tables"].update();
|
CSMPrefs::get()["ID Tables"].update();
|
||||||
|
|
||||||
|
new TableHeaderMouseEventHandler(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVWorld::Table::setEditLock (bool locked)
|
void CSVWorld::Table::setEditLock (bool locked)
|
||||||
|
|
64
apps/opencs/view/world/tableheadermouseeventhandler.cpp
Normal file
64
apps/opencs/view/world/tableheadermouseeventhandler.cpp
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#include "tableheadermouseeventhandler.hpp"
|
||||||
|
#include "dragrecordtable.hpp"
|
||||||
|
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QPoint>
|
||||||
|
|
||||||
|
namespace CSVWorld
|
||||||
|
{
|
||||||
|
|
||||||
|
TableHeaderMouseEventHandler::TableHeaderMouseEventHandler(DragRecordTable * parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, table(*parent)
|
||||||
|
, header(*table.horizontalHeader())
|
||||||
|
{
|
||||||
|
header.setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
|
||||||
|
connect(
|
||||||
|
&header, &QHeaderView::customContextMenuRequested, [=](const QPoint & position) { showContextMenu(position); });
|
||||||
|
|
||||||
|
header.viewport()->installEventFilter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TableHeaderMouseEventHandler::eventFilter(QObject * tableWatched, QEvent * event)
|
||||||
|
{
|
||||||
|
if (event->type() == QEvent::Type::MouseButtonPress)
|
||||||
|
{
|
||||||
|
auto & clickEvent = static_cast<QMouseEvent &>(*event);
|
||||||
|
if ((clickEvent.button() == Qt::MiddleButton))
|
||||||
|
{
|
||||||
|
const auto & index = table.indexAt(clickEvent.pos());
|
||||||
|
table.setColumnHidden(index.column(), true);
|
||||||
|
clickEvent.accept();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TableHeaderMouseEventHandler::showContextMenu(const QPoint & position)
|
||||||
|
{
|
||||||
|
auto & menu{createContextMenu()};
|
||||||
|
menu.popup(header.viewport()->mapToGlobal(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu & TableHeaderMouseEventHandler::createContextMenu()
|
||||||
|
{
|
||||||
|
auto * menu = new QMenu(this);
|
||||||
|
for (int i = 0; i < table.model()->columnCount(); ++i)
|
||||||
|
{
|
||||||
|
const auto & name = table.model()->headerData(i, Qt::Horizontal, Qt::DisplayRole);
|
||||||
|
QAction * action{new QAction(name.toString(), this)};
|
||||||
|
action->setCheckable(true);
|
||||||
|
action->setChecked(!table.isColumnHidden(i));
|
||||||
|
menu->addAction(action);
|
||||||
|
|
||||||
|
connect(action, &QAction::triggered, [=]() {
|
||||||
|
table.setColumnHidden(i, !action->isChecked());
|
||||||
|
action->setChecked(!action->isChecked());
|
||||||
|
action->toggle();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return *menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace CSVWorld
|
25
apps/opencs/view/world/tableheadermouseeventhandler.hpp
Normal file
25
apps/opencs/view/world/tableheadermouseeventhandler.hpp
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QtGui>
|
||||||
|
|
||||||
|
namespace CSVWorld
|
||||||
|
{
|
||||||
|
class DragRecordTable;
|
||||||
|
|
||||||
|
class TableHeaderMouseEventHandler : public QWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit TableHeaderMouseEventHandler(DragRecordTable * parent);
|
||||||
|
|
||||||
|
void showContextMenu(const QPoint &);
|
||||||
|
|
||||||
|
private:
|
||||||
|
DragRecordTable & table;
|
||||||
|
QHeaderView & header;
|
||||||
|
|
||||||
|
QMenu & createContextMenu();
|
||||||
|
bool eventFilter(QObject *, QEvent *) override;
|
||||||
|
|
||||||
|
}; // class TableHeaderMouseEventHandler
|
||||||
|
} // namespace CSVWorld
|
|
@ -2,6 +2,7 @@
|
||||||
set(GAME
|
set(GAME
|
||||||
main.cpp
|
main.cpp
|
||||||
engine.cpp
|
engine.cpp
|
||||||
|
options.cpp
|
||||||
|
|
||||||
${CMAKE_SOURCE_DIR}/files/windows/openmw.rc
|
${CMAKE_SOURCE_DIR}/files/windows/openmw.rc
|
||||||
${CMAKE_SOURCE_DIR}/files/windows/openmw.exe.manifest
|
${CMAKE_SOURCE_DIR}/files/windows/openmw.exe.manifest
|
||||||
|
@ -58,7 +59,7 @@ add_openmw_dir (mwscript
|
||||||
add_openmw_dir (mwlua
|
add_openmw_dir (mwlua
|
||||||
luamanagerimp actions object worldview userdataserializer eventqueue query
|
luamanagerimp actions object worldview userdataserializer eventqueue query
|
||||||
luabindings localscripts objectbindings cellbindings asyncbindings settingsbindings
|
luabindings localscripts objectbindings cellbindings asyncbindings settingsbindings
|
||||||
camerabindings uibindings inputbindings
|
camerabindings uibindings inputbindings nearbybindings
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwsound
|
add_openmw_dir (mwsound
|
||||||
|
@ -92,8 +93,8 @@ add_openmw_dir (mwmechanics
|
||||||
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe
|
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe
|
||||||
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 tickableeffects
|
character actors objects aistate trading weaponpriority spellpriority weapontype spellutil
|
||||||
spellabsorption linkedeffects
|
spellabsorption spelleffects
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwstate
|
add_openmw_dir (mwstate
|
||||||
|
|
|
@ -875,7 +875,14 @@ public:
|
||||||
void join()
|
void join()
|
||||||
{
|
{
|
||||||
if (mThread)
|
if (mThread)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(mMutex);
|
||||||
|
mJoinRequest = true;
|
||||||
|
}
|
||||||
|
mCV.notify_one();
|
||||||
mThread->join();
|
mThread->join();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -891,10 +898,12 @@ private:
|
||||||
|
|
||||||
void threadBody()
|
void threadBody()
|
||||||
{
|
{
|
||||||
while (!mEngine->mViewer->done() && !mEngine->mEnvironment.getStateManager()->hasQuitRequest())
|
while (true)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lk(mMutex);
|
std::unique_lock<std::mutex> lk(mMutex);
|
||||||
mCV.wait(lk, [&]{ return mUpdateRequest; });
|
mCV.wait(lk, [&]{ return mUpdateRequest || mJoinRequest; });
|
||||||
|
if (mJoinRequest)
|
||||||
|
break;
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
|
||||||
|
@ -908,6 +917,7 @@ private:
|
||||||
std::mutex mMutex;
|
std::mutex mMutex;
|
||||||
std::condition_variable mCV;
|
std::condition_variable mCV;
|
||||||
bool mUpdateRequest = false;
|
bool mUpdateRequest = false;
|
||||||
|
bool mJoinRequest = false;
|
||||||
double mDt = 0;
|
double mDt = 0;
|
||||||
bool mIsGuiMode = false;
|
bool mIsGuiMode = false;
|
||||||
std::optional<std::thread> mThread;
|
std::optional<std::thread> mThread;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <components/misc/rng.hpp>
|
#include <components/misc/rng.hpp>
|
||||||
|
|
||||||
#include "engine.hpp"
|
#include "engine.hpp"
|
||||||
|
#include "options.hpp"
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
@ -39,108 +40,11 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
||||||
namespace bpo = boost::program_options;
|
namespace bpo = boost::program_options;
|
||||||
typedef std::vector<std::string> StringsVector;
|
typedef std::vector<std::string> StringsVector;
|
||||||
|
|
||||||
bpo::options_description desc("Syntax: openmw <options>\nAllowed options");
|
bpo::options_description desc = OpenMW::makeOptionsDescription();
|
||||||
|
|
||||||
desc.add_options()
|
|
||||||
("help", "print help message")
|
|
||||||
("version", "print version information and quit")
|
|
||||||
|
|
||||||
("replace", bpo::value<StringsVector>()->default_value(StringsVector(), "")
|
|
||||||
->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended")
|
|
||||||
|
|
||||||
("data", bpo::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")
|
|
||||||
->multitoken()->composing(), "set data directories (later directories have higher priority)")
|
|
||||||
|
|
||||||
("data-local", bpo::value<Files::PathContainer::value_type>()->default_value(Files::PathContainer::value_type(), ""),
|
|
||||||
"set local data directory (highest priority)")
|
|
||||||
|
|
||||||
("fallback-archive", bpo::value<StringsVector>()->default_value(StringsVector(), "fallback-archive")
|
|
||||||
->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)")
|
|
||||||
|
|
||||||
("resources", bpo::value<boost::filesystem::path>()->default_value(boost::filesystem::path(), "resources"),
|
|
||||||
"set resources directory")
|
|
||||||
|
|
||||||
("start", bpo::value<std::string>()->default_value(""),
|
|
||||||
"set initial cell")
|
|
||||||
|
|
||||||
("content", bpo::value<StringsVector>()->default_value(StringsVector(), "")
|
|
||||||
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon")
|
|
||||||
|
|
||||||
("groundcover", bpo::value<StringsVector>()->default_value(StringsVector(), "")
|
|
||||||
->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon")
|
|
||||||
|
|
||||||
("lua-scripts", bpo::value<StringsVector>()->default_value(StringsVector(), "")
|
|
||||||
->multitoken()->composing(), "file(s) with a list of global Lua scripts: omwscripts")
|
|
||||||
|
|
||||||
("no-sound", bpo::value<bool>()->implicit_value(true)
|
|
||||||
->default_value(false), "disable all sounds")
|
|
||||||
|
|
||||||
("script-all", bpo::value<bool>()->implicit_value(true)
|
|
||||||
->default_value(false), "compile all scripts (excluding dialogue scripts) at startup")
|
|
||||||
|
|
||||||
("script-all-dialogue", bpo::value<bool>()->implicit_value(true)
|
|
||||||
->default_value(false), "compile all dialogue scripts at startup")
|
|
||||||
|
|
||||||
("script-console", bpo::value<bool>()->implicit_value(true)
|
|
||||||
->default_value(false), "enable console-only script functionality")
|
|
||||||
|
|
||||||
("script-run", bpo::value<std::string>()->default_value(""),
|
|
||||||
"select a file containing a list of console commands that is executed on startup")
|
|
||||||
|
|
||||||
("script-warn", bpo::value<int>()->implicit_value (1)
|
|
||||||
->default_value (1),
|
|
||||||
"handling of warnings when compiling scripts\n"
|
|
||||||
"\t0 - ignore warning\n"
|
|
||||||
"\t1 - show warning but consider script as correctly compiled anyway\n"
|
|
||||||
"\t2 - treat warnings as errors")
|
|
||||||
|
|
||||||
("script-blacklist", bpo::value<StringsVector>()->default_value(StringsVector(), "")
|
|
||||||
->multitoken()->composing(), "ignore the specified script (if the use of the blacklist is enabled)")
|
|
||||||
|
|
||||||
("script-blacklist-use", bpo::value<bool>()->implicit_value(true)
|
|
||||||
->default_value(true), "enable script blacklisting")
|
|
||||||
|
|
||||||
("load-savegame", bpo::value<boost::filesystem::path>()->default_value(boost::filesystem::path(), ""),
|
|
||||||
"load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory)")
|
|
||||||
|
|
||||||
("skip-menu", bpo::value<bool>()->implicit_value(true)
|
|
||||||
->default_value(false), "skip main menu on game startup")
|
|
||||||
|
|
||||||
("new-game", bpo::value<bool>()->implicit_value(true)
|
|
||||||
->default_value(false), "run new game sequence (ignored if skip-menu=0)")
|
|
||||||
|
|
||||||
("fs-strict", bpo::value<bool>()->implicit_value(true)
|
|
||||||
->default_value(false), "strict file system handling (no case folding)")
|
|
||||||
|
|
||||||
("encoding", bpo::value<std::string>()->
|
|
||||||
default_value("win1252"),
|
|
||||||
"Character encoding used in OpenMW game messages:\n"
|
|
||||||
"\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"
|
|
||||||
"\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
|
|
||||||
"\n\twin1252 - Western European (Latin) alphabet, used by default")
|
|
||||||
|
|
||||||
("fallback", bpo::value<FallbackMap>()->default_value(FallbackMap(), "")
|
|
||||||
->multitoken()->composing(), "fallback values")
|
|
||||||
|
|
||||||
("no-grab", bpo::value<bool>()->implicit_value(true)->default_value(false), "Don't grab mouse cursor")
|
|
||||||
|
|
||||||
("export-fonts", bpo::value<bool>()->implicit_value(true)
|
|
||||||
->default_value(false), "Export Morrowind .fnt fonts to PNG image and XML file in current directory")
|
|
||||||
|
|
||||||
("activate-dist", bpo::value <int> ()->default_value (-1), "activation distance override")
|
|
||||||
|
|
||||||
("random-seed", bpo::value <unsigned int> ()
|
|
||||||
->default_value(Misc::Rng::generateDefaultSeed()),
|
|
||||||
"seed value for random number generator")
|
|
||||||
;
|
|
||||||
|
|
||||||
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv)
|
|
||||||
.options(desc).allow_unregistered().run();
|
|
||||||
|
|
||||||
bpo::variables_map variables;
|
bpo::variables_map variables;
|
||||||
|
|
||||||
// Runtime options override settings from all configs
|
Files::parseArgs(argc, argv, variables, desc);
|
||||||
bpo::store(valid_opts, variables);
|
|
||||||
bpo::notify(variables);
|
bpo::notify(variables);
|
||||||
|
|
||||||
if (variables.count ("help"))
|
if (variables.count ("help"))
|
||||||
|
@ -158,9 +62,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bpo::variables_map composingVariables = cfgMgr.separateComposingVariables(variables, desc);
|
bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc);
|
||||||
cfgMgr.readConfiguration(variables, desc);
|
cfgMgr.readConfiguration(variables, desc);
|
||||||
cfgMgr.mergeComposingVariables(variables, composingVariables, desc);
|
Files::mergeComposingVariables(variables, composingVariables, desc);
|
||||||
|
|
||||||
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<boost::filesystem::path>().string());
|
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<boost::filesystem::path>().string());
|
||||||
Log(Debug::Info) << v.describe();
|
Log(Debug::Info) << v.describe();
|
||||||
|
|
|
@ -58,7 +58,7 @@ namespace MWBase
|
||||||
virtual void add (const MWWorld::Ptr& ptr) = 0;
|
virtual void add (const MWWorld::Ptr& ptr) = 0;
|
||||||
///< Register an object for management
|
///< Register an object for management
|
||||||
|
|
||||||
virtual void remove (const MWWorld::Ptr& ptr) = 0;
|
virtual void remove (const MWWorld::Ptr& ptr, bool keepActive) = 0;
|
||||||
///< Deregister an object for management
|
///< Deregister an object for management
|
||||||
|
|
||||||
virtual void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) = 0;
|
virtual void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) = 0;
|
||||||
|
@ -277,8 +277,6 @@ namespace MWBase
|
||||||
virtual float getAngleToPlayer(const MWWorld::Ptr& ptr) const = 0;
|
virtual float getAngleToPlayer(const MWWorld::Ptr& ptr) const = 0;
|
||||||
virtual MWMechanics::GreetingState getGreetingState(const MWWorld::Ptr& ptr) const = 0;
|
virtual MWMechanics::GreetingState getGreetingState(const MWWorld::Ptr& ptr) const = 0;
|
||||||
virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const = 0;
|
virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const = 0;
|
||||||
|
|
||||||
virtual void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) = 0;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -286,7 +286,7 @@ namespace MWBase
|
||||||
virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, const osg::Vec3f& position, bool movePhysics=true, bool moveToActive=false) = 0;
|
virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, const osg::Vec3f& position, bool movePhysics=true, bool moveToActive=false) = 0;
|
||||||
///< @return an updated Ptr in case the Ptr's cell changes
|
///< @return an updated Ptr in case the Ptr's cell changes
|
||||||
|
|
||||||
virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true) = 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, bool moveToActive, bool ignoreCollisions) = 0;
|
||||||
|
@ -539,7 +539,7 @@ namespace MWBase
|
||||||
|
|
||||||
virtual void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) = 0;
|
virtual void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) = 0;
|
||||||
|
|
||||||
virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0;
|
virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) = 0;
|
||||||
virtual void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile,
|
virtual void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile,
|
||||||
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 0;
|
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 0;
|
||||||
virtual void updateProjectilesCasters() = 0;
|
virtual void updateProjectilesCasters() = 0;
|
||||||
|
@ -591,7 +591,7 @@ namespace MWBase
|
||||||
|
|
||||||
virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster,
|
virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster,
|
||||||
const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id,
|
const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id,
|
||||||
const std::string& sourceName, const bool fromProjectile=false) = 0;
|
const std::string& sourceName, const bool fromProjectile=false, int slot = 0) = 0;
|
||||||
|
|
||||||
virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0;
|
virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0;
|
||||||
|
|
||||||
|
@ -653,7 +653,8 @@ namespace MWBase
|
||||||
|
|
||||||
virtual bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0;
|
virtual bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0;
|
||||||
|
|
||||||
virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const = 0;
|
virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
||||||
|
const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr>* occupyingActors = nullptr) const = 0;
|
||||||
|
|
||||||
virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0;
|
virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0;
|
||||||
|
|
||||||
|
|
|
@ -38,15 +38,15 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const
|
void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const
|
||||||
{
|
{
|
||||||
insertObjectPhysics(ptr, model, rotation, physics, skipAnimated);
|
insertObjectPhysics(ptr, model, rotation, physics);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Activator::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const
|
void Activator::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const
|
||||||
{
|
{
|
||||||
if(!model.empty())
|
if(!model.empty())
|
||||||
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World, skipAnimated);
|
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const
|
std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const
|
||||||
|
|
|
@ -17,9 +17,9 @@ namespace MWClass
|
||||||
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
|
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
|
||||||
///< Add reference into a cell for rendering
|
///< Add reference into a cell for rendering
|
||||||
|
|
||||||
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override;
|
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override;
|
||||||
|
|
||||||
void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const override;
|
void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override;
|
||||||
|
|
||||||
std::string getName (const MWWorld::ConstPtr& ptr) const override;
|
std::string getName (const MWWorld::ConstPtr& ptr) const override;
|
||||||
///< \return name or ID; can return an empty string.
|
///< \return name or ID; can return an empty string.
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace MWClass
|
||||||
MWBase::Environment::get().getWorld()->adjustPosition(ptr, force);
|
MWBase::Environment::get().getWorld()->adjustPosition(ptr, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const
|
void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const
|
||||||
{
|
{
|
||||||
if (!model.empty())
|
if (!model.empty())
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace MWClass
|
||||||
///< Adjust position to stand on ground. Must be called post model load
|
///< Adjust position to stand on ground. Must be called post model load
|
||||||
/// @param force do this even if the ptr is flying
|
/// @param force do this even if the ptr is flying
|
||||||
|
|
||||||
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override;
|
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override;
|
||||||
|
|
||||||
bool useAnim() const override;
|
bool useAnim() const override;
|
||||||
|
|
||||||
|
|
|
@ -106,15 +106,15 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const
|
void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const
|
||||||
{
|
{
|
||||||
insertObjectPhysics(ptr, model, rotation, physics, skipAnimated);
|
insertObjectPhysics(ptr, model, rotation, physics);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Container::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const
|
void Container::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const
|
||||||
{
|
{
|
||||||
if(!model.empty())
|
if(!model.empty())
|
||||||
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World, skipAnimated);
|
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Container::getModel(const MWWorld::ConstPtr &ptr) const
|
std::string Container::getModel(const MWWorld::ConstPtr &ptr) const
|
||||||
|
|
|
@ -42,8 +42,8 @@ namespace MWClass
|
||||||
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
|
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
|
||||||
///< Add reference into a cell for rendering
|
///< Add reference into a cell for rendering
|
||||||
|
|
||||||
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override;
|
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override;
|
||||||
void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override;
|
void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override;
|
||||||
|
|
||||||
std::string getName (const MWWorld::ConstPtr& ptr) const override;
|
std::string getName (const MWWorld::ConstPtr& ptr) const override;
|
||||||
///< \return name or ID; can return an empty string.
|
///< \return name or ID; can return an empty string.
|
||||||
|
|
|
@ -846,6 +846,7 @@ namespace MWClass
|
||||||
|
|
||||||
// Reset to original position
|
// Reset to original position
|
||||||
MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3());
|
MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3());
|
||||||
|
MWBase::Environment::get().getWorld()->rotateObject(ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,9 +55,9 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const
|
void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const
|
||||||
{
|
{
|
||||||
insertObjectPhysics(ptr, model, rotation, physics, skipAnimated);
|
insertObjectPhysics(ptr, model, rotation, physics);
|
||||||
|
|
||||||
// Resume the door's opening/closing animation if it wasn't finished
|
// Resume the door's opening/closing animation if it wasn't finished
|
||||||
if (ptr.getRefData().getCustomData())
|
if (ptr.getRefData().getCustomData())
|
||||||
|
@ -70,10 +70,10 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Door::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const
|
void Door::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const
|
||||||
{
|
{
|
||||||
if(!model.empty())
|
if(!model.empty())
|
||||||
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door, skipAnimated);
|
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Door::isDoor() const
|
bool Door::isDoor() const
|
||||||
|
|
|
@ -18,8 +18,8 @@ namespace MWClass
|
||||||
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
|
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
|
||||||
///< Add reference into a cell for rendering
|
///< Add reference into a cell for rendering
|
||||||
|
|
||||||
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override;
|
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override;
|
||||||
void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override;
|
void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override;
|
||||||
|
|
||||||
bool isDoor() const override;
|
bool isDoor() const override;
|
||||||
|
|
||||||
|
|
|
@ -33,13 +33,13 @@ namespace MWClass
|
||||||
renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault));
|
renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const
|
void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const
|
||||||
{
|
{
|
||||||
MWWorld::LiveCellRef<ESM::Light> *ref =
|
MWWorld::LiveCellRef<ESM::Light> *ref =
|
||||||
ptr.get<ESM::Light>();
|
ptr.get<ESM::Light>();
|
||||||
assert (ref->mBase != nullptr);
|
assert (ref->mBase != nullptr);
|
||||||
|
|
||||||
insertObjectPhysics(ptr, model, rotation, physics, skipAnimated);
|
insertObjectPhysics(ptr, model, rotation, physics);
|
||||||
|
|
||||||
if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault))
|
if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault))
|
||||||
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0,
|
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0,
|
||||||
|
@ -47,11 +47,11 @@ namespace MWClass
|
||||||
MWSound::PlayMode::Loop);
|
MWSound::PlayMode::Loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Light::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const
|
void Light::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const
|
||||||
{
|
{
|
||||||
// TODO: add option somewhere to enable collision for placeable objects
|
// TODO: add option somewhere to enable collision for placeable objects
|
||||||
if (!model.empty() && (ptr.get<ESM::Light>()->mBase->mData.mFlags & ESM::Light::Carry) == 0)
|
if (!model.empty() && (ptr.get<ESM::Light>()->mBase->mData.mFlags & ESM::Light::Carry) == 0)
|
||||||
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World, skipAnimated);
|
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Light::useAnim() const
|
bool Light::useAnim() const
|
||||||
|
|
|
@ -14,8 +14,8 @@ namespace MWClass
|
||||||
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
|
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
|
||||||
///< Add reference into a cell for rendering
|
///< Add reference into a cell for rendering
|
||||||
|
|
||||||
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override;
|
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override;
|
||||||
void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override;
|
void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override;
|
||||||
|
|
||||||
bool useAnim() const override;
|
bool useAnim() const override;
|
||||||
|
|
||||||
|
|
|
@ -1397,6 +1397,7 @@ namespace MWClass
|
||||||
|
|
||||||
// Reset to original position
|
// Reset to original position
|
||||||
MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3());
|
MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3());
|
||||||
|
MWBase::Environment::get().getWorld()->rotateObject(ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,15 +23,15 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const
|
void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const
|
||||||
{
|
{
|
||||||
insertObjectPhysics(ptr, model, rotation, physics, skipAnimated);
|
insertObjectPhysics(ptr, model, rotation, physics);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Static::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const
|
void Static::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const
|
||||||
{
|
{
|
||||||
if(!model.empty())
|
if(!model.empty())
|
||||||
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World, skipAnimated);
|
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Static::getModel(const MWWorld::ConstPtr &ptr) const
|
std::string Static::getModel(const MWWorld::ConstPtr &ptr) const
|
||||||
|
|
|
@ -14,8 +14,8 @@ namespace MWClass
|
||||||
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
|
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
|
||||||
///< Add reference into a cell for rendering
|
///< Add reference into a cell for rendering
|
||||||
|
|
||||||
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override;
|
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override;
|
||||||
void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override;
|
void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override;
|
||||||
|
|
||||||
std::string getName (const MWWorld::ConstPtr& ptr) const override;
|
std::string getName (const MWWorld::ConstPtr& ptr) const override;
|
||||||
///< \return name or ID; can return an empty string.
|
///< \return name or ID; can return an empty string.
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#ifndef GAME_MWDIALOGUE_KEYWORDSEARCH_H
|
#ifndef GAME_MWDIALOGUE_KEYWORDSEARCH_H
|
||||||
#define GAME_MWDIALOGUE_KEYWORDSEARCH_H
|
#define GAME_MWDIALOGUE_KEYWORDSEARCH_H
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <map>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <algorithm> // std::reverse
|
#include <algorithm> // std::reverse
|
||||||
|
@ -68,6 +68,7 @@ public:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool sortMatches(const Match& left, const Match& right)
|
static bool sortMatches(const Match& left, const Match& right)
|
||||||
{
|
{
|
||||||
return left.mBeg < right.mBeg;
|
return left.mBeg < right.mBeg;
|
||||||
|
@ -78,16 +79,6 @@ public:
|
||||||
std::vector<Match> matches;
|
std::vector<Match> matches;
|
||||||
for (Point i = beg; i != end; ++i)
|
for (Point i = beg; i != end; ++i)
|
||||||
{
|
{
|
||||||
// check if previous character marked start of new word
|
|
||||||
if (i != beg)
|
|
||||||
{
|
|
||||||
Point prev = i;
|
|
||||||
--prev;
|
|
||||||
if(isalpha(*prev))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// check first character
|
// check first character
|
||||||
typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i));
|
typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i));
|
||||||
|
|
||||||
|
|
|
@ -72,13 +72,6 @@ namespace
|
||||||
return {question, {r2, r1, r0}, sound};
|
return {question, {r2, r1, r0}, sound};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updatePlayerHealth()
|
|
||||||
{
|
|
||||||
MWWorld::Ptr player = MWMechanics::getPlayer();
|
|
||||||
MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player);
|
|
||||||
npcStats.updateHealth();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
|
@ -372,8 +365,6 @@ namespace MWGui
|
||||||
MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog);
|
MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog);
|
||||||
mPickClassDialog = nullptr;
|
mPickClassDialog = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePlayerHealth();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow)
|
void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow)
|
||||||
|
@ -448,8 +439,6 @@ namespace MWGui
|
||||||
MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog);
|
MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog);
|
||||||
mRaceDialog = nullptr;
|
mRaceDialog = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePlayerHealth();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterCreation::onRaceDialogBack()
|
void CharacterCreation::onRaceDialogBack()
|
||||||
|
@ -477,8 +466,6 @@ namespace MWGui
|
||||||
MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog);
|
MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog);
|
||||||
mBirthSignDialog = nullptr;
|
mBirthSignDialog = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePlayerHealth();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow)
|
void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow)
|
||||||
|
@ -527,7 +514,6 @@ namespace MWGui
|
||||||
// Do not delete dialog, so that choices are remembered in case we want to go back and adjust them later
|
// Do not delete dialog, so that choices are remembered in case we want to go back and adjust them later
|
||||||
mCreateClassDialog->setVisible(false);
|
mCreateClassDialog->setVisible(false);
|
||||||
}
|
}
|
||||||
updatePlayerHealth();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow)
|
void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow)
|
||||||
|
@ -719,8 +705,6 @@ namespace MWGui
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(mGenerateClass);
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(mGenerateClass);
|
||||||
|
|
||||||
mPlayerClass = *klass;
|
mPlayerClass = *klass;
|
||||||
|
|
||||||
updatePlayerHealth();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterCreation::onGenerateClassBack()
|
void CharacterCreation::onGenerateClassBack()
|
||||||
|
|
|
@ -262,7 +262,7 @@ namespace MWGui
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up summoned creatures as well
|
// Clean up summoned creatures as well
|
||||||
std::map<ESM::SummonKey, int>& creatureMap = creatureStats.getSummonedCreatureMap();
|
auto& creatureMap = creatureStats.getSummonedCreatureMap();
|
||||||
for (const auto& creature : creatureMap)
|
for (const auto& creature : creatureMap)
|
||||||
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(ptr, creature.second);
|
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(ptr, creature.second);
|
||||||
creatureMap.clear();
|
creatureMap.clear();
|
||||||
|
@ -277,8 +277,9 @@ namespace MWGui
|
||||||
auto it = std::find_if(summons.begin(), summons.end(), [&] (const auto& entry) { return entry.second == creatureStats.getActorId(); });
|
auto it = std::find_if(summons.begin(), summons.end(), [&] (const auto& entry) { return entry.second == creatureStats.getActorId(); });
|
||||||
if(it != summons.end())
|
if(it != summons.end())
|
||||||
{
|
{
|
||||||
MWMechanics::purgeSummonEffect(summoner, *it);
|
auto summon = *it;
|
||||||
summons.erase(it);
|
summons.erase(it);
|
||||||
|
MWMechanics::purgeSummonEffect(summoner, summon);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,10 +82,7 @@ namespace MWGui
|
||||||
MWBase::Environment::get().getWorld()->advanceTime(mDays * 24);
|
MWBase::Environment::get().getWorld()->advanceTime(mDays * 24);
|
||||||
|
|
||||||
// We should not worsen corprus when in prison
|
// We should not worsen corprus when in prison
|
||||||
for (auto& spell : player.getClass().getCreatureStats(player).getCorprusSpells())
|
player.getClass().getCreatureStats(player).getActiveSpells().skipWorsenings(mDays * 24);
|
||||||
{
|
|
||||||
spell.second.mNextWorsening += mDays * 24;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::set<int> skills;
|
std::set<int> skills;
|
||||||
for (int day=0; day<mDays; ++day)
|
for (int day=0; day<mDays; ++day)
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <MyGUI_Gui.h>
|
#include <MyGUI_Gui.h>
|
||||||
#include <MyGUI_TextBox.h>
|
#include <MyGUI_TextBox.h>
|
||||||
|
|
||||||
|
#include <components/misc/pathhelpers.hpp>
|
||||||
#include <components/misc/rng.hpp>
|
#include <components/misc/rng.hpp>
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/myguiplatform/myguitexture.hpp>
|
#include <components/myguiplatform/myguitexture.hpp>
|
||||||
|
@ -66,35 +67,15 @@ namespace MWGui
|
||||||
|
|
||||||
void LoadingScreen::findSplashScreens()
|
void LoadingScreen::findSplashScreens()
|
||||||
{
|
{
|
||||||
const std::map<std::string, VFS::File*>& index = mResourceSystem->getVFS()->getIndex();
|
auto isSupportedExtension = [](const std::string_view& ext) {
|
||||||
std::string pattern = "Splash/";
|
static const std::array<std::string, 7> supported_extensions{ {"tga", "dds", "ktx", "png", "bmp", "jpeg", "jpg"} };
|
||||||
mResourceSystem->getVFS()->normalizeFilename(pattern);
|
return !ext.empty() && std::find(supported_extensions.begin(), supported_extensions.end(), ext) != supported_extensions.end();
|
||||||
|
};
|
||||||
|
|
||||||
/* priority given to the left */
|
for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator("Splash/"))
|
||||||
const std::array<std::string, 7> supported_extensions {{".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}};
|
|
||||||
|
|
||||||
auto found = index.lower_bound(pattern);
|
|
||||||
while (found != index.end())
|
|
||||||
{
|
{
|
||||||
const std::string& name = found->first;
|
if (isSupportedExtension(Misc::getFileExtension(name)))
|
||||||
if (name.size() >= pattern.size() && name.substr(0, pattern.size()) == pattern)
|
mSplashScreens.push_back(name);
|
||||||
{
|
|
||||||
size_t pos = name.find_last_of('.');
|
|
||||||
if (pos != std::string::npos)
|
|
||||||
{
|
|
||||||
for(auto const& extension: supported_extensions)
|
|
||||||
{
|
|
||||||
if (name.compare(pos, name.size() - pos, extension) == 0)
|
|
||||||
{
|
|
||||||
mSplashScreens.push_back(found->first);
|
|
||||||
break; /* based on priority */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
++found;
|
|
||||||
}
|
}
|
||||||
if (mSplashScreens.empty())
|
if (mSplashScreens.empty())
|
||||||
Log(Debug::Warning) << "Warning: no splash screens found!";
|
Log(Debug::Warning) << "Warning: no splash screens found!";
|
||||||
|
|
|
@ -99,10 +99,8 @@ namespace MWGui
|
||||||
|
|
||||||
std::vector<const ESM::Spell*> spellsToSort;
|
std::vector<const ESM::Spell*> spellsToSort;
|
||||||
|
|
||||||
for (MWMechanics::Spells::TIterator iter = merchantSpells.begin(); iter!=merchantSpells.end(); ++iter)
|
for (const ESM::Spell* spell : merchantSpells)
|
||||||
{
|
{
|
||||||
const ESM::Spell* spell = iter->first;
|
|
||||||
|
|
||||||
if (spell->mData.mType!=ESM::Spell::ST_Spell)
|
if (spell->mData.mType!=ESM::Spell::ST_Spell)
|
||||||
continue; // don't try to sell diseases, curses or powers
|
continue; // don't try to sell diseases, curses or powers
|
||||||
|
|
||||||
|
@ -115,10 +113,10 @@ namespace MWGui
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerHasSpell(iter->first->mId))
|
if (playerHasSpell(spell->mId))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
spellsToSort.push_back(iter->first);
|
spellsToSort.push_back(spell);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::stable_sort(spellsToSort.begin(), spellsToSort.end(), sortSpells);
|
std::stable_sort(spellsToSort.begin(), spellsToSort.end(), sortSpells);
|
||||||
|
|
|
@ -520,10 +520,8 @@ namespace MWGui
|
||||||
|
|
||||||
std::vector<short> knownEffects;
|
std::vector<short> knownEffects;
|
||||||
|
|
||||||
for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
|
for (const ESM::Spell* spell : spells)
|
||||||
{
|
{
|
||||||
const ESM::Spell* spell = it->first;
|
|
||||||
|
|
||||||
// only normal spells count
|
// only normal spells count
|
||||||
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -24,50 +24,33 @@
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
|
|
||||||
void EffectSourceVisitor::visit (MWMechanics::EffectKey key, int effectIndex,
|
|
||||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
|
||||||
float magnitude, float remainingTime, float totalTime)
|
|
||||||
{
|
|
||||||
MagicEffectInfo newEffectSource;
|
|
||||||
newEffectSource.mKey = key;
|
|
||||||
newEffectSource.mMagnitude = static_cast<int>(magnitude);
|
|
||||||
newEffectSource.mPermanent = mIsPermanent;
|
|
||||||
newEffectSource.mRemainingTime = remainingTime;
|
|
||||||
newEffectSource.mSource = sourceName;
|
|
||||||
newEffectSource.mTotalTime = totalTime;
|
|
||||||
|
|
||||||
mEffectSources[key.mId].push_back(newEffectSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize)
|
void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize)
|
||||||
{
|
{
|
||||||
// TODO: Tracking add/remove/expire would be better than force updating every frame
|
|
||||||
|
|
||||||
MWWorld::Ptr player = MWMechanics::getPlayer();
|
MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||||
const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
|
const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
|
||||||
|
|
||||||
|
std::map<int, std::vector<MagicEffectInfo>> effects;
|
||||||
EffectSourceVisitor visitor;
|
for(const auto& params : stats.getActiveSpells())
|
||||||
|
{
|
||||||
// permanent item enchantments & permanent spells
|
for(const auto& effect : params.getEffects())
|
||||||
visitor.mIsPermanent = true;
|
{
|
||||||
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
|
if(!(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
|
||||||
store.visitEffectSources(visitor);
|
continue;
|
||||||
stats.getSpells().visitEffectSources(visitor);
|
MagicEffectInfo newEffectSource;
|
||||||
|
newEffectSource.mKey = MWMechanics::EffectKey(effect.mEffectId, effect.mArg);
|
||||||
// now add lasting effects
|
newEffectSource.mMagnitude = static_cast<int>(effect.mMagnitude);
|
||||||
visitor.mIsPermanent = false;
|
newEffectSource.mPermanent = effect.mDuration == -1.f;
|
||||||
stats.getActiveSpells().visitEffectSources(visitor);
|
newEffectSource.mRemainingTime = effect.mTimeLeft;
|
||||||
|
newEffectSource.mSource = params.getDisplayName();
|
||||||
std::map <int, std::vector<MagicEffectInfo> >& effects = visitor.mEffectSources;
|
newEffectSource.mTotalTime = effect.mDuration;
|
||||||
|
effects[effect.mEffectId].push_back(newEffectSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int w=2;
|
int w=2;
|
||||||
|
|
||||||
for (auto& effectInfoPair : effects)
|
for (const auto& [effectId, effectInfos] : effects)
|
||||||
{
|
{
|
||||||
const int effectId = effectInfoPair.first;
|
|
||||||
const ESM::MagicEffect* effect =
|
const ESM::MagicEffect* effect =
|
||||||
MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(effectId);
|
MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(effectId);
|
||||||
|
|
||||||
|
@ -78,7 +61,6 @@ namespace MWGui
|
||||||
|
|
||||||
static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fMagicStartIconBlink")->mValue.getFloat();
|
static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fMagicStartIconBlink")->mValue.getFloat();
|
||||||
|
|
||||||
std::vector<MagicEffectInfo>& effectInfos = effectInfoPair.second;
|
|
||||||
bool addNewLine = false;
|
bool addNewLine = false;
|
||||||
for (const MagicEffectInfo& effectInfo : effectInfos)
|
for (const MagicEffectInfo& effectInfo : effectInfos)
|
||||||
{
|
{
|
||||||
|
|
|
@ -37,20 +37,6 @@ namespace MWGui
|
||||||
bool mPermanent; // the effect is permanent
|
bool mPermanent; // the effect is permanent
|
||||||
};
|
};
|
||||||
|
|
||||||
class EffectSourceVisitor : public MWMechanics::EffectSourceVisitor
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
bool mIsPermanent;
|
|
||||||
|
|
||||||
std::map <int, std::vector<MagicEffectInfo> > mEffectSources;
|
|
||||||
|
|
||||||
virtual ~EffectSourceVisitor() {}
|
|
||||||
|
|
||||||
void visit (MWMechanics::EffectKey key, int effectIndex,
|
|
||||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
|
||||||
float magnitude, float remainingTime = -1, float totalTime = -1) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SpellIcons
|
class SpellIcons
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -92,9 +92,8 @@ namespace MWGui
|
||||||
|
|
||||||
std::string filter = Misc::StringUtils::lowerCaseUtf8(mFilter);
|
std::string filter = Misc::StringUtils::lowerCaseUtf8(mFilter);
|
||||||
|
|
||||||
for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
|
for (const ESM::Spell* spell : spells)
|
||||||
{
|
{
|
||||||
const ESM::Spell* spell = it->first;
|
|
||||||
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;
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ namespace MWGui
|
||||||
|
|
||||||
// Add price for the travelling followers
|
// Add price for the travelling followers
|
||||||
std::set<MWWorld::Ptr> followers;
|
std::set<MWWorld::Ptr> followers;
|
||||||
MWWorld::ActionTeleport::getFollowers(player, followers);
|
MWWorld::ActionTeleport::getFollowers(player, followers, !interior);
|
||||||
|
|
||||||
// Apply followers cost, unlike vanilla the first follower doesn't travel for free
|
// Apply followers cost, unlike vanilla the first follower doesn't travel for free
|
||||||
price *= 1 + static_cast<int>(followers.size());
|
price *= 1 + static_cast<int>(followers.size());
|
||||||
|
|
|
@ -99,8 +99,8 @@ namespace MWInput
|
||||||
// We keep track of our own mouse position, so that moving the mouse while in
|
// We keep track of our own mouse position, so that moving the mouse while in
|
||||||
// game mode does not move the position of the GUI cursor
|
// game mode does not move the position of the GUI cursor
|
||||||
float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor();
|
float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor();
|
||||||
float xMove = xAxis * dt * 1500.0f / uiScale;
|
float xMove = xAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed;
|
||||||
float yMove = yAxis * dt * 1500.0f / uiScale;
|
float yMove = yAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed;
|
||||||
|
|
||||||
float mouseWheelMove = -zAxis * dt * 1500.0f;
|
float mouseWheelMove = -zAxis * dt * 1500.0f;
|
||||||
if (xMove != 0 || yMove != 0 || mouseWheelMove != 0)
|
if (xMove != 0 || yMove != 0 || mouseWheelMove != 0)
|
||||||
|
|
|
@ -51,8 +51,8 @@ namespace MWLua
|
||||||
std::array<bool, MWWorld::InventoryStore::Slots> usedSlots;
|
std::array<bool, MWWorld::InventoryStore::Slots> usedSlots;
|
||||||
std::fill(usedSlots.begin(), usedSlots.end(), false);
|
std::fill(usedSlots.begin(), usedSlots.end(), false);
|
||||||
|
|
||||||
constexpr int anySlot = -1;
|
static constexpr int anySlot = -1;
|
||||||
auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView, anySlot](int slot, const Item& item) -> bool
|
auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView](int slot, const Item& item) -> bool
|
||||||
{
|
{
|
||||||
auto old_it = slot != anySlot ? store.getSlot(slot) : store.end();
|
auto old_it = slot != anySlot ? store.getSlot(slot) : store.end();
|
||||||
MWWorld::Ptr itemPtr;
|
MWWorld::Ptr itemPtr;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "luabindings.hpp"
|
#include "luabindings.hpp"
|
||||||
|
|
||||||
|
#include "luamanagerimp.hpp"
|
||||||
|
|
||||||
namespace sol
|
namespace sol
|
||||||
{
|
{
|
||||||
template <>
|
template <>
|
||||||
|
@ -48,11 +50,16 @@ namespace MWLua
|
||||||
asyncId.mContainer->setupUnsavableTimer(
|
asyncId.mContainer->setupUnsavableTimer(
|
||||||
TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScript, std::move(callback));
|
TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScript, std::move(callback));
|
||||||
};
|
};
|
||||||
|
api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn)
|
||||||
|
{
|
||||||
|
return 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::ScriptId::KEY];
|
||||||
return AsyncPackageId{id.mContainer, id.mPath};
|
hiddenData[Callback::SCRIPT_NAME_KEY] = id.toString();
|
||||||
|
return AsyncPackageId{id.mContainer, id.mPath, hiddenData};
|
||||||
};
|
};
|
||||||
return sol::make_object(context.mLua->sol(), initializer);
|
return sol::make_object(context.mLua->sol(), initializer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace MWLua
|
||||||
{
|
{
|
||||||
sol::table api(context.mLua->sol(), sol::create);
|
sol::table api(context.mLua->sol(), sol::create);
|
||||||
// TODO
|
// TODO
|
||||||
return context.mLua->makeReadOnly(api);
|
return LuaUtil::makeReadOnly(api);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ namespace MWLua
|
||||||
api["getControlSwitch"] = [input](const std::string& key) { return input->getControlSwitch(key); };
|
api["getControlSwitch"] = [input](const std::string& key) { return input->getControlSwitch(key); };
|
||||||
api["setControlSwitch"] = [input](const std::string& key, bool v) { input->toggleControlSwitch(key, v); };
|
api["setControlSwitch"] = [input](const std::string& key, bool v) { input->toggleControlSwitch(key, v); };
|
||||||
|
|
||||||
api["ACTION"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with(
|
api["ACTION"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with(
|
||||||
"GameMenu", MWInput::A_GameMenu,
|
"GameMenu", MWInput::A_GameMenu,
|
||||||
"Screenshot", MWInput::A_Screenshot,
|
"Screenshot", MWInput::A_Screenshot,
|
||||||
"Inventory", MWInput::A_Inventory,
|
"Inventory", MWInput::A_Inventory,
|
||||||
|
@ -102,7 +102,7 @@ namespace MWLua
|
||||||
"ZoomOut", MWInput::A_ZoomOut
|
"ZoomOut", MWInput::A_ZoomOut
|
||||||
));
|
));
|
||||||
|
|
||||||
api["CONTROL_SWITCH"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with(
|
api["CONTROL_SWITCH"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with(
|
||||||
"Controls", "playercontrols",
|
"Controls", "playercontrols",
|
||||||
"Fighting", "playerfighting",
|
"Fighting", "playerfighting",
|
||||||
"Jumping", "playerjumping",
|
"Jumping", "playerjumping",
|
||||||
|
@ -112,7 +112,7 @@ namespace MWLua
|
||||||
"VanityMode", "vanitymode"
|
"VanityMode", "vanitymode"
|
||||||
));
|
));
|
||||||
|
|
||||||
api["CONTROLLER_BUTTON"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with(
|
api["CONTROLLER_BUTTON"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with(
|
||||||
"A", SDL_CONTROLLER_BUTTON_A,
|
"A", SDL_CONTROLLER_BUTTON_A,
|
||||||
"B", SDL_CONTROLLER_BUTTON_B,
|
"B", SDL_CONTROLLER_BUTTON_B,
|
||||||
"X", SDL_CONTROLLER_BUTTON_X,
|
"X", SDL_CONTROLLER_BUTTON_X,
|
||||||
|
@ -130,7 +130,7 @@ namespace MWLua
|
||||||
"DPadRight", SDL_CONTROLLER_BUTTON_DPAD_RIGHT
|
"DPadRight", SDL_CONTROLLER_BUTTON_DPAD_RIGHT
|
||||||
));
|
));
|
||||||
|
|
||||||
api["CONTROLLER_AXIS"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with(
|
api["CONTROLLER_AXIS"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with(
|
||||||
"LeftX", SDL_CONTROLLER_AXIS_LEFTX,
|
"LeftX", SDL_CONTROLLER_AXIS_LEFTX,
|
||||||
"LeftY", SDL_CONTROLLER_AXIS_LEFTY,
|
"LeftY", SDL_CONTROLLER_AXIS_LEFTY,
|
||||||
"RightX", SDL_CONTROLLER_AXIS_RIGHTX,
|
"RightX", SDL_CONTROLLER_AXIS_RIGHTX,
|
||||||
|
@ -144,7 +144,7 @@ namespace MWLua
|
||||||
"MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveLeftRight
|
"MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveLeftRight
|
||||||
));
|
));
|
||||||
|
|
||||||
return context.mLua->makeReadOnly(api);
|
return LuaUtil::makeReadOnly(api);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,14 +18,14 @@ namespace MWLua
|
||||||
sol::table res(lua.sol(), sol::create);
|
sol::table res(lua.sol(), sol::create);
|
||||||
for (const std::string& v : values)
|
for (const std::string& v : values)
|
||||||
res[v] = v;
|
res[v] = v;
|
||||||
return lua.makeReadOnly(res);
|
return LuaUtil::makeReadOnly(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::table initCorePackage(const Context& context)
|
sol::table initCorePackage(const Context& context)
|
||||||
{
|
{
|
||||||
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"] = 5;
|
api["API_REVISION"] = 7;
|
||||||
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>();
|
||||||
|
@ -43,7 +43,7 @@ namespace MWLua
|
||||||
"Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient",
|
"Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient",
|
||||||
"Light", "Miscellaneous", "NPC", "Player", "Potion", "Static", "Weapon"
|
"Light", "Miscellaneous", "NPC", "Player", "Potion", "Static", "Weapon"
|
||||||
});
|
});
|
||||||
api["EQUIPMENT_SLOT"] = lua->makeReadOnly(lua->sol().create_table_with(
|
api["EQUIPMENT_SLOT"] = LuaUtil::makeReadOnly(lua->sol().create_table_with(
|
||||||
"Helmet", MWWorld::InventoryStore::Slot_Helmet,
|
"Helmet", MWWorld::InventoryStore::Slot_Helmet,
|
||||||
"Cuirass", MWWorld::InventoryStore::Slot_Cuirass,
|
"Cuirass", MWWorld::InventoryStore::Slot_Cuirass,
|
||||||
"Greaves", MWWorld::InventoryStore::Slot_Greaves,
|
"Greaves", MWWorld::InventoryStore::Slot_Greaves,
|
||||||
|
@ -64,7 +64,7 @@ namespace MWLua
|
||||||
"CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft,
|
"CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft,
|
||||||
"Ammunition", MWWorld::InventoryStore::Slot_Ammunition
|
"Ammunition", MWWorld::InventoryStore::Slot_Ammunition
|
||||||
));
|
));
|
||||||
return lua->makeReadOnly(api);
|
return LuaUtil::makeReadOnly(api);
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::table initWorldPackage(const Context& context)
|
sol::table initWorldPackage(const Context& context)
|
||||||
|
@ -107,37 +107,7 @@ namespace MWLua
|
||||||
// return GObjectList{worldView->selectObjects(query, false)};
|
// return GObjectList{worldView->selectObjects(query, false)};
|
||||||
};
|
};
|
||||||
// TODO: add world.placeNewObject(recordId, cell, pos, [rot])
|
// TODO: add world.placeNewObject(recordId, cell, pos, [rot])
|
||||||
return context.mLua->makeReadOnly(api);
|
return LuaUtil::makeReadOnly(api);
|
||||||
}
|
|
||||||
|
|
||||||
sol::table initNearbyPackage(const Context& context)
|
|
||||||
{
|
|
||||||
sol::table api(context.mLua->sol(), sol::create);
|
|
||||||
WorldView* worldView = context.mWorldView;
|
|
||||||
api["activators"] = LObjectList{worldView->getActivatorsInScene()};
|
|
||||||
api["actors"] = LObjectList{worldView->getActorsInScene()};
|
|
||||||
api["containers"] = LObjectList{worldView->getContainersInScene()};
|
|
||||||
api["doors"] = LObjectList{worldView->getDoorsInScene()};
|
|
||||||
api["items"] = LObjectList{worldView->getItemsInScene()};
|
|
||||||
api["selectObjects"] = [context](const Queries::Query& query)
|
|
||||||
{
|
|
||||||
ObjectIdList list;
|
|
||||||
WorldView* worldView = context.mWorldView;
|
|
||||||
if (query.mQueryType == "activators")
|
|
||||||
list = worldView->getActivatorsInScene();
|
|
||||||
else if (query.mQueryType == "actors")
|
|
||||||
list = worldView->getActorsInScene();
|
|
||||||
else if (query.mQueryType == "containers")
|
|
||||||
list = worldView->getContainersInScene();
|
|
||||||
else if (query.mQueryType == "doors")
|
|
||||||
list = worldView->getDoorsInScene();
|
|
||||||
else if (query.mQueryType == "items")
|
|
||||||
list = worldView->getItemsInScene();
|
|
||||||
return LObjectList{selectObjectsFromList(query, list, context)};
|
|
||||||
// TODO: Maybe use sqlite
|
|
||||||
// return LObjectList{worldView->selectObjects(query, true)};
|
|
||||||
};
|
|
||||||
return context.mLua->makeReadOnly(api);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::table initQueryPackage(const Context& context)
|
sol::table initQueryPackage(const Context& context)
|
||||||
|
@ -148,7 +118,7 @@ namespace MWLua
|
||||||
query[t] = Queries::Query(std::string(t));
|
query[t] = Queries::Query(std::string(t));
|
||||||
for (const QueryFieldGroup& group : getBasicQueryFieldGroups())
|
for (const QueryFieldGroup& group : getBasicQueryFieldGroups())
|
||||||
query[group.mName] = initFieldGroup(context, group);
|
query[group.mName] = initFieldGroup(context, group);
|
||||||
return query; // makeReadonly is applied by LuaState::addCommonPackage
|
return query; // makeReadOnly is applied by LuaState::addCommonPackage
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::table initFieldGroup(const Context& context, const QueryFieldGroup& group)
|
sol::table initFieldGroup(const Context& context, const QueryFieldGroup& group)
|
||||||
|
@ -163,12 +133,12 @@ namespace MWLua
|
||||||
{
|
{
|
||||||
const std::string& name = field->path()[i];
|
const std::string& name = field->path()[i];
|
||||||
if (subgroup[name] == sol::nil)
|
if (subgroup[name] == sol::nil)
|
||||||
subgroup[name] = context.mLua->makeReadOnly(context.mLua->newTable());
|
subgroup[name] = LuaUtil::makeReadOnly(context.mLua->newTable());
|
||||||
subgroup = context.mLua->getMutableFromReadOnly(subgroup[name]);
|
subgroup = LuaUtil::getMutableFromReadOnly(subgroup[name]);
|
||||||
}
|
}
|
||||||
subgroup[field->path().back()] = field;
|
subgroup[field->path().back()] = field;
|
||||||
}
|
}
|
||||||
return context.mLua->makeReadOnly(res);
|
return LuaUtil::makeReadOnly(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,11 +21,13 @@ namespace MWLua
|
||||||
|
|
||||||
sol::table initCorePackage(const Context&);
|
sol::table initCorePackage(const Context&);
|
||||||
sol::table initWorldPackage(const Context&);
|
sol::table initWorldPackage(const Context&);
|
||||||
sol::table initNearbyPackage(const Context&);
|
|
||||||
sol::table initQueryPackage(const Context&);
|
sol::table initQueryPackage(const Context&);
|
||||||
|
|
||||||
sol::table initFieldGroup(const Context&, const QueryFieldGroup&);
|
sol::table initFieldGroup(const Context&, const QueryFieldGroup&);
|
||||||
|
|
||||||
|
// Implemented in nearbybindings.cpp
|
||||||
|
sol::table initNearbyPackage(const Context&);
|
||||||
|
|
||||||
// Implemented in objectbindings.cpp
|
// Implemented in objectbindings.cpp
|
||||||
void initObjectBindingsForLocalScripts(const Context&);
|
void initObjectBindingsForLocalScripts(const Context&);
|
||||||
void initObjectBindingsForGlobalScripts(const Context&);
|
void initObjectBindingsForGlobalScripts(const Context&);
|
||||||
|
@ -45,9 +47,9 @@ namespace MWLua
|
||||||
// Implemented in asyncbindings.cpp
|
// Implemented in asyncbindings.cpp
|
||||||
struct AsyncPackageId
|
struct AsyncPackageId
|
||||||
{
|
{
|
||||||
// TODO: add ObjectId mLocalObject;
|
|
||||||
LuaUtil::ScriptsContainer* mContainer;
|
LuaUtil::ScriptsContainer* mContainer;
|
||||||
std::string mScript;
|
std::string mScript;
|
||||||
|
sol::table mHiddenData;
|
||||||
};
|
};
|
||||||
sol::function getAsyncPackageInitializer(const Context&);
|
sol::function getAsyncPackageInitializer(const Context&);
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,16 @@ namespace MWLua
|
||||||
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();
|
||||||
|
@ -126,6 +136,11 @@ namespace MWLua
|
||||||
<< ". Object not found or has no attached scripts";
|
<< ". Object not found or has no attached scripts";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run queued callbacks
|
||||||
|
for (CallbackWithData& c : mQueuedCallbacks)
|
||||||
|
c.mCallback(c.mArg);
|
||||||
|
mQueuedCallbacks.clear();
|
||||||
|
|
||||||
// Engine handlers in local scripts
|
// Engine handlers in local scripts
|
||||||
PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts());
|
PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts());
|
||||||
if (playerScripts)
|
if (playerScripts)
|
||||||
|
|
|
@ -19,6 +19,19 @@
|
||||||
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:
|
||||||
|
@ -67,6 +80,18 @@ namespace MWLua
|
||||||
// Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script.
|
// Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script.
|
||||||
void reloadAllScripts() override;
|
void reloadAllScripts() override;
|
||||||
|
|
||||||
|
// Used to call Lua callbacks from C++
|
||||||
|
void queueCallback(Callback callback, sol::object arg) { mQueuedCallbacks.push_back({std::move(callback), std::move(arg)}); }
|
||||||
|
|
||||||
|
// Wraps Lua callback into an std::function.
|
||||||
|
// NOTE: Resulted function is not thread safe. Can not be used while LuaManager::update() or
|
||||||
|
// any other Lua-related function is running.
|
||||||
|
template <class Arg>
|
||||||
|
std::function<void(Arg)> wrapLuaCallback(const Callback& c)
|
||||||
|
{
|
||||||
|
return [this, c](Arg arg) { this->queueCallback(c, sol::make_object(c.mFunc.lua_state(), arg)); };
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr);
|
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr);
|
||||||
|
|
||||||
|
@ -100,6 +125,13 @@ namespace MWLua
|
||||||
std::vector<MWBase::LuaManager::InputEvent> mInputEvents;
|
std::vector<MWBase::LuaManager::InputEvent> mInputEvents;
|
||||||
std::vector<ObjectId> mActorAddedEvents;
|
std::vector<ObjectId> mActorAddedEvents;
|
||||||
|
|
||||||
|
struct CallbackWithData
|
||||||
|
{
|
||||||
|
Callback mCallback;
|
||||||
|
sol::object mArg;
|
||||||
|
};
|
||||||
|
std::vector<CallbackWithData> mQueuedCallbacks;
|
||||||
|
|
||||||
struct LocalEngineEvent
|
struct LocalEngineEvent
|
||||||
{
|
{
|
||||||
ObjectId mDest;
|
ObjectId mDest;
|
||||||
|
|
120
apps/openmw/mwlua/nearbybindings.cpp
Normal file
120
apps/openmw/mwlua/nearbybindings.cpp
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
#include "luabindings.hpp"
|
||||||
|
|
||||||
|
#include <components/lua/luastate.hpp>
|
||||||
|
#include <components/queries/luabindings.hpp>
|
||||||
|
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
#include "../mwphysics/raycasting.hpp"
|
||||||
|
|
||||||
|
#include "worldview.hpp"
|
||||||
|
|
||||||
|
namespace sol
|
||||||
|
{
|
||||||
|
template <>
|
||||||
|
struct is_automagical<MWPhysics::RayCastingResult> : std::false_type {};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWLua
|
||||||
|
{
|
||||||
|
sol::table initNearbyPackage(const Context& context)
|
||||||
|
{
|
||||||
|
sol::table api(context.mLua->sol(), sol::create);
|
||||||
|
WorldView* worldView = context.mWorldView;
|
||||||
|
|
||||||
|
sol::usertype<MWPhysics::RayCastingResult> rayResult =
|
||||||
|
context.mLua->sol().new_usertype<MWPhysics::RayCastingResult>("RayCastingResult");
|
||||||
|
rayResult["hit"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) { return r.mHit; });
|
||||||
|
rayResult["hitPos"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional<osg::Vec3f>
|
||||||
|
{
|
||||||
|
if (r.mHit)
|
||||||
|
return r.mHitPos;
|
||||||
|
else
|
||||||
|
return sol::nullopt;
|
||||||
|
});
|
||||||
|
rayResult["hitNormal"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional<osg::Vec3f>
|
||||||
|
{
|
||||||
|
if (r.mHit)
|
||||||
|
return r.mHitNormal;
|
||||||
|
else
|
||||||
|
return sol::nullopt;
|
||||||
|
});
|
||||||
|
rayResult["hitObject"] = sol::readonly_property([worldView](const MWPhysics::RayCastingResult& r) -> sol::optional<LObject>
|
||||||
|
{
|
||||||
|
if (r.mHitObject.isEmpty())
|
||||||
|
return sol::nullopt;
|
||||||
|
else
|
||||||
|
return LObject(getId(r.mHitObject), worldView->getObjectRegistry());
|
||||||
|
});
|
||||||
|
|
||||||
|
api["COLLISION_TYPE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with(
|
||||||
|
"World", MWPhysics::CollisionType_World,
|
||||||
|
"Door", MWPhysics::CollisionType_Door,
|
||||||
|
"Actor", MWPhysics::CollisionType_Actor,
|
||||||
|
"HeightMap", MWPhysics::CollisionType_HeightMap,
|
||||||
|
"Projectile", MWPhysics::CollisionType_Projectile,
|
||||||
|
"Water", MWPhysics::CollisionType_Water,
|
||||||
|
"Default", MWPhysics::CollisionType_Default));
|
||||||
|
|
||||||
|
api["castRay"] = [](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional<sol::table> options)
|
||||||
|
{
|
||||||
|
MWWorld::Ptr ignore;
|
||||||
|
int collisionType = MWPhysics::CollisionType_Default;
|
||||||
|
float radius = 0;
|
||||||
|
if (options)
|
||||||
|
{
|
||||||
|
sol::optional<LObject> ignoreObj = options->get<sol::optional<LObject>>("ignore");
|
||||||
|
if (ignoreObj) ignore = ignoreObj->ptr();
|
||||||
|
collisionType = options->get<sol::optional<int>>("collisionType").value_or(collisionType);
|
||||||
|
radius = options->get<sol::optional<float>>("radius").value_or(0);
|
||||||
|
}
|
||||||
|
const MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting();
|
||||||
|
if (radius <= 0)
|
||||||
|
return rayCasting->castRay(from, to, ignore, std::vector<MWWorld::Ptr>(), collisionType);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!ignore.isEmpty()) throw std::logic_error("Currently castRay doesn't support `ignore` when radius > 0");
|
||||||
|
return rayCasting->castSphere(from, to, radius, collisionType);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// TODO: async raycasting
|
||||||
|
/*api["asyncCastRay"] = [luaManager = context.mLuaManager](
|
||||||
|
const Callback& luaCallback, const osg::Vec3f& from, const osg::Vec3f& to, sol::optional<sol::table> options)
|
||||||
|
{
|
||||||
|
std::function<void(MWPhysics::RayCastingResult)> callback =
|
||||||
|
luaManager->wrapLuaCallback<MWPhysics::RayCastingResult>(luaCallback);
|
||||||
|
MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting();
|
||||||
|
|
||||||
|
// Handle options the same way as in `castRay`.
|
||||||
|
|
||||||
|
// NOTE: `callback` is not thread safe. If MWPhysics works in separate thread, it must put results to a queue
|
||||||
|
// and use this callback from the main thread at the beginning of the next frame processing.
|
||||||
|
rayCasting->asyncCastRay(callback, from, to, ignore, std::vector<MWWorld::Ptr>(), collisionType);
|
||||||
|
};*/
|
||||||
|
|
||||||
|
api["activators"] = LObjectList{worldView->getActivatorsInScene()};
|
||||||
|
api["actors"] = LObjectList{worldView->getActorsInScene()};
|
||||||
|
api["containers"] = LObjectList{worldView->getContainersInScene()};
|
||||||
|
api["doors"] = LObjectList{worldView->getDoorsInScene()};
|
||||||
|
api["items"] = LObjectList{worldView->getItemsInScene()};
|
||||||
|
api["selectObjects"] = [context](const Queries::Query& query)
|
||||||
|
{
|
||||||
|
ObjectIdList list;
|
||||||
|
WorldView* worldView = context.mWorldView;
|
||||||
|
if (query.mQueryType == "activators")
|
||||||
|
list = worldView->getActivatorsInScene();
|
||||||
|
else if (query.mQueryType == "actors")
|
||||||
|
list = worldView->getActorsInScene();
|
||||||
|
else if (query.mQueryType == "containers")
|
||||||
|
list = worldView->getContainersInScene();
|
||||||
|
else if (query.mQueryType == "doors")
|
||||||
|
list = worldView->getDoorsInScene();
|
||||||
|
else if (query.mQueryType == "items")
|
||||||
|
list = worldView->getItemsInScene();
|
||||||
|
return LObjectList{selectObjectsFromList(query, list, context)};
|
||||||
|
// TODO: Maybe use sqlite
|
||||||
|
// return LObjectList{worldView->selectObjects(query, true)};
|
||||||
|
};
|
||||||
|
return LuaUtil::makeReadOnly(api);
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,7 +62,7 @@ namespace MWLua
|
||||||
else
|
else
|
||||||
return sol::make_object<float>(lua->sol(), value.getFloat());
|
return sol::make_object<float>(lua->sol(), value.getFloat());
|
||||||
};
|
};
|
||||||
return lua->makeReadOnly(config);
|
return LuaUtil::makeReadOnly(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::table initGlobalSettingsPackage(const Context& context) { return initSettingsPackage(context, true, false); }
|
sol::table initGlobalSettingsPackage(const Context& context) { return initSettingsPackage(context, true, false); }
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace MWLua
|
||||||
{
|
{
|
||||||
luaManager->addUIMessage(message);
|
luaManager->addUIMessage(message);
|
||||||
};
|
};
|
||||||
return context.mLua->makeReadOnly(api);
|
return LuaUtil::makeReadOnly(api);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,97 +5,279 @@
|
||||||
|
|
||||||
#include <components/esm/loadmgef.hpp>
|
#include <components/esm/loadmgef.hpp>
|
||||||
|
|
||||||
|
#include "creaturestats.hpp"
|
||||||
|
#include "spellcasting.hpp"
|
||||||
|
#include "spelleffects.hpp"
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
#include "../mwworld/inventorystore.hpp"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool merge(std::vector<ESM::ActiveEffect>& present, const std::vector<ESM::ActiveEffect>& queued)
|
||||||
|
{
|
||||||
|
// Can't merge if we already have an effect with the same effect index
|
||||||
|
auto problem = std::find_if(queued.begin(), queued.end(), [&] (const auto& qEffect)
|
||||||
|
{
|
||||||
|
return std::find_if(present.begin(), present.end(), [&] (const auto& pEffect) { return pEffect.mEffectIndex == qEffect.mEffectIndex; }) != present.end();
|
||||||
|
});
|
||||||
|
if(problem != queued.end())
|
||||||
|
return false;
|
||||||
|
present.insert(present.end(), queued.begin(), queued.end());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addEffects(std::vector<ESM::ActiveEffect>& effects, const ESM::EffectList& list, bool ignoreResistances = false)
|
||||||
|
{
|
||||||
|
int currentEffectIndex = 0;
|
||||||
|
for(const auto& enam : list.mList)
|
||||||
|
{
|
||||||
|
ESM::ActiveEffect effect;
|
||||||
|
effect.mEffectId = enam.mEffectID;
|
||||||
|
effect.mArg = MWMechanics::EffectKey(enam).mArg;
|
||||||
|
effect.mMagnitude = 0.f;
|
||||||
|
effect.mMinMagnitude = enam.mMagnMin;
|
||||||
|
effect.mMaxMagnitude = enam.mMagnMax;
|
||||||
|
effect.mEffectIndex = currentEffectIndex++;
|
||||||
|
effect.mFlags = ESM::ActiveEffect::Flag_None;
|
||||||
|
if(ignoreResistances)
|
||||||
|
effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances;
|
||||||
|
effect.mDuration = -1;
|
||||||
|
effect.mTimeLeft = -1;
|
||||||
|
effects.emplace_back(effect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
void ActiveSpells::update(float duration) const
|
ActiveSpells::IterationGuard::IterationGuard(ActiveSpells& spells) : mActiveSpells(spells)
|
||||||
{
|
{
|
||||||
bool rebuild = false;
|
mActiveSpells.mIterating = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Erase no longer active spells and effects
|
ActiveSpells::IterationGuard::~IterationGuard()
|
||||||
if (duration > 0)
|
{
|
||||||
|
mActiveSpells.mIterating = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster)
|
||||||
|
: mId(cast.mId), mDisplayName(cast.mSourceName), mCasterActorId(-1), mSlot(cast.mSlot), mType(cast.mType), mWorsenings(-1)
|
||||||
|
{
|
||||||
|
if(!caster.isEmpty() && caster.getClass().isActor())
|
||||||
|
mCasterActorId = caster.getClass().getCreatureStats(caster).getActorId();
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances)
|
||||||
|
: mId(spell->mId), mDisplayName(spell->mName), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()), mSlot(0)
|
||||||
|
, mType(spell->mData.mType == ESM::Spell::ST_Ability ? ESM::ActiveSpells::Type_Ability : ESM::ActiveSpells::Type_Permanent), mWorsenings(-1)
|
||||||
|
{
|
||||||
|
assert(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power);
|
||||||
|
addEffects(mEffects, spell->mEffects, ignoreResistances);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor)
|
||||||
|
: mId(item.getCellRef().getRefId()), mDisplayName(item.getClass().getName(item)), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
|
||||||
|
, mSlot(slotIndex), mType(ESM::ActiveSpells::Type_Enchantment), mWorsenings(-1)
|
||||||
|
{
|
||||||
|
assert(enchantment->mData.mType == ESM::Enchantment::ConstantEffect);
|
||||||
|
addEffects(mEffects, enchantment->mEffects);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params)
|
||||||
|
: mId(params.mId), mEffects(params.mEffects), mDisplayName(params.mDisplayName), mCasterActorId(params.mCasterActorId)
|
||||||
|
, mSlot(params.mItem.isSet() ? params.mItem.mIndex : 0)
|
||||||
|
, mType(params.mType), mWorsenings(params.mWorsenings), mNextWorsening({params.mNextWorsening})
|
||||||
|
{}
|
||||||
|
|
||||||
|
ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const
|
||||||
|
{
|
||||||
|
ESM::ActiveSpells::ActiveSpellParams params;
|
||||||
|
params.mId = mId;
|
||||||
|
params.mEffects = mEffects;
|
||||||
|
params.mDisplayName = mDisplayName;
|
||||||
|
params.mCasterActorId = mCasterActorId;
|
||||||
|
params.mItem.unset();
|
||||||
|
if(mSlot)
|
||||||
{
|
{
|
||||||
TContainer::iterator iter (mSpells.begin());
|
// Note that we're storing the inventory slot as a RefNum instead of an int as a matter of future proofing
|
||||||
while (iter!=mSpells.end())
|
// mSlot needs to be replaced with a RefNum once inventory items get persistent RefNum (#4508 #6148)
|
||||||
|
params.mItem = { static_cast<unsigned int>(mSlot), 0 };
|
||||||
|
}
|
||||||
|
params.mType = mType;
|
||||||
|
params.mWorsenings = mWorsenings;
|
||||||
|
params.mNextWorsening = mNextWorsening.toEsm();
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActiveSpells::ActiveSpellParams::worsen()
|
||||||
|
{
|
||||||
|
++mWorsenings;
|
||||||
|
if(!mWorsenings)
|
||||||
|
mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp();
|
||||||
|
mNextWorsening += CorprusStats::sWorseningPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActiveSpells::ActiveSpellParams::shouldWorsen() const
|
||||||
|
{
|
||||||
|
return mWorsenings >= 0 && MWBase::Environment::get().getWorld()->getTimeStamp() >= mNextWorsening;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActiveSpells::ActiveSpellParams::resetWorsenings()
|
||||||
|
{
|
||||||
|
mWorsenings = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration)
|
||||||
|
{
|
||||||
|
const auto& creatureStats = ptr.getClass().getCreatureStats(ptr);
|
||||||
|
assert(&creatureStats.getActiveSpells() == this);
|
||||||
|
IterationGuard guard{*this};
|
||||||
|
// Erase no longer active spells and effects
|
||||||
|
for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
|
||||||
|
{
|
||||||
|
if(spellIt->mType != ESM::ActiveSpells::Type_Temporary && spellIt->mType != ESM::ActiveSpells::Type_Consumable)
|
||||||
{
|
{
|
||||||
if (!timeToExpire (iter))
|
++spellIt;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool removedSpell = false;
|
||||||
|
for(auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();)
|
||||||
|
{
|
||||||
|
if(effectIt->mFlags & ESM::ActiveEffect::Flag_Remove && effectIt->mTimeLeft <= 0.f)
|
||||||
{
|
{
|
||||||
mSpells.erase (iter++);
|
auto effect = *effectIt;
|
||||||
rebuild = true;
|
effectIt = spellIt->mEffects.erase(effectIt);
|
||||||
|
onMagicEffectRemoved(ptr, *spellIt, effect);
|
||||||
|
removedSpell = applyPurges(ptr, &spellIt, &effectIt);
|
||||||
|
if(removedSpell)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bool interrupt = false;
|
++effectIt;
|
||||||
std::vector<ActiveEffect>& effects = iter->second.mEffects;
|
}
|
||||||
for (std::vector<ActiveEffect>::iterator effectIt = effects.begin(); effectIt != effects.end();)
|
}
|
||||||
|
if(removedSpell)
|
||||||
|
continue;
|
||||||
|
if(spellIt->mEffects.empty())
|
||||||
|
spellIt = mSpells.erase(spellIt);
|
||||||
|
else
|
||||||
|
++spellIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& spell : mQueue)
|
||||||
|
addToSpells(ptr, spell);
|
||||||
|
mQueue.clear();
|
||||||
|
|
||||||
|
// Vanilla only does this on cell change I think
|
||||||
|
const auto& spells = creatureStats.getSpells();
|
||||||
|
for(const ESM::Spell* spell : spells)
|
||||||
|
{
|
||||||
|
if(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power && !isSpellActive(spell->mId))
|
||||||
|
mSpells.emplace_back(ActiveSpellParams{spell, ptr});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ptr.getClass().hasInventoryStore(ptr) && !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished()))
|
||||||
|
{
|
||||||
|
auto& store = ptr.getClass().getInventoryStore(ptr);
|
||||||
|
if(store.getInvListener() != nullptr)
|
||||||
|
{
|
||||||
|
bool playNonLooping = !store.isFirstEquip();
|
||||||
|
const auto world = MWBase::Environment::get().getWorld();
|
||||||
|
for(int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++)
|
||||||
|
{
|
||||||
|
auto slot = store.getSlot(slotIndex);
|
||||||
|
if(slot == store.end())
|
||||||
|
continue;
|
||||||
|
const auto& enchantmentId = slot->getClass().getEnchantment(*slot);
|
||||||
|
if(enchantmentId.empty())
|
||||||
|
continue;
|
||||||
|
const ESM::Enchantment* enchantment = world->getStore().get<ESM::Enchantment>().find(enchantmentId);
|
||||||
|
if(enchantment->mData.mType != ESM::Enchantment::ConstantEffect)
|
||||||
|
continue;
|
||||||
|
if(std::find_if(mSpells.begin(), mSpells.end(), [&] (const ActiveSpellParams& params)
|
||||||
{
|
{
|
||||||
if (effectIt->mTimeLeft <= 0)
|
return params.mSlot == slotIndex && params.mType == ESM::ActiveSpells::Type_Enchantment && params.mId == slot->getCellRef().getRefId();
|
||||||
{
|
}) != mSpells.end())
|
||||||
rebuild = true;
|
continue;
|
||||||
|
ActiveSpellParams params(*slot, enchantment, slotIndex, ptr);
|
||||||
// Note: it we expire a Corprus effect, we should remove the whole spell.
|
mSpells.emplace_back(params);
|
||||||
if (effectIt->mEffectId == ESM::MagicEffect::Corprus)
|
for(const auto& effect : params.mEffects)
|
||||||
{
|
MWMechanics::playEffects(ptr, *world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId), playNonLooping);
|
||||||
iter = mSpells.erase (iter);
|
|
||||||
interrupt = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
effectIt = effects.erase(effectIt);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
effectIt->mTimeLeft -= duration;
|
|
||||||
++effectIt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!interrupt)
|
|
||||||
++iter;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mSpellsChanged)
|
// Update effects
|
||||||
|
for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
|
||||||
{
|
{
|
||||||
mSpellsChanged = false;
|
const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid?
|
||||||
rebuild = true;
|
bool removedSpell = false;
|
||||||
}
|
for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
|
||||||
|
{
|
||||||
|
bool remove = applyMagicEffect(ptr, caster, *spellIt, *it, duration);
|
||||||
|
if(remove)
|
||||||
|
it = spellIt->mEffects.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
removedSpell = applyPurges(ptr, &spellIt, &it);
|
||||||
|
if(removedSpell)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(removedSpell)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (rebuild)
|
bool remove = false;
|
||||||
rebuildEffects();
|
if(spellIt->mType == ESM::ActiveSpells::Type_Ability || spellIt->mType == ESM::ActiveSpells::Type_Permanent)
|
||||||
|
remove = !spells.hasSpell(spellIt->mId);
|
||||||
|
else if(spellIt->mType == ESM::ActiveSpells::Type_Enchantment)
|
||||||
|
{
|
||||||
|
const auto& store = ptr.getClass().getInventoryStore(ptr);
|
||||||
|
auto slot = store.getSlot(spellIt->mSlot);
|
||||||
|
remove = slot == store.end() || slot->getCellRef().getRefId() != spellIt->mId;
|
||||||
|
}
|
||||||
|
if(remove)
|
||||||
|
{
|
||||||
|
auto params = *spellIt;
|
||||||
|
spellIt = mSpells.erase(spellIt);
|
||||||
|
for(const auto& effect : params.mEffects)
|
||||||
|
onMagicEffectRemoved(ptr, params, effect);
|
||||||
|
applyPurges(ptr, &spellIt);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++spellIt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActiveSpells::rebuildEffects() const
|
void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell)
|
||||||
{
|
{
|
||||||
mEffects = MagicEffects();
|
if(spell.mType != ESM::ActiveSpells::Type_Consumable)
|
||||||
|
|
||||||
for (TIterator iter (begin()); iter!=end(); ++iter)
|
|
||||||
{
|
{
|
||||||
const std::vector<ActiveEffect>& effects = iter->second.mEffects;
|
auto found = std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& existing)
|
||||||
|
|
||||||
for (std::vector<ActiveEffect>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt)
|
|
||||||
{
|
{
|
||||||
if (effectIt->mTimeLeft > 0)
|
return spell.mId == existing.mId && spell.mCasterActorId == existing.mCasterActorId && spell.mSlot == existing.mSlot;
|
||||||
mEffects.add(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), MWMechanics::EffectParam(effectIt->mMagnitude));
|
});
|
||||||
|
if(found != mSpells.end())
|
||||||
|
{
|
||||||
|
if(merge(found->mEffects, spell.mEffects))
|
||||||
|
return;
|
||||||
|
auto params = *found;
|
||||||
|
mSpells.erase(found);
|
||||||
|
for(const auto& effect : params.mEffects)
|
||||||
|
onMagicEffectRemoved(ptr, params, effect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mSpells.emplace_back(spell);
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveSpells::ActiveSpells()
|
ActiveSpells::ActiveSpells() : mIterating(false)
|
||||||
: mSpellsChanged (false)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
const MagicEffects& ActiveSpells::getMagicEffects() const
|
|
||||||
{
|
|
||||||
update(0.f);
|
|
||||||
return mEffects;
|
|
||||||
}
|
|
||||||
|
|
||||||
ActiveSpells::TIterator ActiveSpells::begin() const
|
ActiveSpells::TIterator ActiveSpells::begin() const
|
||||||
{
|
{
|
||||||
return mSpells.begin();
|
return mSpells.begin();
|
||||||
|
@ -106,246 +288,159 @@ namespace MWMechanics
|
||||||
return mSpells.end();
|
return mSpells.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
double ActiveSpells::timeToExpire (const TIterator& iterator) const
|
|
||||||
{
|
|
||||||
const std::vector<ActiveEffect>& effects = iterator->second.mEffects;
|
|
||||||
|
|
||||||
float duration = 0;
|
|
||||||
|
|
||||||
for (std::vector<ActiveEffect>::const_iterator iter (effects.begin());
|
|
||||||
iter!=effects.end(); ++iter)
|
|
||||||
{
|
|
||||||
if (iter->mTimeLeft > duration)
|
|
||||||
duration = iter->mTimeLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duration < 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ActiveSpells::isSpellActive(const std::string& id) const
|
bool ActiveSpells::isSpellActive(const std::string& id) const
|
||||||
{
|
{
|
||||||
for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter)
|
return std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& spell)
|
||||||
{
|
{
|
||||||
if (Misc::StringUtils::ciEqual(iter->first, id))
|
return Misc::StringUtils::ciEqual(spell.mId, id);
|
||||||
return true;
|
}) != mSpells.end();
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ActiveSpells::TContainer& ActiveSpells::getActiveSpells() const
|
void ActiveSpells::addSpell(const ActiveSpellParams& params)
|
||||||
{
|
{
|
||||||
return mSpells;
|
mQueue.emplace_back(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActiveSpells::addSpell(const std::string &id, bool stack, const std::vector<ActiveEffect>& effects,
|
void ActiveSpells::addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor)
|
||||||
const std::string &displayName, int casterActorId)
|
|
||||||
{
|
{
|
||||||
TContainer::iterator it(mSpells.find(id));
|
mQueue.emplace_back(ActiveSpellParams{spell, actor, true});
|
||||||
|
|
||||||
ActiveSpellParams params;
|
|
||||||
params.mEffects = effects;
|
|
||||||
params.mDisplayName = displayName;
|
|
||||||
params.mCasterActorId = casterActorId;
|
|
||||||
|
|
||||||
if (it == end() || stack)
|
|
||||||
{
|
|
||||||
mSpells.insert(std::make_pair(id, params));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// addSpell() is called with effects for a range.
|
|
||||||
// but a spell may have effects with different ranges (e.g. Touch & Target)
|
|
||||||
// so, if we see new effects for same spell assume additional
|
|
||||||
// spell effects and add to existing effects of spell
|
|
||||||
mergeEffects(params.mEffects, it->second.mEffects);
|
|
||||||
it->second = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
mSpellsChanged = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActiveSpells::mergeEffects(std::vector<ActiveEffect>& addTo, const std::vector<ActiveEffect>& from)
|
void ActiveSpells::purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr)
|
||||||
{
|
{
|
||||||
for (std::vector<ActiveEffect>::const_iterator effect(from.begin()); effect != from.end(); ++effect)
|
assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this);
|
||||||
|
mPurges.emplace(predicate);
|
||||||
|
if(!mIterating)
|
||||||
{
|
{
|
||||||
// if effect is not in addTo, add it
|
IterationGuard guard{*this};
|
||||||
bool missing = true;
|
applyPurges(ptr);
|
||||||
for (std::vector<ActiveEffect>::const_iterator iter(addTo.begin()); iter != addTo.end(); ++iter)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActiveSpells::purge(EffectPredicate predicate, const MWWorld::Ptr& ptr)
|
||||||
|
{
|
||||||
|
assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this);
|
||||||
|
mPurges.emplace(predicate);
|
||||||
|
if(!mIterating)
|
||||||
|
{
|
||||||
|
IterationGuard guard{*this};
|
||||||
|
applyPurges(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActiveSpells::applyPurges(const MWWorld::Ptr& ptr, std::list<ActiveSpellParams>::iterator* currentSpell, std::vector<ActiveEffect>::iterator* currentEffect)
|
||||||
|
{
|
||||||
|
bool removedCurrentSpell = false;
|
||||||
|
while(!mPurges.empty())
|
||||||
|
{
|
||||||
|
auto predicate = mPurges.front();
|
||||||
|
mPurges.pop();
|
||||||
|
for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
|
||||||
{
|
{
|
||||||
if ((effect->mEffectId == iter->mEffectId) && (effect->mArg == iter->mArg))
|
bool isCurrentSpell = currentSpell && *currentSpell == spellIt;
|
||||||
|
std::visit([&] (auto&& variant)
|
||||||
{
|
{
|
||||||
missing = false;
|
using T = std::decay_t<decltype(variant)>;
|
||||||
break;
|
if constexpr (std::is_same_v<T, ParamsPredicate>)
|
||||||
}
|
{
|
||||||
}
|
if(variant(*spellIt))
|
||||||
if (missing)
|
{
|
||||||
{
|
auto params = *spellIt;
|
||||||
addTo.push_back(*effect);
|
spellIt = mSpells.erase(spellIt);
|
||||||
|
if(isCurrentSpell)
|
||||||
|
{
|
||||||
|
*currentSpell = spellIt;
|
||||||
|
removedCurrentSpell = true;
|
||||||
|
}
|
||||||
|
for(const auto& effect : params.mEffects)
|
||||||
|
onMagicEffectRemoved(ptr, params, effect);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
++spellIt;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
static_assert(std::is_same_v<T, EffectPredicate>, "Non-exhaustive visitor");
|
||||||
|
for(auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();)
|
||||||
|
{
|
||||||
|
if(variant(*spellIt, *effectIt))
|
||||||
|
{
|
||||||
|
auto effect = *effectIt;
|
||||||
|
if(isCurrentSpell && currentEffect)
|
||||||
|
{
|
||||||
|
auto distance = std::distance(spellIt->mEffects.begin(), *currentEffect);
|
||||||
|
if(effectIt <= *currentEffect)
|
||||||
|
distance--;
|
||||||
|
effectIt = spellIt->mEffects.erase(effectIt);
|
||||||
|
*currentEffect = spellIt->mEffects.begin() + distance;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
effectIt = spellIt->mEffects.erase(effectIt);
|
||||||
|
onMagicEffectRemoved(ptr, *spellIt, effect);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
++effectIt;
|
||||||
|
}
|
||||||
|
++spellIt;
|
||||||
|
}
|
||||||
|
}, predicate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return removedCurrentSpell;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActiveSpells::removeEffects(const std::string &id)
|
void ActiveSpells::removeEffects(const MWWorld::Ptr& ptr, const std::string &id)
|
||||||
{
|
{
|
||||||
for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell)
|
purge([=] (const ActiveSpellParams& params)
|
||||||
{
|
{
|
||||||
if (spell->first == id)
|
return params.mId == id;
|
||||||
{
|
}, ptr);
|
||||||
spell->second.mEffects.clear();
|
|
||||||
mSpellsChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const
|
void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, short effectId)
|
||||||
{
|
{
|
||||||
for (TContainer::const_iterator it = begin(); it != end(); ++it)
|
purge([=] (const ActiveSpellParams&, const ESM::ActiveEffect& effect)
|
||||||
{
|
{
|
||||||
for (std::vector<ActiveEffect>::const_iterator effectIt = it->second.mEffects.begin();
|
return effect.mEffectId == effectId;
|
||||||
effectIt != it->second.mEffects.end(); ++effectIt)
|
}, ptr);
|
||||||
{
|
|
||||||
std::string name = it->second.mDisplayName;
|
|
||||||
|
|
||||||
float magnitude = effectIt->mMagnitude;
|
|
||||||
if (magnitude)
|
|
||||||
visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), effectIt->mEffectIndex, name, it->first, it->second.mCasterActorId, magnitude, effectIt->mTimeLeft, effectIt->mDuration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActiveSpells::purgeAll(float chance, bool spellOnly)
|
void ActiveSpells::purge(const MWWorld::Ptr& ptr, int casterActorId)
|
||||||
{
|
{
|
||||||
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); )
|
purge([=] (const ActiveSpellParams& params)
|
||||||
{
|
{
|
||||||
const std::string spellId = it->first;
|
return params.mCasterActorId == casterActorId;
|
||||||
|
}, ptr);
|
||||||
// if spellOnly is true, dispell only spells. Leave potions, enchanted items etc.
|
|
||||||
if (spellOnly)
|
|
||||||
{
|
|
||||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellId);
|
|
||||||
if (!spell || spell->mData.mType != ESM::Spell::ST_Spell)
|
|
||||||
{
|
|
||||||
++it;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Misc::Rng::roll0to99() < chance)
|
|
||||||
mSpells.erase(it++);
|
|
||||||
else
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
mSpellsChanged = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActiveSpells::purgeEffect(short effectId)
|
void ActiveSpells::clear(const MWWorld::Ptr& ptr)
|
||||||
{
|
{
|
||||||
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
|
mQueue.clear();
|
||||||
|
purge([] (const ActiveSpellParams& params) { return true; }, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActiveSpells::skipWorsenings(double hours)
|
||||||
|
{
|
||||||
|
for(auto& spell : mSpells)
|
||||||
{
|
{
|
||||||
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
|
if(spell.mWorsenings >= 0)
|
||||||
effectIt != it->second.mEffects.end();)
|
spell.mNextWorsening += hours;
|
||||||
{
|
|
||||||
if (effectIt->mEffectId == effectId)
|
|
||||||
effectIt = it->second.mEffects.erase(effectIt);
|
|
||||||
else
|
|
||||||
++effectIt;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
mSpellsChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActiveSpells::purgeEffect(short effectId, const std::string& sourceId, int effectIndex)
|
|
||||||
{
|
|
||||||
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
|
|
||||||
{
|
|
||||||
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
|
|
||||||
effectIt != it->second.mEffects.end();)
|
|
||||||
{
|
|
||||||
if (effectIt->mEffectId == effectId && it->first == sourceId && (effectIndex < 0 || effectIndex == effectIt->mEffectIndex))
|
|
||||||
effectIt = it->second.mEffects.erase(effectIt);
|
|
||||||
else
|
|
||||||
++effectIt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mSpellsChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActiveSpells::purge(int casterActorId)
|
|
||||||
{
|
|
||||||
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
|
|
||||||
{
|
|
||||||
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
|
|
||||||
effectIt != it->second.mEffects.end();)
|
|
||||||
{
|
|
||||||
if (it->second.mCasterActorId == casterActorId)
|
|
||||||
effectIt = it->second.mEffects.erase(effectIt);
|
|
||||||
else
|
|
||||||
++effectIt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mSpellsChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActiveSpells::purgeCorprusDisease()
|
|
||||||
{
|
|
||||||
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
|
|
||||||
{
|
|
||||||
bool hasCorprusEffect = false;
|
|
||||||
for (std::vector<ActiveEffect>::iterator effectIt = iter->second.mEffects.begin();
|
|
||||||
effectIt != iter->second.mEffects.end();++effectIt)
|
|
||||||
{
|
|
||||||
if (effectIt->mEffectId == ESM::MagicEffect::Corprus)
|
|
||||||
{
|
|
||||||
hasCorprusEffect = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasCorprusEffect)
|
|
||||||
{
|
|
||||||
mSpells.erase(iter++);
|
|
||||||
mSpellsChanged = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
++iter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActiveSpells::clear()
|
|
||||||
{
|
|
||||||
mSpells.clear();
|
|
||||||
mSpellsChanged = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActiveSpells::writeState(ESM::ActiveSpells &state) const
|
void ActiveSpells::writeState(ESM::ActiveSpells &state) const
|
||||||
{
|
{
|
||||||
for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it)
|
for(const auto& spell : mSpells)
|
||||||
{
|
state.mSpells.emplace_back(spell.toEsm());
|
||||||
// Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp
|
for(const auto& spell : mQueue)
|
||||||
ESM::ActiveSpells::ActiveSpellParams params;
|
state.mQueue.emplace_back(spell.toEsm());
|
||||||
params.mEffects = it->second.mEffects;
|
|
||||||
params.mCasterActorId = it->second.mCasterActorId;
|
|
||||||
params.mDisplayName = it->second.mDisplayName;
|
|
||||||
|
|
||||||
state.mSpells.insert (std::make_pair(it->first, params));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActiveSpells::readState(const ESM::ActiveSpells &state)
|
void ActiveSpells::readState(const ESM::ActiveSpells &state)
|
||||||
{
|
{
|
||||||
for (ESM::ActiveSpells::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it)
|
for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mSpells)
|
||||||
{
|
mSpells.emplace_back(ActiveSpellParams{spell});
|
||||||
// Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp
|
for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue)
|
||||||
ActiveSpellParams params;
|
mQueue.emplace_back(ActiveSpellParams{spell});
|
||||||
params.mEffects = it->second.mEffects;
|
|
||||||
params.mCasterActorId = it->second.mCasterActorId;
|
|
||||||
params.mDisplayName = it->second.mDisplayName;
|
|
||||||
|
|
||||||
mSpells.insert (std::make_pair(it->first, params));
|
|
||||||
mSpellsChanged = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,83 @@
|
||||||
#ifndef GAME_MWMECHANICS_ACTIVESPELLS_H
|
#ifndef GAME_MWMECHANICS_ACTIVESPELLS_H
|
||||||
#define GAME_MWMECHANICS_ACTIVESPELLS_H
|
#define GAME_MWMECHANICS_ACTIVESPELLS_H
|
||||||
|
|
||||||
#include <map>
|
#include <functional>
|
||||||
#include <vector>
|
#include <list>
|
||||||
|
#include <queue>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <components/esm/activespells.hpp>
|
#include <components/esm/activespells.hpp>
|
||||||
|
|
||||||
#include "../mwworld/timestamp.hpp"
|
#include "../mwworld/timestamp.hpp"
|
||||||
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
#include "magiceffects.hpp"
|
#include "magiceffects.hpp"
|
||||||
|
#include "spellcasting.hpp"
|
||||||
|
|
||||||
|
namespace ESM
|
||||||
|
{
|
||||||
|
struct Enchantment;
|
||||||
|
struct Spell;
|
||||||
|
}
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
/// \brief Lasting spell effects
|
/// \brief Lasting spell effects
|
||||||
///
|
///
|
||||||
/// \note The name of this class is slightly misleading, since it also handels lasting potion
|
/// \note The name of this class is slightly misleading, since it also handles lasting potion
|
||||||
/// effects.
|
/// effects.
|
||||||
class ActiveSpells
|
class ActiveSpells
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
typedef ESM::ActiveEffect ActiveEffect;
|
using ActiveEffect = ESM::ActiveEffect;
|
||||||
|
class ActiveSpellParams
|
||||||
struct ActiveSpellParams
|
|
||||||
{
|
{
|
||||||
std::vector<ActiveEffect> mEffects;
|
std::string mId;
|
||||||
MWWorld::TimeStamp mTimeStamp;
|
std::vector<ActiveEffect> mEffects;
|
||||||
std::string mDisplayName;
|
std::string mDisplayName;
|
||||||
|
int mCasterActorId;
|
||||||
|
int mSlot;
|
||||||
|
ESM::ActiveSpells::EffectType mType;
|
||||||
|
int mWorsenings;
|
||||||
|
MWWorld::TimeStamp mNextWorsening;
|
||||||
|
|
||||||
// The caster that inflicted this spell on us
|
ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params);
|
||||||
int mCasterActorId;
|
|
||||||
|
ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances = false);
|
||||||
|
|
||||||
|
ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor);
|
||||||
|
|
||||||
|
ESM::ActiveSpells::ActiveSpellParams toEsm() const;
|
||||||
|
|
||||||
|
friend class ActiveSpells;
|
||||||
|
public:
|
||||||
|
ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster);
|
||||||
|
|
||||||
|
const std::string& getId() const { return mId; }
|
||||||
|
|
||||||
|
const std::vector<ActiveEffect>& getEffects() const { return mEffects; }
|
||||||
|
std::vector<ActiveEffect>& getEffects() { return mEffects; }
|
||||||
|
|
||||||
|
ESM::ActiveSpells::EffectType getType() const { return mType; }
|
||||||
|
|
||||||
|
int getCasterActorId() const { return mCasterActorId; }
|
||||||
|
|
||||||
|
int getWorsenings() const { return mWorsenings; }
|
||||||
|
|
||||||
|
const std::string& getDisplayName() const { return mDisplayName; }
|
||||||
|
|
||||||
|
// Increments worsenings count and sets the next timestamp
|
||||||
|
void worsen();
|
||||||
|
|
||||||
|
bool shouldWorsen() const;
|
||||||
|
|
||||||
|
void resetWorsenings();
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::multimap<std::string, ActiveSpellParams > TContainer;
|
typedef std::list<ActiveSpellParams>::const_iterator TIterator;
|
||||||
typedef TContainer::const_iterator TIterator;
|
|
||||||
|
|
||||||
void readState (const ESM::ActiveSpells& state);
|
void readState (const ESM::ActiveSpells& state);
|
||||||
void writeState (ESM::ActiveSpells& state) const;
|
void writeState (ESM::ActiveSpells& state) const;
|
||||||
|
@ -43,24 +86,29 @@ namespace MWMechanics
|
||||||
|
|
||||||
TIterator end() const;
|
TIterator end() const;
|
||||||
|
|
||||||
void update(float duration) const;
|
void update(const MWWorld::Ptr& ptr, float duration);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
using ParamsPredicate = std::function<bool(const ActiveSpellParams&)>;
|
||||||
|
using EffectPredicate = std::function<bool(const ActiveSpellParams&, const ESM::ActiveEffect&)>;
|
||||||
|
using Predicate = std::variant<ParamsPredicate, EffectPredicate>;
|
||||||
|
|
||||||
mutable TContainer mSpells;
|
struct IterationGuard
|
||||||
mutable MagicEffects mEffects;
|
{
|
||||||
mutable bool mSpellsChanged;
|
ActiveSpells& mActiveSpells;
|
||||||
|
|
||||||
void rebuildEffects() const;
|
IterationGuard(ActiveSpells& spells);
|
||||||
|
~IterationGuard();
|
||||||
|
};
|
||||||
|
|
||||||
/// Add any effects that are in "from" and not in "addTo" to "addTo"
|
std::list<ActiveSpellParams> mSpells;
|
||||||
void mergeEffects(std::vector<ActiveEffect>& addTo, const std::vector<ActiveEffect>& from);
|
std::vector<ActiveSpellParams> mQueue;
|
||||||
|
std::queue<Predicate> mPurges;
|
||||||
|
bool mIterating;
|
||||||
|
|
||||||
double timeToExpire (const TIterator& iterator) const;
|
void addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell);
|
||||||
///< Returns time (in in-game hours) until the spell pointed to by \a iterator
|
|
||||||
/// expires.
|
|
||||||
|
|
||||||
const TContainer& getActiveSpells() const;
|
bool applyPurges(const MWWorld::Ptr& ptr, std::list<ActiveSpellParams>::iterator* currentSpell = nullptr, std::vector<ActiveEffect>::iterator* currentEffect = nullptr);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -70,40 +118,31 @@ namespace MWMechanics
|
||||||
///
|
///
|
||||||
/// \brief addSpell
|
/// \brief addSpell
|
||||||
/// \param id ID for stacking purposes.
|
/// \param id ID for stacking purposes.
|
||||||
/// \param stack If false, the spell is not added if one with the same ID exists already.
|
|
||||||
/// \param effects
|
|
||||||
/// \param displayName Name for display in magic menu.
|
|
||||||
///
|
///
|
||||||
void addSpell (const std::string& id, bool stack, const std::vector<ActiveEffect>& effects,
|
void addSpell (const ActiveSpellParams& params);
|
||||||
const std::string& displayName, int casterActorId);
|
|
||||||
|
/// Bypasses resistances
|
||||||
|
void addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor);
|
||||||
|
|
||||||
/// Removes the active effects from this spell/potion/.. with \a id
|
/// Removes the active effects from this spell/potion/.. with \a id
|
||||||
void removeEffects (const std::string& id);
|
void removeEffects (const MWWorld::Ptr& ptr, const std::string& id);
|
||||||
|
|
||||||
/// Remove all active effects with this effect id
|
/// Remove all active effects with this effect id
|
||||||
void purgeEffect (short effectId);
|
void purgeEffect (const MWWorld::Ptr& ptr, short effectId);
|
||||||
|
|
||||||
/// Remove all active effects with this effect id and source id
|
void purge(EffectPredicate predicate, const MWWorld::Ptr& ptr);
|
||||||
void purgeEffect (short effectId, const std::string& sourceId, int effectIndex=-1);
|
void purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr);
|
||||||
|
|
||||||
/// Remove all active effects, if roll succeeds (for each effect)
|
/// Remove all effects that were cast by \a casterActorId
|
||||||
void purgeAll(float chance, bool spellOnly = false);
|
void purge (const MWWorld::Ptr& ptr, int casterActorId);
|
||||||
|
|
||||||
/// Remove all effects with CASTER_LINKED flag that were cast by \a casterActorId
|
|
||||||
void purge (int casterActorId);
|
|
||||||
|
|
||||||
/// Remove all spells
|
/// Remove all spells
|
||||||
void clear();
|
void clear(const MWWorld::Ptr& ptr);
|
||||||
|
|
||||||
bool isSpellActive (const std::string& id) const;
|
bool isSpellActive (const std::string& id) const;
|
||||||
///< case insensitive
|
///< case insensitive
|
||||||
|
|
||||||
void purgeCorprusDisease();
|
void skipWorsenings(double hours);
|
||||||
|
|
||||||
const MagicEffects& getMagicEffects() const;
|
|
||||||
|
|
||||||
void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
Actor::Actor(const MWWorld::Ptr &ptr, MWRender::Animation *animation)
|
Actor::Actor(const MWWorld::Ptr &ptr, MWRender::Animation *animation)
|
||||||
|
: mPositionAdjusted(false)
|
||||||
{
|
{
|
||||||
mCharacterController.reset(new CharacterController(ptr, animation));
|
mCharacterController.reset(new CharacterController(ptr, animation));
|
||||||
}
|
}
|
||||||
|
@ -58,4 +59,14 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
mIsTurningToPlayer = turning;
|
mIsTurningToPlayer = turning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Actor::setPositionAdjusted(bool adjusted)
|
||||||
|
{
|
||||||
|
mPositionAdjusted = adjusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Actor::getPositionAdjusted() const
|
||||||
|
{
|
||||||
|
return mPositionAdjusted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,9 @@ namespace MWMechanics
|
||||||
return mEngageCombat.update(duration);
|
return mEngageCombat.update(duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setPositionAdjusted(bool adjusted);
|
||||||
|
bool getPositionAdjusted() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<CharacterController> mCharacterController;
|
std::unique_ptr<CharacterController> mCharacterController;
|
||||||
int mGreetingTimer{0};
|
int mGreetingTimer{0};
|
||||||
|
@ -55,6 +58,7 @@ namespace MWMechanics
|
||||||
GreetingState mGreetingState{Greet_None};
|
GreetingState mGreetingState{Greet_None};
|
||||||
bool mIsTurningToPlayer{false};
|
bool mIsTurningToPlayer{false};
|
||||||
Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f)};
|
Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f)};
|
||||||
|
bool mPositionAdjusted;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -41,15 +41,11 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
std::map<std::string, int> mDeathCount;
|
std::map<std::string, int> mDeathCount;
|
||||||
|
|
||||||
void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor);
|
void adjustMagicEffects (const MWWorld::Ptr& creature, float duration);
|
||||||
void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor);
|
|
||||||
|
|
||||||
void adjustMagicEffects (const MWWorld::Ptr& creature);
|
|
||||||
|
|
||||||
void calculateDynamicStats (const MWWorld::Ptr& ptr);
|
void calculateDynamicStats (const MWWorld::Ptr& ptr);
|
||||||
|
|
||||||
void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration);
|
void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration);
|
||||||
void calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration);
|
|
||||||
|
|
||||||
void calculateRestoration (const MWWorld::Ptr& ptr, float duration);
|
void calculateRestoration (const MWWorld::Ptr& ptr, float duration);
|
||||||
|
|
||||||
|
@ -94,7 +90,7 @@ namespace MWMechanics
|
||||||
///
|
///
|
||||||
/// \note Dead actors are ignored.
|
/// \note Dead actors are ignored.
|
||||||
|
|
||||||
void removeActor (const MWWorld::Ptr& ptr);
|
void removeActor (const MWWorld::Ptr& ptr, bool keepActive);
|
||||||
///< Deregister an actor for stats management
|
///< Deregister an actor for stats management
|
||||||
///
|
///
|
||||||
/// \note Ignored, if \a ptr is not a registered actor.
|
/// \note Ignored, if \a ptr is not a registered actor.
|
||||||
|
@ -208,7 +204,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl);
|
void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl);
|
||||||
void applyCureEffects (const MWWorld::Ptr& actor);
|
|
||||||
|
|
||||||
PtrActorMap mActors;
|
PtrActorMap mActors;
|
||||||
float mTimerDisposeSummonsCorpses;
|
float mTimerDisposeSummonsCorpses;
|
||||||
|
|
|
@ -348,7 +348,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool runFallback = true;
|
bool runFallback = true;
|
||||||
|
|
||||||
if (pathgrid && !actor.getClass().isPureWaterCreature(actor))
|
if (pathgrid != nullptr && !pathgrid->mPoints.empty() && !actor.getClass().isPureWaterCreature(actor))
|
||||||
{
|
{
|
||||||
ESM::Pathgrid::PointList points;
|
ESM::Pathgrid::PointList points;
|
||||||
Misc::CoordinateConverter coords(storage.mCell->getCell());
|
Misc::CoordinateConverter coords(storage.mCell->getCell());
|
||||||
|
|
|
@ -208,14 +208,14 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
|
for (const ESM::Spell* spell : spells)
|
||||||
{
|
{
|
||||||
float rating = rateSpell(it->first, actor, enemy);
|
float rating = rateSpell(spell, actor, enemy);
|
||||||
if (rating > bestActionRating)
|
if (rating > bestActionRating)
|
||||||
{
|
{
|
||||||
bestActionRating = rating;
|
bestActionRating = rating;
|
||||||
bestAction.reset(new ActionSpell(it->first->mId));
|
bestAction.reset(new ActionSpell(spell->mId));
|
||||||
antiFleeRating = vanillaRateSpell(it->first, actor, enemy);
|
antiFleeRating = vanillaRateSpell(spell, actor, enemy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,9 +266,9 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
|
for (const ESM::Spell* spell : spells)
|
||||||
{
|
{
|
||||||
float rating = rateSpell(it->first, actor, enemy);
|
float rating = rateSpell(spell, actor, enemy);
|
||||||
if (rating > bestActionRating)
|
if (rating > bestActionRating)
|
||||||
{
|
{
|
||||||
bestActionRating = rating;
|
bestActionRating = rating;
|
||||||
|
|
|
@ -34,6 +34,11 @@ namespace
|
||||||
const float actorTolerance = 2 * speed * duration + 1.2 * std::max(halfExtents.x(), halfExtents.y());
|
const float actorTolerance = 2 * speed * duration + 1.2 * std::max(halfExtents.x(), halfExtents.y());
|
||||||
return std::max(MWMechanics::MIN_TOLERANCE, actorTolerance);
|
return std::max(MWMechanics::MIN_TOLERANCE, actorTolerance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool canOpenDoors(const MWWorld::Ptr& ptr)
|
||||||
|
{
|
||||||
|
return ptr.getClass().isBipedal(ptr) || ptr.getClass().hasInventoryStore(ptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
|
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
|
||||||
|
@ -118,7 +123,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
||||||
|
|
||||||
if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed)
|
if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed)
|
||||||
{
|
{
|
||||||
if (actor.getClass().isBipedal(actor))
|
if (canOpenDoors(actor))
|
||||||
openDoors(actor);
|
openDoors(actor);
|
||||||
|
|
||||||
const bool wasShortcutting = mIsShortcutting;
|
const bool wasShortcutting = mIsShortcutting;
|
||||||
|
@ -232,7 +237,7 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor)
|
||||||
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
|
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
|
||||||
|
|
||||||
const MWWorld::Ptr door = getNearbyDoor(actor, distance);
|
const MWWorld::Ptr door = getNearbyDoor(actor, distance);
|
||||||
if (!door.isEmpty() && actor.getClass().isBipedal(actor))
|
if (!door.isEmpty() && canOpenDoors(actor))
|
||||||
{
|
{
|
||||||
openDoors(actor);
|
openDoors(actor);
|
||||||
}
|
}
|
||||||
|
@ -443,9 +448,13 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::
|
||||||
result |= DetourNavigator::Flag_swim;
|
result |= DetourNavigator::Flag_swim;
|
||||||
|
|
||||||
if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0)
|
if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0)
|
||||||
|
{
|
||||||
result |= DetourNavigator::Flag_walk;
|
result |= DetourNavigator::Flag_walk;
|
||||||
|
if (getTypeId() == AiPackageTypeId::Travel)
|
||||||
|
result |= DetourNavigator::Flag_usePathgrid;
|
||||||
|
}
|
||||||
|
|
||||||
if (actorClass.isBipedal(actor) && getTypeId() != AiPackageTypeId::Wander)
|
if (canOpenDoors(actor) && getTypeId() != AiPackageTypeId::Wander)
|
||||||
result |= DetourNavigator::Flag_openDoor;
|
result |= DetourNavigator::Flag_openDoor;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -457,20 +466,31 @@ DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::P
|
||||||
const DetourNavigator::Flags flags = getNavigatorFlags(actor);
|
const DetourNavigator::Flags flags = getNavigatorFlags(actor);
|
||||||
const MWWorld::Class& actorClass = actor.getClass();
|
const MWWorld::Class& actorClass = actor.getClass();
|
||||||
|
|
||||||
if (flags & DetourNavigator::Flag_swim)
|
const float swimSpeed = (flags & DetourNavigator::Flag_swim) == 0
|
||||||
costs.mWater = divOrMax(costs.mWater, actorClass.getSwimSpeed(actor));
|
? 0.0f
|
||||||
|
: actorClass.getSwimSpeed(actor);
|
||||||
|
|
||||||
if (flags & DetourNavigator::Flag_walk)
|
const float walkSpeed = [&]
|
||||||
{
|
{
|
||||||
float walkCost;
|
if ((flags & DetourNavigator::Flag_walk) == 0)
|
||||||
|
return 0.0f;
|
||||||
if (getTypeId() == AiPackageTypeId::Wander)
|
if (getTypeId() == AiPackageTypeId::Wander)
|
||||||
walkCost = divOrMax(1.0, actorClass.getWalkSpeed(actor));
|
return actorClass.getWalkSpeed(actor);
|
||||||
else
|
return actorClass.getRunSpeed(actor);
|
||||||
walkCost = divOrMax(1.0, actorClass.getRunSpeed(actor));
|
} ();
|
||||||
costs.mDoor = costs.mDoor * walkCost;
|
|
||||||
costs.mPathgrid = costs.mPathgrid * walkCost;
|
const float maxSpeed = std::max(swimSpeed, walkSpeed);
|
||||||
costs.mGround = costs.mGround * walkCost;
|
|
||||||
}
|
if (maxSpeed == 0)
|
||||||
|
return costs;
|
||||||
|
|
||||||
|
const float swimFactor = swimSpeed / maxSpeed;
|
||||||
|
const float walkFactor = walkSpeed / maxSpeed;
|
||||||
|
|
||||||
|
costs.mWater = divOrMax(costs.mWater, swimFactor);
|
||||||
|
costs.mDoor = divOrMax(costs.mDoor, walkFactor);
|
||||||
|
costs.mPathgrid = divOrMax(costs.mPathgrid, walkFactor);
|
||||||
|
costs.mGround = divOrMax(costs.mGround, walkFactor);
|
||||||
|
|
||||||
return costs;
|
return costs;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "aitravel.hpp"
|
#include "aitravel.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <components/esm/aisequence.hpp>
|
#include <components/esm/aisequence.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
@ -23,6 +25,11 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2)
|
||||||
return (pos1 - pos2).length2() <= 7168*7168;
|
return (pos1 - pos2).length2() <= 7168*7168;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float getActorRadius(const MWWorld::ConstPtr& actor)
|
||||||
|
{
|
||||||
|
const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor);
|
||||||
|
return std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
|
@ -70,16 +77,24 @@ namespace MWMechanics
|
||||||
|
|
||||||
// Unfortunately, with vanilla assets destination is sometimes blocked by other actor.
|
// Unfortunately, with vanilla assets destination is sometimes blocked by other actor.
|
||||||
// If we got close to target, check for actors nearby. If they are, finish AI package.
|
// If we got close to target, check for actors nearby. If they are, finish AI package.
|
||||||
int destinationTolerance = 64;
|
if (mDestinationCheck.update(duration) == Misc::TimerStatus::Elapsed)
|
||||||
if (distance(actorPos, targetPos) <= destinationTolerance)
|
|
||||||
{
|
{
|
||||||
std::vector<MWWorld::Ptr> targetActors;
|
std::vector<MWWorld::Ptr> occupyingActors;
|
||||||
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors);
|
if (isAreaOccupiedByOtherActor(actor, targetPos, &occupyingActors))
|
||||||
|
|
||||||
if (!result.first.isEmpty())
|
|
||||||
{
|
{
|
||||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
const float actorRadius = getActorRadius(actor);
|
||||||
return true;
|
const float distanceToTarget = distance(actorPos, targetPos);
|
||||||
|
for (const MWWorld::Ptr& other : occupyingActors)
|
||||||
|
{
|
||||||
|
const float otherRadius = getActorRadius(other);
|
||||||
|
const auto [minRadius, maxRadius] = std::minmax(actorRadius, otherRadius);
|
||||||
|
constexpr float toleranceFactor = 1.25;
|
||||||
|
if (minRadius * toleranceFactor + maxRadius > distanceToTarget)
|
||||||
|
{
|
||||||
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,8 @@ namespace MWMechanics
|
||||||
const float mZ;
|
const float mZ;
|
||||||
|
|
||||||
const bool mHidden;
|
const bool mHidden;
|
||||||
|
|
||||||
|
AiReactionTimer mDestinationCheck;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AiInternalTravel final : public AiTravel
|
struct AiInternalTravel final : public AiTravel
|
||||||
|
|
|
@ -85,14 +85,6 @@ namespace MWMechanics
|
||||||
return MWBase::Environment::get().getWorld()->castRay(position, visibleDestination, mask, actor);
|
return MWBase::Environment::get().getWorld()->castRay(position, visibleDestination, mask, actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr &actor, const osg::Vec3f& destination)
|
|
||||||
{
|
|
||||||
const auto world = MWBase::Environment::get().getWorld();
|
|
||||||
const osg::Vec3f halfExtents = world->getPathfindingHalfExtents(actor);
|
|
||||||
const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z()));
|
|
||||||
return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, actor);
|
|
||||||
}
|
|
||||||
|
|
||||||
void stopMovement(const MWWorld::Ptr& actor)
|
void stopMovement(const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
|
actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
|
||||||
|
@ -758,6 +750,9 @@ namespace MWMechanics
|
||||||
const ESM::Pathgrid *pathgrid =
|
const ESM::Pathgrid *pathgrid =
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*currentCell->getCell());
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*currentCell->getCell());
|
||||||
|
|
||||||
|
if (pathgrid == nullptr || pathgrid->mPoints.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
int index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest));
|
int index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest));
|
||||||
|
|
||||||
getPathGridGraph(currentCell).getNeighbouringPoints(index, points);
|
getPathGridGraph(currentCell).getNeighbouringPoints(index, points);
|
||||||
|
|
|
@ -545,20 +545,12 @@ namespace MWMechanics
|
||||||
mAiSequence.writeState(state.mAiSequence);
|
mAiSequence.writeState(state.mAiSequence);
|
||||||
mMagicEffects.writeState(state.mMagicEffects);
|
mMagicEffects.writeState(state.mMagicEffects);
|
||||||
|
|
||||||
state.mSummonedCreatureMap = mSummonedCreatures;
|
state.mSummonedCreatures = mSummonedCreatures;
|
||||||
state.mSummonGraveyard = mSummonGraveyard;
|
state.mSummonGraveyard = mSummonGraveyard;
|
||||||
|
|
||||||
state.mHasAiSettings = true;
|
state.mHasAiSettings = true;
|
||||||
for (int i=0; i<4; ++i)
|
for (int i=0; i<4; ++i)
|
||||||
mAiSettings[i].writeState (state.mAiSettings[i]);
|
mAiSettings[i].writeState (state.mAiSettings[i]);
|
||||||
|
|
||||||
for (auto it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it)
|
|
||||||
{
|
|
||||||
for (int i=0; i<ESM::Attribute::Length; ++i)
|
|
||||||
state.mCorprusSpells[it->first].mWorsenings[i] = mCorprusSpells.at(it->first).mWorsenings[i];
|
|
||||||
|
|
||||||
state.mCorprusSpells[it->first].mNextWorsening = mCorprusSpells.at(it->first).mNextWorsening.toEsm();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreatureStats::readState (const ESM::CreatureStats& state)
|
void CreatureStats::readState (const ESM::CreatureStats& state)
|
||||||
|
@ -618,7 +610,7 @@ namespace MWMechanics
|
||||||
// We can't use mActiveSpells::getMagicEffects here because it doesn't include expired effects
|
// We can't use mActiveSpells::getMagicEffects here because it doesn't include expired effects
|
||||||
auto spell = std::find_if(mActiveSpells.begin(), mActiveSpells.end(), [&] (const auto& spell)
|
auto spell = std::find_if(mActiveSpells.begin(), mActiveSpells.end(), [&] (const auto& spell)
|
||||||
{
|
{
|
||||||
const auto& effects = spell.second.mEffects;
|
const auto& effects = spell.getEffects();
|
||||||
return std::find_if(effects.begin(), effects.end(), [&] (const auto& effect)
|
return std::find_if(effects.begin(), effects.end(), [&] (const auto& effect)
|
||||||
{
|
{
|
||||||
return effect.mEffectId == effectId;
|
return effect.mEffectId == effectId;
|
||||||
|
@ -629,21 +621,12 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mSummonedCreatures = state.mSummonedCreatureMap;
|
mSummonedCreatures = state.mSummonedCreatures;
|
||||||
mSummonGraveyard = state.mSummonGraveyard;
|
mSummonGraveyard = state.mSummonGraveyard;
|
||||||
|
|
||||||
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]);
|
||||||
|
|
||||||
mCorprusSpells.clear();
|
|
||||||
for (auto it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it)
|
|
||||||
{
|
|
||||||
for (int i=0; i<ESM::Attribute::Length; ++i)
|
|
||||||
mCorprusSpells[it->first].mWorsenings[i] = state.mCorprusSpells.at(it->first).mWorsenings[i];
|
|
||||||
|
|
||||||
mCorprusSpells[it->first].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime)
|
void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime)
|
||||||
|
@ -710,7 +693,7 @@ namespace MWMechanics
|
||||||
return mTimeOfDeath;
|
return mTimeOfDeath;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<ESM::SummonKey, int>& CreatureStats::getSummonedCreatureMap()
|
std::multimap<int, int>& CreatureStats::getSummonedCreatureMap()
|
||||||
{
|
{
|
||||||
return mSummonedCreatures;
|
return mSummonedCreatures;
|
||||||
}
|
}
|
||||||
|
@ -719,23 +702,4 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
return mSummonGraveyard;
|
return mSummonGraveyard;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<std::string, CorprusStats> &CreatureStats::getCorprusSpells()
|
|
||||||
{
|
|
||||||
return mCorprusSpells;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreatureStats::addCorprusSpell(const std::string& sourceId, CorprusStats& stats)
|
|
||||||
{
|
|
||||||
mCorprusSpells[sourceId] = stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreatureStats::removeCorprusSpell(const std::string& sourceId)
|
|
||||||
{
|
|
||||||
auto corprusIt = mCorprusSpells.find(sourceId);
|
|
||||||
if (corprusIt != mCorprusSpells.end())
|
|
||||||
{
|
|
||||||
mCorprusSpells.erase(corprusIt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef GAME_MWMECHANICS_CREATURESTATS_H
|
#ifndef GAME_MWMECHANICS_CREATURESTATS_H
|
||||||
#define GAME_MWMECHANICS_CREATURESTATS_H
|
#define GAME_MWMECHANICS_CREATURESTATS_H
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
@ -87,14 +88,12 @@ namespace MWMechanics
|
||||||
float mSideMovementAngle;
|
float mSideMovementAngle;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::map<ESM::SummonKey, int> mSummonedCreatures; // <SummonKey, ActorId>
|
std::multimap<int, int> mSummonedCreatures; // <Effect, ActorId>
|
||||||
|
|
||||||
// Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet.
|
// Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet.
|
||||||
// This may be necessary when the creature is in an inactive cell.
|
// This may be necessary when the creature is in an inactive cell.
|
||||||
std::vector<int> mSummonGraveyard;
|
std::vector<int> mSummonGraveyard;
|
||||||
|
|
||||||
std::map<std::string, CorprusStats> mCorprusSpells;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int mLevel;
|
int mLevel;
|
||||||
|
|
||||||
|
@ -236,7 +235,7 @@ namespace MWMechanics
|
||||||
void setBlock(bool value);
|
void setBlock(bool value);
|
||||||
bool getBlock() const;
|
bool getBlock() const;
|
||||||
|
|
||||||
std::map<ESM::SummonKey, int>& getSummonedCreatureMap(); // <SummonKey, ActorId of summoned creature>
|
std::multimap<int, int>& getSummonedCreatureMap(); // <Effect, ActorId of summoned creature>
|
||||||
std::vector<int>& getSummonedCreatureGraveyard(); // ActorIds
|
std::vector<int>& getSummonedCreatureGraveyard(); // ActorIds
|
||||||
|
|
||||||
enum Flag
|
enum Flag
|
||||||
|
@ -297,12 +296,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
static void cleanup();
|
static void cleanup();
|
||||||
|
|
||||||
std::map<std::string, CorprusStats> & getCorprusSpells();
|
|
||||||
|
|
||||||
void addCorprusSpell(const std::string& sourceId, CorprusStats& stats);
|
|
||||||
|
|
||||||
void removeCorprusSpell(const std::string& sourceId);
|
|
||||||
|
|
||||||
float getSideMovementAngle() const { return mSideMovementAngle; }
|
float getSideMovementAngle() const { return mSideMovementAngle; }
|
||||||
void setSideMovementAngle(float angle) { mSideMovementAngle = angle; }
|
void setSideMovementAngle(float angle) { mSideMovementAngle = angle; }
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,12 +30,11 @@ namespace MWMechanics
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||||
"fDiseaseXferChance")->mValue.getFloat();
|
"fDiseaseXferChance")->mValue.getFloat();
|
||||||
|
|
||||||
MagicEffects& actorEffects = actor.getClass().getCreatureStats(actor).getMagicEffects();
|
const MagicEffects& actorEffects = actor.getClass().getCreatureStats(actor).getMagicEffects();
|
||||||
|
|
||||||
Spells& spells = carrier.getClass().getCreatureStats(carrier).getSpells();
|
Spells& spells = carrier.getClass().getCreatureStats(carrier).getSpells();
|
||||||
for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
|
for (const ESM::Spell* spell : spells)
|
||||||
{
|
{
|
||||||
const ESM::Spell* spell = it->first;
|
|
||||||
if (actor.getClass().getCreatureStats(actor).getSpells().hasSpell(spell->mId))
|
if (actor.getClass().getCreatureStats(actor).getSpells().hasSpell(spell->mId))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -56,7 +55,7 @@ namespace MWMechanics
|
||||||
if (Misc::Rng::rollDice(10000) < x)
|
if (Misc::Rng::rollDice(10000) < x)
|
||||||
{
|
{
|
||||||
// Contracted disease!
|
// Contracted disease!
|
||||||
actor.getClass().getCreatureStats(actor).getSpells().add(it->first);
|
actor.getClass().getCreatureStats(actor).getSpells().add(spell);
|
||||||
MWBase::Environment::get().getWorld()->applyLoopingParticles(actor);
|
MWBase::Environment::get().getWorld()->applyLoopingParticles(actor);
|
||||||
|
|
||||||
std::string msg = "sMagicContractDisease";
|
std::string msg = "sMagicContractDisease";
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
#include "linkedeffects.hpp"
|
|
||||||
|
|
||||||
#include <components/misc/rng.hpp>
|
|
||||||
#include <components/settings/settings.hpp>
|
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
|
||||||
#include "../mwbase/world.hpp"
|
|
||||||
|
|
||||||
#include "../mwrender/animation.hpp"
|
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
|
||||||
|
|
||||||
#include "creaturestats.hpp"
|
|
||||||
|
|
||||||
namespace MWMechanics
|
|
||||||
{
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect,
|
|
||||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source)
|
|
||||||
{
|
|
||||||
if (caster.isEmpty() || caster == target)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!target.getClass().isActor() || !caster.getClass().isActor())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Make sure callers don't do something weird
|
|
||||||
if (effect.mEffectID < ESM::MagicEffect::AbsorbAttribute || effect.mEffectID > ESM::MagicEffect::AbsorbSkill)
|
|
||||||
throw std::runtime_error("invalid absorb stat effect");
|
|
||||||
|
|
||||||
if (appliedEffect.mMagnitude == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::vector<ActiveSpells::ActiveEffect> absorbEffects;
|
|
||||||
ActiveSpells::ActiveEffect absorbEffect = appliedEffect;
|
|
||||||
absorbEffect.mMagnitude *= -1;
|
|
||||||
absorbEffect.mEffectIndex = appliedEffect.mEffectIndex;
|
|
||||||
absorbEffects.emplace_back(absorbEffect);
|
|
||||||
|
|
||||||
// Morrowind negates reflected Absorb spells so the original caster won't be harmed.
|
|
||||||
if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"))
|
|
||||||
{
|
|
||||||
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(std::string(), true,
|
|
||||||
absorbEffects, source, caster.getClass().getCreatureStats(caster).getActorId());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(std::string(), true,
|
|
||||||
absorbEffects, source, target.getClass().getCreatureStats(target).getActorId());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
#ifndef MWMECHANICS_LINKEDEFFECTS_H
|
|
||||||
#define MWMECHANICS_LINKEDEFFECTS_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ESM
|
|
||||||
{
|
|
||||||
struct ActiveEffect;
|
|
||||||
struct EffectList;
|
|
||||||
struct ENAMstruct;
|
|
||||||
struct MagicEffect;
|
|
||||||
struct Spell;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWWorld
|
|
||||||
{
|
|
||||||
class Ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWMechanics
|
|
||||||
{
|
|
||||||
|
|
||||||
// Try to reflect a spell effect. If it's reflected, it's also put into the passed reflected effects list.
|
|
||||||
bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect,
|
|
||||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects);
|
|
||||||
|
|
||||||
// Try to absorb a stat (skill, attribute, etc.) from the target and transfer it to the caster.
|
|
||||||
void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect,
|
|
||||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -193,22 +193,22 @@ namespace MWMechanics
|
||||||
|
|
||||||
void MagicEffects::writeState(ESM::MagicEffects &state) const
|
void MagicEffects::writeState(ESM::MagicEffects &state) const
|
||||||
{
|
{
|
||||||
// Don't need to save Modifiers, they are recalculated every frame anyway.
|
for (const auto& [key, params] : mCollection)
|
||||||
for (Collection::const_iterator iter (begin()); iter!=end(); ++iter)
|
|
||||||
{
|
{
|
||||||
if (iter->second.getBase() != 0)
|
if (params.getBase() != 0 || params.getModifier() != 0.f)
|
||||||
{
|
{
|
||||||
// Don't worry about mArg, never used by magic effect script instructions
|
// Don't worry about mArg, never used by magic effect script instructions
|
||||||
state.mEffects.insert(std::make_pair(iter->first.mId, iter->second.getBase()));
|
state.mEffects[key.mId] = {params.getBase(), params.getModifier()};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MagicEffects::readState(const ESM::MagicEffects &state)
|
void MagicEffects::readState(const ESM::MagicEffects &state)
|
||||||
{
|
{
|
||||||
for (std::map<int, int>::const_iterator it = state.mEffects.begin(); it != state.mEffects.end(); ++it)
|
for (const auto& [key, params] : state.mEffects)
|
||||||
{
|
{
|
||||||
mCollection[EffectKey(it->first)].setBase(it->second);
|
mCollection[EffectKey(key)].setBase(params.first);
|
||||||
|
mCollection[EffectKey(key)].setModifier(params.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,16 +69,6 @@ namespace MWMechanics
|
||||||
return param -= right;
|
return param -= right;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display
|
|
||||||
struct EffectSourceVisitor
|
|
||||||
{
|
|
||||||
virtual ~EffectSourceVisitor() { }
|
|
||||||
|
|
||||||
virtual void visit (EffectKey key, int effectIndex,
|
|
||||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
|
||||||
float magnitude, float remainingTime = -1, float totalTime = -1) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// \brief Effects currently affecting a NPC or creature
|
/// \brief Effects currently affecting a NPC or creature
|
||||||
class MagicEffects
|
class MagicEffects
|
||||||
{
|
{
|
||||||
|
|
|
@ -84,7 +84,7 @@ namespace MWMechanics
|
||||||
// reset
|
// reset
|
||||||
creatureStats.setLevel(player->mNpdt.mLevel);
|
creatureStats.setLevel(player->mNpdt.mLevel);
|
||||||
creatureStats.getSpells().clear(true);
|
creatureStats.getSpells().clear(true);
|
||||||
creatureStats.modifyMagicEffects(MagicEffects());
|
creatureStats.getActiveSpells().clear(ptr);
|
||||||
|
|
||||||
for (int i=0; i<27; ++i)
|
for (int i=0; i<27; ++i)
|
||||||
npcStats.getSkill (i).setBase (player->mNpdt.mSkills[i]);
|
npcStats.getSkill (i).setBase (player->mNpdt.mSkills[i]);
|
||||||
|
@ -213,6 +213,7 @@ namespace MWMechanics
|
||||||
int attributes[ESM::Attribute::Length];
|
int attributes[ESM::Attribute::Length];
|
||||||
for (int i=0; i<ESM::Attribute::Length; ++i)
|
for (int i=0; i<ESM::Attribute::Length; ++i)
|
||||||
attributes[i] = npcStats.getAttribute(i).getBase();
|
attributes[i] = npcStats.getAttribute(i).getBase();
|
||||||
|
npcStats.updateHealth();
|
||||||
|
|
||||||
std::vector<std::string> selectedSpells = autoCalcPlayerSpells(skills, attributes, race);
|
std::vector<std::string> selectedSpells = autoCalcPlayerSpells(skills, attributes, race);
|
||||||
|
|
||||||
|
@ -221,6 +222,7 @@ 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)
|
||||||
{
|
{
|
||||||
|
@ -257,11 +259,11 @@ namespace MWMechanics
|
||||||
mActors.castSpell(ptr, spellId, manualSpell);
|
mActors.castSpell(ptr, spellId, manualSpell);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MechanicsManager::remove(const MWWorld::Ptr& ptr)
|
void MechanicsManager::remove(const MWWorld::Ptr& ptr, bool keepActive)
|
||||||
{
|
{
|
||||||
if(ptr == MWBase::Environment::get().getWindowManager()->getWatchedActor())
|
if(ptr == MWBase::Environment::get().getWindowManager()->getWatchedActor())
|
||||||
MWBase::Environment::get().getWindowManager()->watchActor(MWWorld::Ptr());
|
MWBase::Environment::get().getWindowManager()->watchActor(MWWorld::Ptr());
|
||||||
mActors.removeActor(ptr);
|
mActors.removeActor(ptr, keepActive);
|
||||||
mObjects.removeObject(ptr);
|
mObjects.removeObject(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,24 +284,6 @@ namespace MWMechanics
|
||||||
mObjects.dropObjects(cellStore);
|
mObjects.dropObjects(cellStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MechanicsManager::restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId)
|
|
||||||
{
|
|
||||||
auto& stats = actor.getClass().getCreatureStats (actor);
|
|
||||||
auto& corprusSpells = stats.getCorprusSpells();
|
|
||||||
|
|
||||||
auto corprusIt = corprusSpells.find(sourceId);
|
|
||||||
|
|
||||||
if (corprusIt != corprusSpells.end())
|
|
||||||
{
|
|
||||||
for (int i = 0; i < ESM::Attribute::Length; ++i)
|
|
||||||
{
|
|
||||||
MWMechanics::AttributeValue attr = stats.getAttribute(i);
|
|
||||||
attr.restore(corprusIt->second.mWorsenings[i]);
|
|
||||||
actor.getClass().getCreatureStats(actor).setAttribute(i, attr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MechanicsManager::update(float duration, bool paused)
|
void MechanicsManager::update(float duration, bool paused)
|
||||||
{
|
{
|
||||||
// Note: we should do it here since game mechanics and world updates use these values
|
// Note: we should do it here since game mechanics and world updates use these values
|
||||||
|
@ -333,7 +317,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
// HACK? The player has been changed, so a new Animation object may
|
// HACK? The player has been changed, so a new Animation object may
|
||||||
// have been made for them. Make sure they're properly updated.
|
// have been made for them. Make sure they're properly updated.
|
||||||
mActors.removeActor(ptr);
|
mActors.removeActor(ptr, true);
|
||||||
mActors.addActor(ptr, true);
|
mActors.addActor(ptr, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ namespace MWMechanics
|
||||||
void add (const MWWorld::Ptr& ptr) override;
|
void add (const MWWorld::Ptr& ptr) override;
|
||||||
///< Register an object for management
|
///< Register an object for management
|
||||||
|
|
||||||
void remove (const MWWorld::Ptr& ptr) override;
|
void remove (const MWWorld::Ptr& ptr, bool keepActive) override;
|
||||||
///< Deregister an object for management
|
///< Deregister an object for management
|
||||||
|
|
||||||
void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) override;
|
void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) override;
|
||||||
|
@ -230,8 +230,6 @@ namespace MWMechanics
|
||||||
GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override;
|
GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override;
|
||||||
bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override;
|
bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override;
|
||||||
|
|
||||||
void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
|
bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
|
||||||
bool canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set<MWWorld::Ptr> &playerFollowers);
|
bool canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set<MWWorld::Ptr> &playerFollowers);
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
#include "movement.hpp"
|
#include "movement.hpp"
|
||||||
|
|
||||||
|
@ -72,6 +74,15 @@ namespace MWMechanics
|
||||||
return MWWorld::Ptr(); // none found
|
return MWWorld::Ptr(); // none found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination,
|
||||||
|
std::vector<MWWorld::Ptr>* occupyingActors)
|
||||||
|
{
|
||||||
|
const auto world = MWBase::Environment::get().getWorld();
|
||||||
|
const osg::Vec3f halfExtents = world->getPathfindingHalfExtents(actor);
|
||||||
|
const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z()));
|
||||||
|
return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, actor, occupyingActors);
|
||||||
|
}
|
||||||
|
|
||||||
ObstacleCheck::ObstacleCheck()
|
ObstacleCheck::ObstacleCheck()
|
||||||
: mWalkState(WalkState::Initial)
|
: mWalkState(WalkState::Initial)
|
||||||
, mStateDuration(0)
|
, mStateDuration(0)
|
||||||
|
|
|
@ -3,9 +3,12 @@
|
||||||
|
|
||||||
#include <osg/Vec3f>
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
class Ptr;
|
class Ptr;
|
||||||
|
class ConstPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
|
@ -21,6 +24,9 @@ namespace MWMechanics
|
||||||
/** \return Pointer to the door, or empty pointer if none exists **/
|
/** \return Pointer to the door, or empty pointer if none exists **/
|
||||||
const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist);
|
const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist);
|
||||||
|
|
||||||
|
bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination,
|
||||||
|
std::vector<MWWorld::Ptr>* occupyingActors = nullptr);
|
||||||
|
|
||||||
class ObstacleCheck
|
class ObstacleCheck
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -397,7 +397,7 @@ namespace MWMechanics
|
||||||
mPath.clear();
|
mPath.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty())
|
if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty() && (flags & DetourNavigator::Flag_usePathgrid) == 0)
|
||||||
{
|
{
|
||||||
status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents,
|
status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents,
|
||||||
flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType, std::back_inserter(mPath));
|
flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType, std::back_inserter(mPath));
|
||||||
|
|
|
@ -16,33 +16,30 @@
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
float getProbability(const MWMechanics::ActiveSpells& activeSpells)
|
||||||
class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor
|
|
||||||
{
|
{
|
||||||
public:
|
float probability = 0.f;
|
||||||
float mProbability{0.f};
|
for(const auto& params : activeSpells)
|
||||||
|
|
||||||
GetAbsorptionProbability() = default;
|
|
||||||
|
|
||||||
void visit (MWMechanics::EffectKey key, int /*effectIndex*/,
|
|
||||||
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
|
|
||||||
float magnitude, float /*remainingTime*/, float /*totalTime*/) override
|
|
||||||
{
|
{
|
||||||
if (key.mId == ESM::MagicEffect::SpellAbsorption)
|
for(const auto& effect : params.getEffects())
|
||||||
{
|
{
|
||||||
if (mProbability == 0.f)
|
if(effect.mEffectId == ESM::MagicEffect::SpellAbsorption)
|
||||||
mProbability = magnitude / 100;
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// If there are different sources of SpellAbsorption effect, multiply failing probability for all effects.
|
if(probability == 0.f)
|
||||||
// Real absorption probability will be the (1 - total fail chance) in this case.
|
probability = effect.mMagnitude / 100;
|
||||||
float failProbability = 1.f - mProbability;
|
else
|
||||||
failProbability *= 1.f - magnitude / 100;
|
{
|
||||||
mProbability = 1.f - failProbability;
|
// 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)
|
bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
|
||||||
{
|
{
|
||||||
|
@ -53,13 +50,7 @@ namespace MWMechanics
|
||||||
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f)
|
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
GetAbsorptionProbability check;
|
int chance = getProbability(stats.getActiveSpells());
|
||||||
stats.getActiveSpells().visitEffectSources(check);
|
|
||||||
stats.getSpells().visitEffectSources(check);
|
|
||||||
if (target.getClass().hasInventoryStore(target))
|
|
||||||
target.getClass().getInventoryStore(target).visitEffectSources(check);
|
|
||||||
|
|
||||||
int chance = check.mProbability * 100;
|
|
||||||
if (Misc::Rng::roll0to99() >= chance)
|
if (Misc::Rng::roll0to99() >= chance)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue