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.
pull/3225/head
AnyOldName3 3 years ago
commit 1b83b08d80

@ -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
------------- -------------

@ -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* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals");
connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView()));
QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos"); QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos");
connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView())); connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView()));
characters->addSeparator();
QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals");
connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView()));
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)

@ -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

@ -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;

@ -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,347 +5,442 @@
#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 MWMechanics namespace
{ {
void ActiveSpells::update(float duration) const bool merge(std::vector<ESM::ActiveEffect>& present, const std::vector<ESM::ActiveEffect>& queued)
{ {
bool rebuild = false; // 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)
// Erase no longer active spells and effects
if (duration > 0)
{ {
TContainer::iterator iter (mSpells.begin()); return std::find_if(present.begin(), present.end(), [&] (const auto& pEffect) { return pEffect.mEffectIndex == qEffect.mEffectIndex; }) != present.end();
while (iter!=mSpells.end()) });
{ if(problem != queued.end())
if (!timeToExpire (iter)) return false;
{ present.insert(present.end(), queued.begin(), queued.end());
mSpells.erase (iter++); return true;
rebuild = true;
}
else
{
bool interrupt = false;
std::vector<ActiveEffect>& effects = iter->second.mEffects;
for (std::vector<ActiveEffect>::iterator effectIt = effects.begin(); effectIt != effects.end();)
{
if (effectIt->mTimeLeft <= 0)
{
rebuild = true;
// Note: it we expire a Corprus effect, we should remove the whole spell.
if (effectIt->mEffectId == ESM::MagicEffect::Corprus)
{
iter = mSpells.erase (iter);
interrupt = true;
break;
}
effectIt = effects.erase(effectIt);
}
else
{
effectIt->mTimeLeft -= duration;
++effectIt;
}
}
if (!interrupt)
++iter;
}
}
}
if (mSpellsChanged)
{
mSpellsChanged = false;
rebuild = true;
}
if (rebuild)
rebuildEffects();
} }
void ActiveSpells::rebuildEffects() const void addEffects(std::vector<ESM::ActiveEffect>& effects, const ESM::EffectList& list, bool ignoreResistances = false)
{ {
mEffects = MagicEffects(); int currentEffectIndex = 0;
for(const auto& enam : list.mList)
for (TIterator iter (begin()); iter!=end(); ++iter)
{ {
const std::vector<ActiveEffect>& effects = iter->second.mEffects; ESM::ActiveEffect effect;
effect.mEffectId = enam.mEffectID;
for (std::vector<ActiveEffect>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) effect.mArg = MWMechanics::EffectKey(enam).mArg;
{ effect.mMagnitude = 0.f;
if (effectIt->mTimeLeft > 0) effect.mMinMagnitude = enam.mMagnMin;
mEffects.add(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), MWMechanics::EffectParam(effectIt->mMagnitude)); 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);
} }
} }
}
ActiveSpells::ActiveSpells() namespace MWMechanics
: mSpellsChanged (false) {
{} ActiveSpells::IterationGuard::IterationGuard(ActiveSpells& spells) : mActiveSpells(spells)
{
mActiveSpells.mIterating = true;
}
const MagicEffects& ActiveSpells::getMagicEffects() const ActiveSpells::IterationGuard::~IterationGuard()
{ {
update(0.f); mActiveSpells.mIterating = false;
return mEffects;
} }
ActiveSpells::TIterator ActiveSpells::begin() const 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)
{ {
return mSpells.begin(); if(!caster.isEmpty() && caster.getClass().isActor())
mCasterActorId = caster.getClass().getCreatureStats(caster).getActorId();
} }
ActiveSpells::TIterator ActiveSpells::end() const 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)
{ {
return mSpells.end(); assert(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power);
addEffects(mEffects, spell->mEffects, ignoreResistances);
} }
double ActiveSpells::timeToExpire (const TIterator& iterator) const 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)
{ {
const std::vector<ActiveEffect>& effects = iterator->second.mEffects; assert(enchantment->mData.mType == ESM::Enchantment::ConstantEffect);
addEffects(mEffects, enchantment->mEffects);
}
float duration = 0; 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})
{}
for (std::vector<ActiveEffect>::const_iterator iter (effects.begin()); ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const
iter!=effects.end(); ++iter) {
ESM::ActiveSpells::ActiveSpellParams params;
params.mId = mId;
params.mEffects = mEffects;
params.mDisplayName = mDisplayName;
params.mCasterActorId = mCasterActorId;
params.mItem.unset();
if(mSlot)
{ {
if (iter->mTimeLeft > duration) // Note that we're storing the inventory slot as a RefNum instead of an int as a matter of future proofing
duration = iter->mTimeLeft; // 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;
}
if (duration < 0) void ActiveSpells::ActiveSpellParams::worsen()
return 0; {
++mWorsenings;
return duration; if(!mWorsenings)
mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp();
mNextWorsening += CorprusStats::sWorseningPeriod;
} }
bool ActiveSpells::isSpellActive(const std::string& id) const bool ActiveSpells::ActiveSpellParams::shouldWorsen() const
{ {
for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter) return mWorsenings >= 0 && MWBase::Environment::get().getWorld()->getTimeStamp() >= mNextWorsening;
{
if (Misc::StringUtils::ciEqual(iter->first, id))
return true;
}
return false;
} }
const ActiveSpells::TContainer& ActiveSpells::getActiveSpells() const void ActiveSpells::ActiveSpellParams::resetWorsenings()
{ {
return mSpells; mWorsenings = -1;
} }
void ActiveSpells::addSpell(const std::string &id, bool stack, const std::vector<ActiveEffect>& effects, void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration)
const std::string &displayName, int casterActorId)
{ {
TContainer::iterator it(mSpells.find(id)); 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)
{
++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)
{
auto effect = *effectIt;
effectIt = spellIt->mEffects.erase(effectIt);
onMagicEffectRemoved(ptr, *spellIt, effect);
removedSpell = applyPurges(ptr, &spellIt, &effectIt);
if(removedSpell)
break;
}
else
{
++effectIt;
}
}
if(removedSpell)
continue;
if(spellIt->mEffects.empty())
spellIt = mSpells.erase(spellIt);
else
++spellIt;
}
ActiveSpellParams params; for(const auto& spell : mQueue)
params.mEffects = effects; addToSpells(ptr, spell);
params.mDisplayName = displayName; mQueue.clear();
params.mCasterActorId = casterActorId;
if (it == end() || stack) // Vanilla only does this on cell change I think
const auto& spells = creatureStats.getSpells();
for(const ESM::Spell* spell : spells)
{ {
mSpells.insert(std::make_pair(id, params)); if(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power && !isSpellActive(spell->mId))
mSpells.emplace_back(ActiveSpellParams{spell, ptr});
} }
else
if(ptr.getClass().hasInventoryStore(ptr) && !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished()))
{ {
// addSpell() is called with effects for a range. auto& store = ptr.getClass().getInventoryStore(ptr);
// but a spell may have effects with different ranges (e.g. Touch & Target) if(store.getInvListener() != nullptr)
// so, if we see new effects for same spell assume additional {
// spell effects and add to existing effects of spell bool playNonLooping = !store.isFirstEquip();
mergeEffects(params.mEffects, it->second.mEffects); const auto world = MWBase::Environment::get().getWorld();
it->second = params; 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)
{
return params.mSlot == slotIndex && params.mType == ESM::ActiveSpells::Type_Enchantment && params.mId == slot->getCellRef().getRefId();
}) != mSpells.end())
continue;
ActiveSpellParams params(*slot, enchantment, slotIndex, ptr);
mSpells.emplace_back(params);
for(const auto& effect : params.mEffects)
MWMechanics::playEffects(ptr, *world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId), playNonLooping);
}
}
} }
mSpellsChanged = true; // Update effects
} for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
void ActiveSpells::mergeEffects(std::vector<ActiveEffect>& addTo, const std::vector<ActiveEffect>& from)
{
for (std::vector<ActiveEffect>::const_iterator effect(from.begin()); effect != from.end(); ++effect)
{ {
// if effect is not in addTo, add it const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid?
bool missing = true; bool removedSpell = false;
for (std::vector<ActiveEffect>::const_iterator iter(addTo.begin()); iter != addTo.end(); ++iter) for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
{ {
if ((effect->mEffectId == iter->mEffectId) && (effect->mArg == iter->mArg)) bool remove = applyMagicEffect(ptr, caster, *spellIt, *it, duration);
{ if(remove)
missing = false; it = spellIt->mEffects.erase(it);
else
++it;
removedSpell = applyPurges(ptr, &spellIt, &it);
if(removedSpell)
break; break;
}
} }
if (missing) if(removedSpell)
continue;
bool remove = false;
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)
{ {
addTo.push_back(*effect); 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::removeEffects(const std::string &id) void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell)
{ {
for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell) if(spell.mType != ESM::ActiveSpells::Type_Consumable)
{ {
if (spell->first == id) auto found = std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& existing)
{
return spell.mId == existing.mId && spell.mCasterActorId == existing.mCasterActorId && spell.mSlot == existing.mSlot;
});
if(found != mSpells.end())
{ {
spell->second.mEffects.clear(); if(merge(found->mEffects, spell.mEffects))
mSpellsChanged = true; return;
auto params = *found;
mSpells.erase(found);
for(const auto& effect : params.mEffects)
onMagicEffectRemoved(ptr, params, effect);
} }
} }
mSpells.emplace_back(spell);
} }
void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const ActiveSpells::ActiveSpells() : mIterating(false)
{}
ActiveSpells::TIterator ActiveSpells::begin() const
{ {
for (TContainer::const_iterator it = begin(); it != end(); ++it) return mSpells.begin();
{ }
for (std::vector<ActiveEffect>::const_iterator effectIt = it->second.mEffects.begin();
effectIt != it->second.mEffects.end(); ++effectIt)
{
std::string name = it->second.mDisplayName;
float magnitude = effectIt->mMagnitude; ActiveSpells::TIterator ActiveSpells::end() const
if (magnitude) {
visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), effectIt->mEffectIndex, name, it->first, it->second.mCasterActorId, magnitude, effectIt->mTimeLeft, effectIt->mDuration); return mSpells.end();
}
}
} }
void ActiveSpells::purgeAll(float chance, bool spellOnly) bool ActiveSpells::isSpellActive(const std::string& id) const
{ {
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ) return std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& spell)
{ {
const std::string spellId = it->first; return Misc::StringUtils::ciEqual(spell.mId, id);
}) != mSpells.end();
}
// if spellOnly is true, dispell only spells. Leave potions, enchanted items etc. void ActiveSpells::addSpell(const ActiveSpellParams& params)
if (spellOnly) {
{ mQueue.emplace_back(params);
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) void ActiveSpells::addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor)
mSpells.erase(it++); {
else mQueue.emplace_back(ActiveSpellParams{spell, actor, true});
++it;
}
mSpellsChanged = true;
} }
void ActiveSpells::purgeEffect(short effectId) void ActiveSpells::purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr)
{ {
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this);
mPurges.emplace(predicate);
if(!mIterating)
{ {
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin(); IterationGuard guard{*this};
effectIt != it->second.mEffects.end();) applyPurges(ptr);
{
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) void ActiveSpells::purge(EffectPredicate predicate, const MWWorld::Ptr& ptr)
{ {
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this);
mPurges.emplace(predicate);
if(!mIterating)
{ {
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin(); IterationGuard guard{*this};
effectIt != it->second.mEffects.end();) applyPurges(ptr);
{
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) bool ActiveSpells::applyPurges(const MWWorld::Ptr& ptr, std::list<ActiveSpellParams>::iterator* currentSpell, std::vector<ActiveEffect>::iterator* currentEffect)
{ {
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) bool removedCurrentSpell = false;
while(!mPurges.empty())
{ {
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin(); auto predicate = mPurges.front();
effectIt != it->second.mEffects.end();) mPurges.pop();
for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
{ {
if (it->second.mCasterActorId == casterActorId) bool isCurrentSpell = currentSpell && *currentSpell == spellIt;
effectIt = it->second.mEffects.erase(effectIt); std::visit([&] (auto&& variant)
else {
++effectIt; using T = std::decay_t<decltype(variant)>;
if constexpr (std::is_same_v<T, ParamsPredicate>)
{
if(variant(*spellIt))
{
auto params = *spellIt;
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);
} }
} }
mSpellsChanged = true; return removedCurrentSpell;
} }
void ActiveSpells::purgeCorprusDisease() void ActiveSpells::removeEffects(const MWWorld::Ptr& ptr, const std::string &id)
{ {
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) purge([=] (const ActiveSpellParams& params)
{ {
bool hasCorprusEffect = false; return params.mId == id;
for (std::vector<ActiveEffect>::iterator effectIt = iter->second.mEffects.begin(); }, ptr);
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() void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, short effectId)
{ {
mSpells.clear(); purge([=] (const ActiveSpellParams&, const ESM::ActiveEffect& effect)
mSpellsChanged = true; {
return effect.mEffectId == effectId;
}, ptr);
} }
void ActiveSpells::writeState(ESM::ActiveSpells &state) const void ActiveSpells::purge(const MWWorld::Ptr& ptr, int casterActorId)
{ {
for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) purge([=] (const ActiveSpellParams& params)
{ {
// Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp return params.mCasterActorId == casterActorId;
ESM::ActiveSpells::ActiveSpellParams params; }, ptr);
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::clear(const MWWorld::Ptr& ptr)
} {
mQueue.clear();
purge([] (const ActiveSpellParams& params) { return true; }, ptr);
} }
void ActiveSpells::readState(const ESM::ActiveSpells &state) void ActiveSpells::skipWorsenings(double hours)
{ {
for (ESM::ActiveSpells::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) for(auto& spell : mSpells)
{ {
// Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp if(spell.mWorsenings >= 0)
ActiveSpellParams params; spell.mNextWorsening += hours;
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;
} }
} }
void ActiveSpells::writeState(ESM::ActiveSpells &state) const
{
for(const auto& spell : mSpells)
state.mSpells.emplace_back(spell.toEsm());
for(const auto& spell : mQueue)
state.mQueue.emplace_back(spell.toEsm());
}
void ActiveSpells::readState(const ESM::ActiveSpells &state)
{
for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mSpells)
mSpells.emplace_back(ActiveSpellParams{spell});
for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue)
mQueue.emplace_back(ActiveSpellParams{spell});
}
} }

@ -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;
ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params);
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; }
// The caster that inflicted this spell on us ESM::ActiveSpells::EffectType getType() const { return mType; }
int mCasterActorId;
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 purgeEffect (short effectId, const std::string& sourceId, int effectIndex=-1);
/// Remove all active effects, if roll succeeds (for each effect) void purge(EffectPredicate predicate, const MWWorld::Ptr& ptr);
void purgeAll(float chance, bool spellOnly = false); void purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr);
/// Remove all effects with CASTER_LINKED flag that were cast by \a casterActorId /// Remove all effects that were cast by \a casterActorId
void purge (int casterActorId); void purge (const MWWorld::Ptr& ptr, 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…
Cancel
Save