mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-28 21:39:48 +00:00
Merge remote-tracking branch 'upstream/master' into detain-hash
Tests which no longer work are commented out. Some of these don't work because they're effectively testing for the presence of bugs in the old implementation. Others don't work because we're no longer accidentally disabling the boost::program_options feature where it generates an error if only part of a token gets consumed. These will be fixed by later commits.
This commit is contained in:
commit
1b83b08d80
313 changed files with 5815 additions and 5055 deletions
50
.github/workflows/cmake.yml
vendored
Normal file
50
.github/workflows/cmake.yml
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
name: CMake
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
BUILD_TYPE: RelWithDebInfo
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Add OpenMW PPA Dependancies
|
||||
run: sudo add-apt-repository ppa:openmw/openmw; sudo apt-get update
|
||||
|
||||
- name: Install Building Dependancies
|
||||
run: sudo CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
|
||||
|
||||
- name: Prime ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
key: ${{ matrix.os }}-${{ env.BUILD_TYPE }}
|
||||
max-size: 1000M
|
||||
|
||||
- name: Configure
|
||||
run: cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_FLAGS='-Werror' -DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy"
|
||||
|
||||
- name: Build
|
||||
run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
run: cmake --install .
|
||||
|
||||
- name: Create Artifact
|
||||
shell: bash
|
||||
working-directory: install
|
||||
run: |
|
||||
ls -laR
|
||||
7z a ../build_artifact.7z .
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
path: ./build_artifact.7z
|
||||
name: build_artifact.7z
|
|
@ -80,6 +80,7 @@ Programmers
|
|||
Federico Guerra (FedeWar)
|
||||
Fil Krynicki (filkry)
|
||||
Finbar Crago (finbar-crago)
|
||||
Florent Teppe (Tetramir)
|
||||
Florian Weber (Florianjw)
|
||||
Frédéric Chardon (fr3dz10)
|
||||
Gaëtan Dezeiraud (Brouilles)
|
||||
|
@ -228,6 +229,7 @@ Programmers
|
|||
Yuri Krupenin
|
||||
zelurker
|
||||
Noah Gooder
|
||||
Andrew Appuhamy (andrew-app)
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
|
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -1,6 +1,7 @@
|
|||
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 #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)
|
||||
|
@ -10,20 +11,25 @@
|
|||
Bug #4602: Robert's Bodies: crash inside createInstance()
|
||||
Bug #4700: Editor: Incorrect command implementation
|
||||
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 #5120: Scripted object spawning updates physics system
|
||||
Bug #5207: Loose summons can be present in scene
|
||||
Bug #5379: Wandering NPCs falling through cantons
|
||||
Bug #5453: Magic effect VFX are offset for creatures
|
||||
Bug #5483: AutoCalc flag is not used to calculate spells cost
|
||||
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 #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention
|
||||
Bug #5842: GetDisposition adds temporary disposition change from different actors
|
||||
Bug #5863: GetEffect should return true after the player has teleported
|
||||
Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher
|
||||
Bug #6051: NaN water height in ESM file is not handled gracefully
|
||||
Bug #6066: addtopic "return" does not work from within script. No errors thrown
|
||||
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 #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed
|
||||
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 #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 #6172: Some creatures can't open doors
|
||||
Bug #6174: Spellmaking and Enchanting sliders differences from vanilla
|
||||
Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla
|
||||
Bug #6197: Infinite Casting Loop
|
||||
Bug #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 #2780: A way to see current OpenMW version in the console
|
||||
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 #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 #5737: Handle instance move from one cell to another
|
||||
Feature #5996: Support Lua scripts in OpenMW
|
||||
Feature #6017: Separate persistent and temporary cell references when saving
|
||||
Feature #6032: Reverse-z depth buffer
|
||||
Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly
|
||||
Feature #6199: Support FBO Rendering
|
||||
Feature #6249: Alpha testing support for Collada
|
||||
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
|
||||
|
||||
|
||||
0.47.0
|
||||
------
|
||||
|
||||
|
@ -184,6 +208,7 @@
|
|||
Bug #6043: Actor can have torch missing when torch animation is played
|
||||
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 #6294: Game crashes with empty pathgrid
|
||||
Feature #390: 3rd person look "over the shoulder"
|
||||
Feature #832: OpenMW-CS: Handle deleted references
|
||||
Feature #1536: Show more information about level on menu
|
||||
|
@ -213,6 +238,7 @@
|
|||
Feature #5519: Code Patch tab in launcher
|
||||
Feature #5524: Resume failed script execution after reload
|
||||
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 #5579: MCP SetAngle enhancement
|
||||
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 #5828: Support more than 8 lights
|
||||
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 #6033: Include pathgrid to navigation mesh
|
||||
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_C_FLAGS='-Werror'
|
||||
-DCMAKE_CXX_FLAGS="${CXX_FLAGS}"
|
||||
-DOPENMW_CXX_FLAGS="-Werror=implicit-fallthrough"
|
||||
)
|
||||
|
||||
if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then
|
||||
|
|
|
@ -13,6 +13,11 @@ if(POLICY CMP0083)
|
|||
cmake_policy(SET CMP0083 NEW)
|
||||
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)
|
||||
if(OPENMW_GL4ES_MANUAL_INIT)
|
||||
add_definitions(-DOPENMW_GL4ES_MANUAL_INIT)
|
||||
|
@ -202,8 +207,6 @@ if (USE_QT)
|
|||
find_package(Qt5Widgets REQUIRED)
|
||||
find_package(Qt5Network REQUIRED)
|
||||
find_package(Qt5OpenGL REQUIRED)
|
||||
# Instruct CMake to run moc automatically when needed.
|
||||
#set(CMAKE_AUTOMOC ON)
|
||||
endif()
|
||||
|
||||
set(USED_OSG_COMPONENTS
|
||||
|
@ -540,6 +543,10 @@ if (BUILD_OPENCS)
|
|||
add_subdirectory (extern/osgQt)
|
||||
endif()
|
||||
|
||||
if (OPENMW_CXX_FLAGS)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPENMW_CXX_FLAGS}")
|
||||
endif()
|
||||
|
||||
# Components
|
||||
add_subdirectory (components)
|
||||
|
||||
|
|
|
@ -124,11 +124,9 @@ public:
|
|||
{
|
||||
mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt.mLevel;
|
||||
mContext->mPlayerBase = npc;
|
||||
ESM::SpellState::SpellParams empty;
|
||||
// FIXME: player start spells and birthsign spells aren't listed here,
|
||||
// need to fix openmw to account for this
|
||||
for (const auto & spell : npc.mSpells.mList)
|
||||
mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[spell] = empty;
|
||||
mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells = npc.mSpells.mList;
|
||||
|
||||
// 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.
|
||||
|
|
|
@ -36,23 +36,6 @@ set(LAUNCHER_HEADER
|
|||
)
|
||||
|
||||
# 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
|
||||
${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui
|
||||
${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui
|
||||
|
@ -74,7 +57,6 @@ if(WIN32)
|
|||
endif(WIN32)
|
||||
|
||||
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})
|
||||
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
@ -109,4 +91,7 @@ if (BUILD_WITH_CODE_COVERAGE)
|
|||
target_link_libraries(openmw-launcher gcov)
|
||||
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.buildIndex();
|
||||
|
||||
std::map<std::string, VFS::File*> files=myManager.getIndex();
|
||||
for(auto it=files.begin(); it!=files.end(); ++it)
|
||||
for(const auto& name : myManager.getRecursiveDirectoryIterator(""))
|
||||
{
|
||||
std::string name = it->first;
|
||||
|
||||
try{
|
||||
if(isNIF(name))
|
||||
{
|
||||
|
|
|
@ -8,11 +8,11 @@ opencs_units (model/doc
|
|||
document operation saving documentmanager loader runner operationholder
|
||||
)
|
||||
|
||||
opencs_units_noqt (model/doc
|
||||
opencs_units (model/doc
|
||||
stage savingstate savingstages blacklist messages
|
||||
)
|
||||
|
||||
opencs_hdrs_noqt (model/doc
|
||||
opencs_hdrs (model/doc
|
||||
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
|
||||
refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope
|
||||
pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection
|
||||
idcompletionmanager metadata defaultgmsts infoselectwrapper commandmacro
|
||||
)
|
||||
|
||||
opencs_hdrs_noqt (model/world
|
||||
opencs_hdrs (model/world
|
||||
columnimp idcollection collection info subcellcollection
|
||||
)
|
||||
|
||||
|
@ -39,14 +39,14 @@ opencs_units (model/tools
|
|||
tools reportmodel mergeoperation
|
||||
)
|
||||
|
||||
opencs_units_noqt (model/tools
|
||||
opencs_units (model/tools
|
||||
mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
|
||||
birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck
|
||||
startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck
|
||||
mergestages gmstcheck topicinfocheck journalcheck enchantmentcheck
|
||||
)
|
||||
|
||||
opencs_hdrs_noqt (model/tools
|
||||
opencs_hdrs (model/tools
|
||||
mergestate
|
||||
)
|
||||
|
||||
|
@ -57,11 +57,11 @@ opencs_units (view/doc
|
|||
)
|
||||
|
||||
|
||||
opencs_units_noqt (view/doc
|
||||
opencs_units (view/doc
|
||||
subviewfactory
|
||||
)
|
||||
|
||||
opencs_hdrs_noqt (view/doc
|
||||
opencs_hdrs (view/doc
|
||||
subviewfactoryimp
|
||||
)
|
||||
|
||||
|
@ -71,10 +71,10 @@ opencs_units (view/world
|
|||
cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview
|
||||
infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable
|
||||
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
|
||||
scripthighlighter idvalidator dialoguecreator idcompletiondelegate
|
||||
colordelegate dragdroputils
|
||||
|
@ -92,12 +92,12 @@ opencs_units (view/render
|
|||
cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands
|
||||
)
|
||||
|
||||
opencs_units_noqt (view/render
|
||||
opencs_units (view/render
|
||||
lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase
|
||||
cellarrow cellmarker cellborder pathgrid
|
||||
)
|
||||
|
||||
opencs_hdrs_noqt (view/render
|
||||
opencs_hdrs (view/render
|
||||
mask
|
||||
)
|
||||
|
||||
|
@ -106,7 +106,7 @@ opencs_units (view/tools
|
|||
reportsubview reporttable searchsubview searchbox merge
|
||||
)
|
||||
|
||||
opencs_units_noqt (view/tools
|
||||
opencs_units (view/tools
|
||||
subviews
|
||||
)
|
||||
|
||||
|
@ -119,11 +119,11 @@ opencs_units (model/prefs
|
|||
shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting
|
||||
)
|
||||
|
||||
opencs_units_noqt (model/prefs
|
||||
opencs_units (model/prefs
|
||||
category
|
||||
)
|
||||
|
||||
opencs_units_noqt (model/filter
|
||||
opencs_units (model/filter
|
||||
node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode
|
||||
)
|
||||
|
||||
|
@ -150,7 +150,6 @@ if(WIN32)
|
|||
endif(WIN32)
|
||||
|
||||
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})
|
||||
|
||||
# for compiled .ui files
|
||||
|
@ -259,3 +258,7 @@ endif (MSVC)
|
|||
if(APPLE)
|
||||
INSTALL(TARGETS openmw-cs BUNDLE DESTINATION "." COMPONENT Bundle)
|
||||
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++;
|
||||
}
|
||||
|
||||
if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell)
|
||||
else if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell)
|
||||
!= stream.str() && !interior)
|
||||
{
|
||||
// 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_Deleted)
|
||||
{
|
||||
writer.startRecord (record.sRecordId);
|
||||
writer.startRecord (record.sRecordId, record.mRecordFlags);
|
||||
record.save (writer, state == CSMWorld::RecordBase::State_Deleted);
|
||||
writer.endRecord (record.sRecordId);
|
||||
}
|
||||
|
|
|
@ -371,6 +371,7 @@ namespace CSMWorld
|
|||
{ ColumnId_Skill7, "Skill 7" },
|
||||
|
||||
{ ColumnId_Persistent, "Persistent" },
|
||||
{ ColumnId_Blocked, "Blocked" },
|
||||
|
||||
{ -1, 0 } // end marker
|
||||
};
|
||||
|
|
|
@ -344,6 +344,7 @@ namespace CSMWorld
|
|||
ColumnId_FactionAttrib2 = 312,
|
||||
|
||||
ColumnId_Persistent = 313,
|
||||
ColumnId_Blocked = 314,
|
||||
|
||||
// Allocated to a separate value range, so we don't get a collision should we ever need
|
||||
// to extend the number of use values.
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QAbstractProxyModel>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include <components/misc/constants.hpp>
|
||||
|
||||
|
@ -142,31 +139,12 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons
|
|||
if (mLocked)
|
||||
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;
|
||||
|
||||
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();
|
||||
|
||||
// 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));
|
||||
|
||||
// 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))
|
||||
if (columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos)
|
||||
{
|
||||
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();
|
||||
|
||||
// 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);
|
||||
|
||||
if (cellColumn != -1)
|
||||
if (cellColumn!=-1)
|
||||
{
|
||||
QModelIndex cellIndex = model2.index (row, cellColumn);
|
||||
|
||||
|
@ -188,46 +169,23 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons
|
|||
|
||||
if (cellId.find ('#')!=std::string::npos)
|
||||
{
|
||||
RefCollection& collection = mDocument.getData().getReferences();
|
||||
newId = collection.getNewId();
|
||||
|
||||
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));
|
||||
// Need to recalculate the cell
|
||||
modifyCell.reset (new UpdateCellCommand (model2, row));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!clonedData.get())
|
||||
{
|
||||
// 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_));
|
||||
}
|
||||
std::unique_ptr<CSMWorld::ModifyCommand> modifyData (
|
||||
new CSMWorld::ModifyCommand (*model, index, new_));
|
||||
|
||||
if (clonedData.get())
|
||||
if (modifyCell.get())
|
||||
{
|
||||
CommandMacro macro (mDocument.getUndoStack());
|
||||
macro.push(clonedData.release());
|
||||
macro.push(deleteData.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());
|
||||
macro.push (modifyData.release());
|
||||
macro.push (modifyCell.release());
|
||||
}
|
||||
else if (!clonedData.get() && modifyData.get())
|
||||
else
|
||||
mDocument.getUndoStack().push (modifyData.release());
|
||||
}
|
||||
|
||||
|
|
|
@ -123,6 +123,14 @@ Qt::ItemFlags CSMWorld::IdTable::flags (const QModelIndex & index) const
|
|||
if (mIdCollection->getColumn (index.column()).isUserEditable())
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -91,12 +91,48 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
|
|||
else
|
||||
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) |
|
||||
(ref.mRefNum.hasContentFile() ? ref.mRefNum.mContentFile : 0xff) << 24;
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace CSMWorld
|
|||
class RefCollection : public Collection<CellRef>
|
||||
{
|
||||
Collection<Cell>& mCells;
|
||||
std::map<unsigned int, int> mRefIndex;
|
||||
std::map<unsigned int, int> mRefIndex; // CellRef index keyed by CSMWorld::CellRef::mIdNum
|
||||
|
||||
int mNextId;
|
||||
|
||||
|
|
|
@ -25,6 +25,9 @@ namespace CSMWorld
|
|||
const RefIdColumn *mId;
|
||||
const RefIdColumn *mModified;
|
||||
const RefIdColumn *mType;
|
||||
const RefIdColumn *mBlocked;
|
||||
|
||||
BaseColumns () : mBlocked(nullptr) {}
|
||||
};
|
||||
|
||||
/// \brief Base adapter for all refereceable record types
|
||||
|
@ -90,6 +93,9 @@ namespace CSMWorld
|
|||
if (column==mBase.mType)
|
||||
return static_cast<int> (mType);
|
||||
|
||||
if (column==mBase.mBlocked)
|
||||
return (record.get().mRecordFlags & ESM::FLAG_Blocked) != 0;
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
|
@ -102,6 +108,17 @@ namespace CSMWorld
|
|||
|
||||
if (column==mBase.mModified)
|
||||
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>
|
||||
|
@ -110,6 +127,14 @@ namespace CSMWorld
|
|||
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
|
||||
{
|
||||
|
@ -1845,6 +1870,7 @@ namespace CSMWorld
|
|||
content.mWander.mDuration = static_cast<short>(value.toInt());
|
||||
else
|
||||
return; // return without saving
|
||||
break;
|
||||
case 3:
|
||||
if (content.mType == ESM::AI_Wander)
|
||||
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,
|
||||
ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false);
|
||||
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);
|
||||
|
||||
mColumns.emplace_back(Columns::ColumnId_Model, ColumnBase::Display_Mesh);
|
||||
modelColumns.mModel = &mColumns.back();
|
||||
mColumns.emplace_back(Columns::ColumnId_Persistent, ColumnBase::Display_Boolean);
|
||||
modelColumns.mPersistence = &mColumns.back();
|
||||
mColumns.emplace_back(Columns::ColumnId_Model, ColumnBase::Display_Mesh);
|
||||
modelColumns.mModel = &mColumns.back();
|
||||
|
||||
NameColumns nameColumns (modelColumns);
|
||||
|
||||
|
|
|
@ -23,10 +23,8 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char * const *
|
|||
|
||||
size_t baseSize = mBaseDirectory.size();
|
||||
|
||||
const std::map<std::string, VFS::File*>& index = vfs->getIndex();
|
||||
for (std::map<std::string, VFS::File*>::const_iterator it = index.begin(); it != index.end(); ++it)
|
||||
for (const auto& filepath : vfs->getRecursiveDirectoryIterator(""))
|
||||
{
|
||||
std::string filepath = it->first;
|
||||
if (filepath.size()<baseSize+1 ||
|
||||
filepath.substr (0, baseSize)!=mBaseDirectory ||
|
||||
(filepath[baseSize]!='/' && filepath[baseSize]!='\\'))
|
||||
|
|
|
@ -161,7 +161,7 @@ void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool)
|
|||
bool isNew = (mAction == ContentAction_New);
|
||||
|
||||
if (isNew)
|
||||
success = success && !(name.isEmpty());
|
||||
success = !name.isEmpty();
|
||||
else if (success)
|
||||
{
|
||||
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");
|
||||
connect (save, SIGNAL (triggered()), this, SLOT (save()));
|
||||
mSave = save;
|
||||
|
||||
file->addSeparator();
|
||||
|
||||
QAction* verify = createMenuEntry("Verify", ":./menu-verify.png", file, "document-file-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");
|
||||
connect (meta, SIGNAL (triggered()), this, SLOT (addMetaDataSubView()));
|
||||
|
||||
file->addSeparator();
|
||||
|
||||
QAction* close = createMenuEntry("Close", ":./menu-close.png", file, "document-file-close");
|
||||
connect (close, SIGNAL (triggered()), this, SLOT (close()));
|
||||
|
||||
|
@ -156,17 +160,16 @@ void CSVDoc::View::setupWorldMenu()
|
|||
{
|
||||
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");
|
||||
connect (referenceables, SIGNAL (triggered()), this, SLOT (addReferenceablesSubView()));
|
||||
|
||||
QAction* references = createMenuEntry(CSMWorld::UniversalId::Type_References, world, "document-world-references");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView()));
|
||||
|
@ -187,14 +193,19 @@ void CSVDoc::View::setupMechanicsMenu()
|
|||
{
|
||||
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");
|
||||
connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView()));
|
||||
|
||||
QAction* gmsts = createMenuEntry(CSMWorld::UniversalId::Type_Gmsts, mechanics, "document-mechanics-gamesettings");
|
||||
connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView()));
|
||||
|
||||
QAction* scripts = createMenuEntry(CSMWorld::UniversalId::Type_Scripts, mechanics, "document-mechanics-scripts");
|
||||
connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView()));
|
||||
|
||||
mechanics->addSeparator();
|
||||
|
||||
QAction* spells = createMenuEntry(CSMWorld::UniversalId::Type_Spells, mechanics, "document-mechanics-spells");
|
||||
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");
|
||||
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()
|
||||
|
@ -227,21 +235,25 @@ void CSVDoc::View::setupCharacterMenu()
|
|||
|
||||
QAction* birthsigns = createMenuEntry(CSMWorld::UniversalId::Type_Birthsigns, characters, "document-character-birthsigns");
|
||||
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");
|
||||
connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView()));
|
||||
|
||||
QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos");
|
||||
connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView()));
|
||||
|
||||
characters->addSeparator();
|
||||
|
||||
QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals");
|
||||
connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView()));
|
||||
|
||||
QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos");
|
||||
connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView()));
|
||||
|
||||
QAction* journalInfos = createMenuEntry(CSMWorld::UniversalId::Type_JournalInfos, characters, "document-character-journalinfos");
|
||||
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()
|
||||
|
|
|
@ -18,6 +18,8 @@ CSVRender::BrushDraw::BrushDraw(osg::ref_ptr<osg::Group> parentNode, bool textur
|
|||
mBrushDrawNode = new osg::Group();
|
||||
mGeometry = new osg::Geometry();
|
||||
mBrushDrawNode->addChild(mGeometry);
|
||||
mBrushDrawNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
|
||||
mBrushDrawNode->getOrCreateStateSet()->setRenderBinDetails(11, "RenderBin");
|
||||
mParentNode->addChild(mBrushDrawNode);
|
||||
if (mTextureMode)
|
||||
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);
|
||||
float diameter = radius * 2;
|
||||
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);
|
||||
|
||||
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::Vec3Array> vertices (new osg::Vec3Array());
|
||||
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 brushOutlineHeight (1.0f);
|
||||
osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f);
|
||||
|
|
|
@ -60,7 +60,7 @@ void CSVRender::CellArrow::adjustTransform()
|
|||
{
|
||||
// position
|
||||
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 y = mCoordinates.getY()*cellSize + cellSize/2;
|
||||
|
@ -92,9 +92,9 @@ void CSVRender::CellArrow::buildShape()
|
|||
{
|
||||
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
|
||||
|
||||
const int arrowWidth = 4000;
|
||||
const int arrowLength = 1500;
|
||||
const int arrowHeight = 500;
|
||||
const int arrowWidth = 2700;
|
||||
const int arrowLength = 1350;
|
||||
const int arrowHeight = 300;
|
||||
|
||||
osg::Vec3Array *vertices = new osg::Vec3Array;
|
||||
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> (
|
||||
CSMWorld::Columns::ColumnId_Cell));
|
||||
int refNumColumn = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (
|
||||
CSMWorld::Columns::ColumnId_RefNum));
|
||||
int origCellColumn = collection.findColumnIndex(static_cast<CSMWorld::Columns::ColumnId> (
|
||||
CSMWorld::Columns::ColumnId_OriginalCell));
|
||||
|
||||
if (cellIndex != originalIndex)
|
||||
{
|
||||
/// \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 ("");
|
||||
|
||||
commands.push (new CSMWorld::ModifyCommand (*model,
|
||||
model->index (recordIndex, cellColumn), QString::fromUtf8 (cellId.c_str())));
|
||||
commands.push (new CSMWorld::ModifyCommand( *model,
|
||||
model->index (recordIndex, refNumColumn), 0));
|
||||
model->index (recordIndex, origCellColumn), QString::fromUtf8 (origCellId.c_str())));
|
||||
commands.push(new CSMWorld::ModifyCommand(*model,
|
||||
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
|
||||
#define CSV_TOOLS_REPORTTABLE_H
|
||||
#ifndef CSV_TOOLS_MERGE_H
|
||||
#define CSV_TOOLS_MERGE_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
|
|
|
@ -538,6 +538,9 @@ void CSVWorld::EditWidget::remake(int row)
|
|||
mainLayout->addLayout(tablesLayout, QSizePolicy::Preferred);
|
||||
mainLayout->addStretch(1);
|
||||
|
||||
int blockedColumn = mTable->searchColumnIndex(CSMWorld::Columns::ColumnId_Blocked);
|
||||
bool isBlocked = mTable->data(mTable->index(row, blockedColumn)).toInt();
|
||||
|
||||
int unlocked = 0;
|
||||
int locked = 0;
|
||||
const int columns = mTable->columnCount();
|
||||
|
@ -583,6 +586,8 @@ void CSVWorld::EditWidget::remake(int row)
|
|||
NestedTable* table =
|
||||
new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows);
|
||||
table->resizeColumnsToContents();
|
||||
if (isBlocked)
|
||||
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
|
||||
int rows = mTable->rowCount(mTable->index(row, i));
|
||||
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);
|
||||
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 (editor, locked, 1);
|
||||
|
@ -639,7 +646,7 @@ void CSVWorld::EditWidget::remake(int row)
|
|||
createEditorContextMenu(editor, display, row);
|
||||
}
|
||||
}
|
||||
else
|
||||
else // Flag_Dialogue_List
|
||||
{
|
||||
CSMWorld::IdTree *tree = static_cast<CSMWorld::IdTree *>(mTable);
|
||||
mNestedTableMapper = new QDataWidgetMapper (this);
|
||||
|
@ -686,7 +693,10 @@ void CSVWorld::EditWidget::remake(int row)
|
|||
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)));
|
||||
|
|
|
@ -78,6 +78,8 @@ QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptio
|
|||
for (std::vector<std::pair<int, QString> >::const_iterator iter (mValues.begin());
|
||||
iter!=mValues.end(); ++iter)
|
||||
comboBox->addItem (iter->second);
|
||||
|
||||
comboBox->setMaxVisibleItems(20);
|
||||
|
||||
return comboBox;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,8 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd
|
|||
std::vector<CSMWorld::UniversalId::Type> types = CSMWorld::UniversalId::listReferenceableTypes();
|
||||
|
||||
mType = new QComboBox (this);
|
||||
|
||||
mType->setMaxVisibleItems(20);
|
||||
|
||||
for (std::vector<CSMWorld::UniversalId::Type>::const_iterator iter (types.begin());
|
||||
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(),
|
||||
static_cast<int> (id2.getType()));
|
||||
}
|
||||
|
||||
|
||||
mType->model()->sort(0);
|
||||
|
||||
insertBeforeButtons (mType, false);
|
||||
|
||||
connect (mType, SIGNAL (currentIndexChanged (int)), this, SLOT (setType (int)));
|
||||
|
|
|
@ -32,7 +32,6 @@ namespace CSVWidget
|
|||
|
||||
namespace CSVWorld
|
||||
{
|
||||
class Table;
|
||||
class TableBottomBox;
|
||||
class CreatorFactoryBase;
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "../../model/prefs/shortcut.hpp"
|
||||
|
||||
#include "tableeditidaction.hpp"
|
||||
#include "tableheadermouseeventhandler.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
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 *)),
|
||||
this, SLOT (settingChanged (const CSMPrefs::Setting *)));
|
||||
CSMPrefs::get()["ID Tables"].update();
|
||||
|
||||
new TableHeaderMouseEventHandler(this);
|
||||
}
|
||||
|
||||
void CSVWorld::Table::setEditLock (bool locked)
|
||||
|
|
64
apps/opencs/view/world/tableheadermouseeventhandler.cpp
Normal file
64
apps/opencs/view/world/tableheadermouseeventhandler.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
#include "tableheadermouseeventhandler.hpp"
|
||||
#include "dragrecordtable.hpp"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QPoint>
|
||||
|
||||
namespace CSVWorld
|
||||
{
|
||||
|
||||
TableHeaderMouseEventHandler::TableHeaderMouseEventHandler(DragRecordTable * parent)
|
||||
: QWidget(parent)
|
||||
, table(*parent)
|
||||
, header(*table.horizontalHeader())
|
||||
{
|
||||
header.setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
|
||||
connect(
|
||||
&header, &QHeaderView::customContextMenuRequested, [=](const QPoint & position) { showContextMenu(position); });
|
||||
|
||||
header.viewport()->installEventFilter(this);
|
||||
}
|
||||
|
||||
bool TableHeaderMouseEventHandler::eventFilter(QObject * tableWatched, QEvent * event)
|
||||
{
|
||||
if (event->type() == QEvent::Type::MouseButtonPress)
|
||||
{
|
||||
auto & clickEvent = static_cast<QMouseEvent &>(*event);
|
||||
if ((clickEvent.button() == Qt::MiddleButton))
|
||||
{
|
||||
const auto & index = table.indexAt(clickEvent.pos());
|
||||
table.setColumnHidden(index.column(), true);
|
||||
clickEvent.accept();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TableHeaderMouseEventHandler::showContextMenu(const QPoint & position)
|
||||
{
|
||||
auto & menu{createContextMenu()};
|
||||
menu.popup(header.viewport()->mapToGlobal(position));
|
||||
}
|
||||
|
||||
QMenu & TableHeaderMouseEventHandler::createContextMenu()
|
||||
{
|
||||
auto * menu = new QMenu(this);
|
||||
for (int i = 0; i < table.model()->columnCount(); ++i)
|
||||
{
|
||||
const auto & name = table.model()->headerData(i, Qt::Horizontal, Qt::DisplayRole);
|
||||
QAction * action{new QAction(name.toString(), this)};
|
||||
action->setCheckable(true);
|
||||
action->setChecked(!table.isColumnHidden(i));
|
||||
menu->addAction(action);
|
||||
|
||||
connect(action, &QAction::triggered, [=]() {
|
||||
table.setColumnHidden(i, !action->isChecked());
|
||||
action->setChecked(!action->isChecked());
|
||||
action->toggle();
|
||||
});
|
||||
}
|
||||
return *menu;
|
||||
}
|
||||
|
||||
} // namespace CSVWorld
|
25
apps/opencs/view/world/tableheadermouseeventhandler.hpp
Normal file
25
apps/opencs/view/world/tableheadermouseeventhandler.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QtGui>
|
||||
|
||||
namespace CSVWorld
|
||||
{
|
||||
class DragRecordTable;
|
||||
|
||||
class TableHeaderMouseEventHandler : public QWidget
|
||||
{
|
||||
public:
|
||||
explicit TableHeaderMouseEventHandler(DragRecordTable * parent);
|
||||
|
||||
void showContextMenu(const QPoint &);
|
||||
|
||||
private:
|
||||
DragRecordTable & table;
|
||||
QHeaderView & header;
|
||||
|
||||
QMenu & createContextMenu();
|
||||
bool eventFilter(QObject *, QEvent *) override;
|
||||
|
||||
}; // class TableHeaderMouseEventHandler
|
||||
} // namespace CSVWorld
|
|
@ -2,6 +2,7 @@
|
|||
set(GAME
|
||||
main.cpp
|
||||
engine.cpp
|
||||
options.cpp
|
||||
|
||||
${CMAKE_SOURCE_DIR}/files/windows/openmw.rc
|
||||
${CMAKE_SOURCE_DIR}/files/windows/openmw.exe.manifest
|
||||
|
@ -58,7 +59,7 @@ add_openmw_dir (mwscript
|
|||
add_openmw_dir (mwlua
|
||||
luamanagerimp actions object worldview userdataserializer eventqueue query
|
||||
luabindings localscripts objectbindings cellbindings asyncbindings settingsbindings
|
||||
camerabindings uibindings inputbindings
|
||||
camerabindings uibindings inputbindings nearbybindings
|
||||
)
|
||||
|
||||
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
|
||||
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance
|
||||
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
|
||||
character actors objects aistate trading weaponpriority spellpriority weapontype spellutil tickableeffects
|
||||
spellabsorption linkedeffects
|
||||
character actors objects aistate trading weaponpriority spellpriority weapontype spellutil
|
||||
spellabsorption spelleffects
|
||||
)
|
||||
|
||||
add_openmw_dir (mwstate
|
||||
|
|
|
@ -875,7 +875,14 @@ public:
|
|||
void join()
|
||||
{
|
||||
if (mThread)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mMutex);
|
||||
mJoinRequest = true;
|
||||
}
|
||||
mCV.notify_one();
|
||||
mThread->join();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -891,10 +898,12 @@ private:
|
|||
|
||||
void threadBody()
|
||||
{
|
||||
while (!mEngine->mViewer->done() && !mEngine->mEnvironment.getStateManager()->hasQuitRequest())
|
||||
while (true)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mMutex);
|
||||
mCV.wait(lk, [&]{ return mUpdateRequest; });
|
||||
mCV.wait(lk, [&]{ return mUpdateRequest || mJoinRequest; });
|
||||
if (mJoinRequest)
|
||||
break;
|
||||
|
||||
update();
|
||||
|
||||
|
@ -908,6 +917,7 @@ private:
|
|||
std::mutex mMutex;
|
||||
std::condition_variable mCV;
|
||||
bool mUpdateRequest = false;
|
||||
bool mJoinRequest = false;
|
||||
double mDt = 0;
|
||||
bool mIsGuiMode = false;
|
||||
std::optional<std::thread> mThread;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <components/misc/rng.hpp>
|
||||
|
||||
#include "engine.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#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;
|
||||
typedef std::vector<std::string> StringsVector;
|
||||
|
||||
bpo::options_description desc("Syntax: openmw <options>\nAllowed options");
|
||||
|
||||
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::options_description desc = OpenMW::makeOptionsDescription();
|
||||
|
||||
bpo::variables_map variables;
|
||||
|
||||
// Runtime options override settings from all configs
|
||||
bpo::store(valid_opts, variables);
|
||||
Files::parseArgs(argc, argv, variables, desc);
|
||||
bpo::notify(variables);
|
||||
|
||||
if (variables.count ("help"))
|
||||
|
@ -158,9 +62,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
|||
return false;
|
||||
}
|
||||
|
||||
bpo::variables_map composingVariables = cfgMgr.separateComposingVariables(variables, desc);
|
||||
bpo::variables_map composingVariables = Files::separateComposingVariables(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());
|
||||
Log(Debug::Info) << v.describe();
|
||||
|
|
|
@ -58,7 +58,7 @@ namespace MWBase
|
|||
virtual void add (const MWWorld::Ptr& ptr) = 0;
|
||||
///< 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
|
||||
|
||||
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 MWMechanics::GreetingState getGreetingState(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;
|
||||
///< @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
|
||||
|
||||
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 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,
|
||||
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 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,
|
||||
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;
|
||||
|
||||
|
@ -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 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;
|
||||
|
||||
|
|
|
@ -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())
|
||||
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
|
||||
|
|
|
@ -17,9 +17,9 @@ namespace MWClass
|
|||
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
|
||||
///< 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;
|
||||
///< \return name or ID; can return an empty string.
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace MWClass
|
|||
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())
|
||||
{
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace MWClass
|
|||
///< Adjust position to stand on ground. Must be called post model load
|
||||
/// @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;
|
||||
|
||||
|
|
|
@ -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())
|
||||
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
|
||||
|
|
|
@ -42,8 +42,8 @@ namespace MWClass
|
|||
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
|
||||
///< 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 insertObjectPhysics(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) const override;
|
||||
|
||||
std::string getName (const MWWorld::ConstPtr& ptr) const override;
|
||||
///< \return name or ID; can return an empty string.
|
||||
|
|
|
@ -846,6 +846,7 @@ namespace MWClass
|
|||
|
||||
// Reset to original position
|
||||
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
|
||||
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())
|
||||
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door, skipAnimated);
|
||||
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door);
|
||||
}
|
||||
|
||||
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;
|
||||
///< 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 insertObjectPhysics(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) 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));
|
||||
}
|
||||
|
||||
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 =
|
||||
ptr.get<ESM::Light>();
|
||||
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))
|
||||
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0,
|
||||
|
@ -47,11 +47,11 @@ namespace MWClass
|
|||
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
|
||||
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
|
||||
|
|
|
@ -14,8 +14,8 @@ namespace MWClass
|
|||
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
|
||||
///< 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 insertObjectPhysics(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) const override;
|
||||
|
||||
bool useAnim() const override;
|
||||
|
||||
|
|
|
@ -1397,6 +1397,7 @@ namespace MWClass
|
|||
|
||||
// Reset to original position
|
||||
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())
|
||||
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
|
||||
|
|
|
@ -14,8 +14,8 @@ namespace MWClass
|
|||
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
|
||||
///< 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 insertObjectPhysics(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) const override;
|
||||
|
||||
std::string getName (const MWWorld::ConstPtr& ptr) const override;
|
||||
///< \return name or ID; can return an empty string.
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#ifndef GAME_MWDIALOGUE_KEYWORDSEARCH_H
|
||||
#define GAME_MWDIALOGUE_KEYWORDSEARCH_H
|
||||
|
||||
#include <map>
|
||||
#include <cctype>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <algorithm> // std::reverse
|
||||
|
@ -68,6 +68,7 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool sortMatches(const Match& left, const Match& right)
|
||||
{
|
||||
return left.mBeg < right.mBeg;
|
||||
|
@ -78,16 +79,6 @@ public:
|
|||
std::vector<Match> matches;
|
||||
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
|
||||
typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i));
|
||||
|
||||
|
|
|
@ -72,13 +72,6 @@ namespace
|
|||
return {question, {r2, r1, r0}, sound};
|
||||
}
|
||||
}
|
||||
|
||||
void updatePlayerHealth()
|
||||
{
|
||||
MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||
MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player);
|
||||
npcStats.updateHealth();
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
|
@ -372,8 +365,6 @@ namespace MWGui
|
|||
MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog);
|
||||
mPickClassDialog = nullptr;
|
||||
}
|
||||
|
||||
updatePlayerHealth();
|
||||
}
|
||||
|
||||
void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow)
|
||||
|
@ -448,8 +439,6 @@ namespace MWGui
|
|||
MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog);
|
||||
mRaceDialog = nullptr;
|
||||
}
|
||||
|
||||
updatePlayerHealth();
|
||||
}
|
||||
|
||||
void CharacterCreation::onRaceDialogBack()
|
||||
|
@ -477,8 +466,6 @@ namespace MWGui
|
|||
MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog);
|
||||
mBirthSignDialog = nullptr;
|
||||
}
|
||||
|
||||
updatePlayerHealth();
|
||||
}
|
||||
|
||||
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
|
||||
mCreateClassDialog->setVisible(false);
|
||||
}
|
||||
updatePlayerHealth();
|
||||
}
|
||||
|
||||
void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow)
|
||||
|
@ -719,8 +705,6 @@ namespace MWGui
|
|||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(mGenerateClass);
|
||||
|
||||
mPlayerClass = *klass;
|
||||
|
||||
updatePlayerHealth();
|
||||
}
|
||||
|
||||
void CharacterCreation::onGenerateClassBack()
|
||||
|
|
|
@ -262,7 +262,7 @@ namespace MWGui
|
|||
}
|
||||
|
||||
// Clean up summoned creatures as well
|
||||
std::map<ESM::SummonKey, int>& creatureMap = creatureStats.getSummonedCreatureMap();
|
||||
auto& creatureMap = creatureStats.getSummonedCreatureMap();
|
||||
for (const auto& creature : creatureMap)
|
||||
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(ptr, creature.second);
|
||||
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(); });
|
||||
if(it != summons.end())
|
||||
{
|
||||
MWMechanics::purgeSummonEffect(summoner, *it);
|
||||
auto summon = *it;
|
||||
summons.erase(it);
|
||||
MWMechanics::purgeSummonEffect(summoner, summon);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,10 +82,7 @@ namespace MWGui
|
|||
MWBase::Environment::get().getWorld()->advanceTime(mDays * 24);
|
||||
|
||||
// We should not worsen corprus when in prison
|
||||
for (auto& spell : player.getClass().getCreatureStats(player).getCorprusSpells())
|
||||
{
|
||||
spell.second.mNextWorsening += mDays * 24;
|
||||
}
|
||||
player.getClass().getCreatureStats(player).getActiveSpells().skipWorsenings(mDays * 24);
|
||||
|
||||
std::set<int> skills;
|
||||
for (int day=0; day<mDays; ++day)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <MyGUI_Gui.h>
|
||||
#include <MyGUI_TextBox.h>
|
||||
|
||||
#include <components/misc/pathhelpers.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/myguiplatform/myguitexture.hpp>
|
||||
|
@ -66,35 +67,15 @@ namespace MWGui
|
|||
|
||||
void LoadingScreen::findSplashScreens()
|
||||
{
|
||||
const std::map<std::string, VFS::File*>& index = mResourceSystem->getVFS()->getIndex();
|
||||
std::string pattern = "Splash/";
|
||||
mResourceSystem->getVFS()->normalizeFilename(pattern);
|
||||
auto isSupportedExtension = [](const std::string_view& ext) {
|
||||
static const std::array<std::string, 7> supported_extensions{ {"tga", "dds", "ktx", "png", "bmp", "jpeg", "jpg"} };
|
||||
return !ext.empty() && std::find(supported_extensions.begin(), supported_extensions.end(), ext) != supported_extensions.end();
|
||||
};
|
||||
|
||||
/* priority given to the left */
|
||||
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())
|
||||
for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator("Splash/"))
|
||||
{
|
||||
const std::string& name = found->first;
|
||||
if (name.size() >= pattern.size() && name.substr(0, pattern.size()) == pattern)
|
||||
{
|
||||
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 (isSupportedExtension(Misc::getFileExtension(name)))
|
||||
mSplashScreens.push_back(name);
|
||||
}
|
||||
if (mSplashScreens.empty())
|
||||
Log(Debug::Warning) << "Warning: no splash screens found!";
|
||||
|
|
|
@ -99,10 +99,8 @@ namespace MWGui
|
|||
|
||||
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)
|
||||
continue; // don't try to sell diseases, curses or powers
|
||||
|
||||
|
@ -115,10 +113,10 @@ namespace MWGui
|
|||
continue;
|
||||
}
|
||||
|
||||
if (playerHasSpell(iter->first->mId))
|
||||
if (playerHasSpell(spell->mId))
|
||||
continue;
|
||||
|
||||
spellsToSort.push_back(iter->first);
|
||||
spellsToSort.push_back(spell);
|
||||
}
|
||||
|
||||
std::stable_sort(spellsToSort.begin(), spellsToSort.end(), sortSpells);
|
||||
|
|
|
@ -520,10 +520,8 @@ namespace MWGui
|
|||
|
||||
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
|
||||
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
||||
continue;
|
||||
|
|
|
@ -24,50 +24,33 @@
|
|||
|
||||
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)
|
||||
{
|
||||
// TODO: Tracking add/remove/expire would be better than force updating every frame
|
||||
|
||||
MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||
const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
|
||||
|
||||
|
||||
EffectSourceVisitor visitor;
|
||||
|
||||
// permanent item enchantments & permanent spells
|
||||
visitor.mIsPermanent = true;
|
||||
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
|
||||
store.visitEffectSources(visitor);
|
||||
stats.getSpells().visitEffectSources(visitor);
|
||||
|
||||
// now add lasting effects
|
||||
visitor.mIsPermanent = false;
|
||||
stats.getActiveSpells().visitEffectSources(visitor);
|
||||
|
||||
std::map <int, std::vector<MagicEffectInfo> >& effects = visitor.mEffectSources;
|
||||
std::map<int, std::vector<MagicEffectInfo>> effects;
|
||||
for(const auto& params : stats.getActiveSpells())
|
||||
{
|
||||
for(const auto& effect : params.getEffects())
|
||||
{
|
||||
if(!(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
|
||||
continue;
|
||||
MagicEffectInfo newEffectSource;
|
||||
newEffectSource.mKey = MWMechanics::EffectKey(effect.mEffectId, effect.mArg);
|
||||
newEffectSource.mMagnitude = static_cast<int>(effect.mMagnitude);
|
||||
newEffectSource.mPermanent = effect.mDuration == -1.f;
|
||||
newEffectSource.mRemainingTime = effect.mTimeLeft;
|
||||
newEffectSource.mSource = params.getDisplayName();
|
||||
newEffectSource.mTotalTime = effect.mDuration;
|
||||
effects[effect.mEffectId].push_back(newEffectSource);
|
||||
}
|
||||
}
|
||||
|
||||
int w=2;
|
||||
|
||||
for (auto& effectInfoPair : effects)
|
||||
for (const auto& [effectId, effectInfos] : effects)
|
||||
{
|
||||
const int effectId = effectInfoPair.first;
|
||||
const ESM::MagicEffect* effect =
|
||||
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();
|
||||
|
||||
std::vector<MagicEffectInfo>& effectInfos = effectInfoPair.second;
|
||||
bool addNewLine = false;
|
||||
for (const MagicEffectInfo& effectInfo : effectInfos)
|
||||
{
|
||||
|
|
|
@ -37,20 +37,6 @@ namespace MWGui
|
|||
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
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -92,9 +92,8 @@ namespace MWGui
|
|||
|
||||
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)
|
||||
continue;
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ namespace MWGui
|
|||
|
||||
// Add price for the travelling 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
|
||||
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
|
||||
// game mode does not move the position of the GUI cursor
|
||||
float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor();
|
||||
float xMove = xAxis * dt * 1500.0f / uiScale;
|
||||
float yMove = yAxis * dt * 1500.0f / uiScale;
|
||||
float xMove = xAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed;
|
||||
float yMove = yAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed;
|
||||
|
||||
float mouseWheelMove = -zAxis * dt * 1500.0f;
|
||||
if (xMove != 0 || yMove != 0 || mouseWheelMove != 0)
|
||||
|
|
|
@ -51,8 +51,8 @@ namespace MWLua
|
|||
std::array<bool, MWWorld::InventoryStore::Slots> usedSlots;
|
||||
std::fill(usedSlots.begin(), usedSlots.end(), false);
|
||||
|
||||
constexpr int anySlot = -1;
|
||||
auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView, anySlot](int slot, const Item& item) -> bool
|
||||
static constexpr int anySlot = -1;
|
||||
auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView](int slot, const Item& item) -> bool
|
||||
{
|
||||
auto old_it = slot != anySlot ? store.getSlot(slot) : store.end();
|
||||
MWWorld::Ptr itemPtr;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "luabindings.hpp"
|
||||
|
||||
#include "luamanagerimp.hpp"
|
||||
|
||||
namespace sol
|
||||
{
|
||||
template <>
|
||||
|
@ -48,11 +50,16 @@ namespace MWLua
|
|||
asyncId.mContainer->setupUnsavableTimer(
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace MWLua
|
|||
{
|
||||
sol::table api(context.mLua->sol(), sol::create);
|
||||
// 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["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,
|
||||
"Screenshot", MWInput::A_Screenshot,
|
||||
"Inventory", MWInput::A_Inventory,
|
||||
|
@ -102,7 +102,7 @@ namespace MWLua
|
|||
"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",
|
||||
"Fighting", "playerfighting",
|
||||
"Jumping", "playerjumping",
|
||||
|
@ -112,7 +112,7 @@ namespace MWLua
|
|||
"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,
|
||||
"B", SDL_CONTROLLER_BUTTON_B,
|
||||
"X", SDL_CONTROLLER_BUTTON_X,
|
||||
|
@ -130,7 +130,7 @@ namespace MWLua
|
|||
"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,
|
||||
"LeftY", SDL_CONTROLLER_AXIS_LEFTY,
|
||||
"RightX", SDL_CONTROLLER_AXIS_RIGHTX,
|
||||
|
@ -144,7 +144,7 @@ namespace MWLua
|
|||
"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);
|
||||
for (const std::string& v : values)
|
||||
res[v] = v;
|
||||
return lua.makeReadOnly(res);
|
||||
return LuaUtil::makeReadOnly(res);
|
||||
}
|
||||
|
||||
sol::table initCorePackage(const Context& context)
|
||||
{
|
||||
auto* lua = context.mLua;
|
||||
sol::table api(lua->sol(), sol::create);
|
||||
api["API_REVISION"] = 5;
|
||||
api["API_REVISION"] = 7;
|
||||
api["quit"] = [lua]()
|
||||
{
|
||||
std::string traceback = lua->sol()["debug"]["traceback"]().get<std::string>();
|
||||
|
@ -43,7 +43,7 @@ namespace MWLua
|
|||
"Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient",
|
||||
"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,
|
||||
"Cuirass", MWWorld::InventoryStore::Slot_Cuirass,
|
||||
"Greaves", MWWorld::InventoryStore::Slot_Greaves,
|
||||
|
@ -64,7 +64,7 @@ namespace MWLua
|
|||
"CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft,
|
||||
"Ammunition", MWWorld::InventoryStore::Slot_Ammunition
|
||||
));
|
||||
return lua->makeReadOnly(api);
|
||||
return LuaUtil::makeReadOnly(api);
|
||||
}
|
||||
|
||||
sol::table initWorldPackage(const Context& context)
|
||||
|
@ -107,37 +107,7 @@ namespace MWLua
|
|||
// return GObjectList{worldView->selectObjects(query, false)};
|
||||
};
|
||||
// TODO: add world.placeNewObject(recordId, cell, pos, [rot])
|
||||
return context.mLua->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);
|
||||
return LuaUtil::makeReadOnly(api);
|
||||
}
|
||||
|
||||
sol::table initQueryPackage(const Context& context)
|
||||
|
@ -148,7 +118,7 @@ namespace MWLua
|
|||
query[t] = Queries::Query(std::string(t));
|
||||
for (const QueryFieldGroup& group : getBasicQueryFieldGroups())
|
||||
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)
|
||||
|
@ -163,12 +133,12 @@ namespace MWLua
|
|||
{
|
||||
const std::string& name = field->path()[i];
|
||||
if (subgroup[name] == sol::nil)
|
||||
subgroup[name] = context.mLua->makeReadOnly(context.mLua->newTable());
|
||||
subgroup = context.mLua->getMutableFromReadOnly(subgroup[name]);
|
||||
subgroup[name] = LuaUtil::makeReadOnly(context.mLua->newTable());
|
||||
subgroup = LuaUtil::getMutableFromReadOnly(subgroup[name]);
|
||||
}
|
||||
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 initWorldPackage(const Context&);
|
||||
sol::table initNearbyPackage(const Context&);
|
||||
sol::table initQueryPackage(const Context&);
|
||||
|
||||
sol::table initFieldGroup(const Context&, const QueryFieldGroup&);
|
||||
|
||||
// Implemented in nearbybindings.cpp
|
||||
sol::table initNearbyPackage(const Context&);
|
||||
|
||||
// Implemented in objectbindings.cpp
|
||||
void initObjectBindingsForLocalScripts(const Context&);
|
||||
void initObjectBindingsForGlobalScripts(const Context&);
|
||||
|
@ -45,9 +47,9 @@ namespace MWLua
|
|||
// Implemented in asyncbindings.cpp
|
||||
struct AsyncPackageId
|
||||
{
|
||||
// TODO: add ObjectId mLocalObject;
|
||||
LuaUtil::ScriptsContainer* mContainer;
|
||||
std::string mScript;
|
||||
sol::table mHiddenData;
|
||||
};
|
||||
sol::function getAsyncPackageInitializer(const Context&);
|
||||
|
||||
|
|
|
@ -74,6 +74,16 @@ namespace MWLua
|
|||
mInitialized = true;
|
||||
}
|
||||
|
||||
void Callback::operator()(sol::object arg) const
|
||||
{
|
||||
if (mHiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY] != sol::nil)
|
||||
LuaUtil::call(mFunc, std::move(arg));
|
||||
else
|
||||
{
|
||||
Log(Debug::Debug) << "Ignored callback to removed script " << mHiddenData.get<std::string>(SCRIPT_NAME_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
void LuaManager::update(bool paused, float dt)
|
||||
{
|
||||
ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry();
|
||||
|
@ -126,6 +136,11 @@ namespace MWLua
|
|||
<< ". 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
|
||||
PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts());
|
||||
if (playerScripts)
|
||||
|
|
|
@ -19,6 +19,19 @@
|
|||
namespace MWLua
|
||||
{
|
||||
|
||||
// Wrapper for a single-argument Lua function.
|
||||
// Holds information about the script the function belongs to.
|
||||
// Needed to prevent callback calls if the script was removed.
|
||||
struct Callback
|
||||
{
|
||||
static constexpr std::string_view SCRIPT_NAME_KEY = "name";
|
||||
|
||||
sol::function mFunc;
|
||||
sol::table mHiddenData;
|
||||
|
||||
void operator()(sol::object arg) const;
|
||||
};
|
||||
|
||||
class LuaManager : public MWBase::LuaManager
|
||||
{
|
||||
public:
|
||||
|
@ -67,6 +80,18 @@ namespace MWLua
|
|||
// Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script.
|
||||
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:
|
||||
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr);
|
||||
|
||||
|
@ -100,6 +125,13 @@ namespace MWLua
|
|||
std::vector<MWBase::LuaManager::InputEvent> mInputEvents;
|
||||
std::vector<ObjectId> mActorAddedEvents;
|
||||
|
||||
struct CallbackWithData
|
||||
{
|
||||
Callback mCallback;
|
||||
sol::object mArg;
|
||||
};
|
||||
std::vector<CallbackWithData> mQueuedCallbacks;
|
||||
|
||||
struct LocalEngineEvent
|
||||
{
|
||||
ObjectId mDest;
|
||||
|
|
120
apps/openmw/mwlua/nearbybindings.cpp
Normal file
120
apps/openmw/mwlua/nearbybindings.cpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
#include "luabindings.hpp"
|
||||
|
||||
#include <components/lua/luastate.hpp>
|
||||
#include <components/queries/luabindings.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwphysics/raycasting.hpp"
|
||||
|
||||
#include "worldview.hpp"
|
||||
|
||||
namespace sol
|
||||
{
|
||||
template <>
|
||||
struct is_automagical<MWPhysics::RayCastingResult> : std::false_type {};
|
||||
}
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
sol::table initNearbyPackage(const Context& context)
|
||||
{
|
||||
sol::table api(context.mLua->sol(), sol::create);
|
||||
WorldView* worldView = context.mWorldView;
|
||||
|
||||
sol::usertype<MWPhysics::RayCastingResult> rayResult =
|
||||
context.mLua->sol().new_usertype<MWPhysics::RayCastingResult>("RayCastingResult");
|
||||
rayResult["hit"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) { return r.mHit; });
|
||||
rayResult["hitPos"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional<osg::Vec3f>
|
||||
{
|
||||
if (r.mHit)
|
||||
return r.mHitPos;
|
||||
else
|
||||
return sol::nullopt;
|
||||
});
|
||||
rayResult["hitNormal"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional<osg::Vec3f>
|
||||
{
|
||||
if (r.mHit)
|
||||
return r.mHitNormal;
|
||||
else
|
||||
return sol::nullopt;
|
||||
});
|
||||
rayResult["hitObject"] = sol::readonly_property([worldView](const MWPhysics::RayCastingResult& r) -> sol::optional<LObject>
|
||||
{
|
||||
if (r.mHitObject.isEmpty())
|
||||
return sol::nullopt;
|
||||
else
|
||||
return LObject(getId(r.mHitObject), worldView->getObjectRegistry());
|
||||
});
|
||||
|
||||
api["COLLISION_TYPE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with(
|
||||
"World", MWPhysics::CollisionType_World,
|
||||
"Door", MWPhysics::CollisionType_Door,
|
||||
"Actor", MWPhysics::CollisionType_Actor,
|
||||
"HeightMap", MWPhysics::CollisionType_HeightMap,
|
||||
"Projectile", MWPhysics::CollisionType_Projectile,
|
||||
"Water", MWPhysics::CollisionType_Water,
|
||||
"Default", MWPhysics::CollisionType_Default));
|
||||
|
||||
api["castRay"] = [](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional<sol::table> options)
|
||||
{
|
||||
MWWorld::Ptr ignore;
|
||||
int collisionType = MWPhysics::CollisionType_Default;
|
||||
float radius = 0;
|
||||
if (options)
|
||||
{
|
||||
sol::optional<LObject> ignoreObj = options->get<sol::optional<LObject>>("ignore");
|
||||
if (ignoreObj) ignore = ignoreObj->ptr();
|
||||
collisionType = options->get<sol::optional<int>>("collisionType").value_or(collisionType);
|
||||
radius = options->get<sol::optional<float>>("radius").value_or(0);
|
||||
}
|
||||
const MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting();
|
||||
if (radius <= 0)
|
||||
return rayCasting->castRay(from, to, ignore, std::vector<MWWorld::Ptr>(), collisionType);
|
||||
else
|
||||
{
|
||||
if (!ignore.isEmpty()) throw std::logic_error("Currently castRay doesn't support `ignore` when radius > 0");
|
||||
return rayCasting->castSphere(from, to, radius, collisionType);
|
||||
}
|
||||
};
|
||||
// TODO: async raycasting
|
||||
/*api["asyncCastRay"] = [luaManager = context.mLuaManager](
|
||||
const Callback& luaCallback, const osg::Vec3f& from, const osg::Vec3f& to, sol::optional<sol::table> options)
|
||||
{
|
||||
std::function<void(MWPhysics::RayCastingResult)> callback =
|
||||
luaManager->wrapLuaCallback<MWPhysics::RayCastingResult>(luaCallback);
|
||||
MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting();
|
||||
|
||||
// Handle options the same way as in `castRay`.
|
||||
|
||||
// NOTE: `callback` is not thread safe. If MWPhysics works in separate thread, it must put results to a queue
|
||||
// and use this callback from the main thread at the beginning of the next frame processing.
|
||||
rayCasting->asyncCastRay(callback, from, to, ignore, std::vector<MWWorld::Ptr>(), collisionType);
|
||||
};*/
|
||||
|
||||
api["activators"] = LObjectList{worldView->getActivatorsInScene()};
|
||||
api["actors"] = LObjectList{worldView->getActorsInScene()};
|
||||
api["containers"] = LObjectList{worldView->getContainersInScene()};
|
||||
api["doors"] = LObjectList{worldView->getDoorsInScene()};
|
||||
api["items"] = LObjectList{worldView->getItemsInScene()};
|
||||
api["selectObjects"] = [context](const Queries::Query& query)
|
||||
{
|
||||
ObjectIdList list;
|
||||
WorldView* worldView = context.mWorldView;
|
||||
if (query.mQueryType == "activators")
|
||||
list = worldView->getActivatorsInScene();
|
||||
else if (query.mQueryType == "actors")
|
||||
list = worldView->getActorsInScene();
|
||||
else if (query.mQueryType == "containers")
|
||||
list = worldView->getContainersInScene();
|
||||
else if (query.mQueryType == "doors")
|
||||
list = worldView->getDoorsInScene();
|
||||
else if (query.mQueryType == "items")
|
||||
list = worldView->getItemsInScene();
|
||||
return LObjectList{selectObjectsFromList(query, list, context)};
|
||||
// TODO: Maybe use sqlite
|
||||
// return LObjectList{worldView->selectObjects(query, true)};
|
||||
};
|
||||
return LuaUtil::makeReadOnly(api);
|
||||
}
|
||||
}
|
|
@ -62,7 +62,7 @@ namespace MWLua
|
|||
else
|
||||
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); }
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace MWLua
|
|||
{
|
||||
luaManager->addUIMessage(message);
|
||||
};
|
||||
return context.mLua->makeReadOnly(api);
|
||||
return LuaUtil::makeReadOnly(api);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,97 +5,279 @@
|
|||
|
||||
#include <components/esm/loadmgef.hpp>
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "spelleffects.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool merge(std::vector<ESM::ActiveEffect>& present, const std::vector<ESM::ActiveEffect>& queued)
|
||||
{
|
||||
// Can't merge if we already have an effect with the same effect index
|
||||
auto problem = std::find_if(queued.begin(), queued.end(), [&] (const auto& qEffect)
|
||||
{
|
||||
return std::find_if(present.begin(), present.end(), [&] (const auto& pEffect) { return pEffect.mEffectIndex == qEffect.mEffectIndex; }) != present.end();
|
||||
});
|
||||
if(problem != queued.end())
|
||||
return false;
|
||||
present.insert(present.end(), queued.begin(), queued.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
void addEffects(std::vector<ESM::ActiveEffect>& effects, const ESM::EffectList& list, bool ignoreResistances = false)
|
||||
{
|
||||
int currentEffectIndex = 0;
|
||||
for(const auto& enam : list.mList)
|
||||
{
|
||||
ESM::ActiveEffect effect;
|
||||
effect.mEffectId = enam.mEffectID;
|
||||
effect.mArg = MWMechanics::EffectKey(enam).mArg;
|
||||
effect.mMagnitude = 0.f;
|
||||
effect.mMinMagnitude = enam.mMagnMin;
|
||||
effect.mMaxMagnitude = enam.mMagnMax;
|
||||
effect.mEffectIndex = currentEffectIndex++;
|
||||
effect.mFlags = ESM::ActiveEffect::Flag_None;
|
||||
if(ignoreResistances)
|
||||
effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances;
|
||||
effect.mDuration = -1;
|
||||
effect.mTimeLeft = -1;
|
||||
effects.emplace_back(effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
void ActiveSpells::update(float duration) const
|
||||
ActiveSpells::IterationGuard::IterationGuard(ActiveSpells& spells) : mActiveSpells(spells)
|
||||
{
|
||||
bool rebuild = false;
|
||||
mActiveSpells.mIterating = true;
|
||||
}
|
||||
|
||||
// Erase no longer active spells and effects
|
||||
if (duration > 0)
|
||||
ActiveSpells::IterationGuard::~IterationGuard()
|
||||
{
|
||||
mActiveSpells.mIterating = false;
|
||||
}
|
||||
|
||||
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster)
|
||||
: mId(cast.mId), mDisplayName(cast.mSourceName), mCasterActorId(-1), mSlot(cast.mSlot), mType(cast.mType), mWorsenings(-1)
|
||||
{
|
||||
if(!caster.isEmpty() && caster.getClass().isActor())
|
||||
mCasterActorId = caster.getClass().getCreatureStats(caster).getActorId();
|
||||
}
|
||||
|
||||
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances)
|
||||
: mId(spell->mId), mDisplayName(spell->mName), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()), mSlot(0)
|
||||
, mType(spell->mData.mType == ESM::Spell::ST_Ability ? ESM::ActiveSpells::Type_Ability : ESM::ActiveSpells::Type_Permanent), mWorsenings(-1)
|
||||
{
|
||||
assert(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power);
|
||||
addEffects(mEffects, spell->mEffects, ignoreResistances);
|
||||
}
|
||||
|
||||
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor)
|
||||
: mId(item.getCellRef().getRefId()), mDisplayName(item.getClass().getName(item)), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
|
||||
, mSlot(slotIndex), mType(ESM::ActiveSpells::Type_Enchantment), mWorsenings(-1)
|
||||
{
|
||||
assert(enchantment->mData.mType == ESM::Enchantment::ConstantEffect);
|
||||
addEffects(mEffects, enchantment->mEffects);
|
||||
}
|
||||
|
||||
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params)
|
||||
: mId(params.mId), mEffects(params.mEffects), mDisplayName(params.mDisplayName), mCasterActorId(params.mCasterActorId)
|
||||
, mSlot(params.mItem.isSet() ? params.mItem.mIndex : 0)
|
||||
, mType(params.mType), mWorsenings(params.mWorsenings), mNextWorsening({params.mNextWorsening})
|
||||
{}
|
||||
|
||||
ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const
|
||||
{
|
||||
ESM::ActiveSpells::ActiveSpellParams params;
|
||||
params.mId = mId;
|
||||
params.mEffects = mEffects;
|
||||
params.mDisplayName = mDisplayName;
|
||||
params.mCasterActorId = mCasterActorId;
|
||||
params.mItem.unset();
|
||||
if(mSlot)
|
||||
{
|
||||
TContainer::iterator iter (mSpells.begin());
|
||||
while (iter!=mSpells.end())
|
||||
// Note that we're storing the inventory slot as a RefNum instead of an int as a matter of future proofing
|
||||
// mSlot needs to be replaced with a RefNum once inventory items get persistent RefNum (#4508 #6148)
|
||||
params.mItem = { static_cast<unsigned int>(mSlot), 0 };
|
||||
}
|
||||
params.mType = mType;
|
||||
params.mWorsenings = mWorsenings;
|
||||
params.mNextWorsening = mNextWorsening.toEsm();
|
||||
return params;
|
||||
}
|
||||
|
||||
void ActiveSpells::ActiveSpellParams::worsen()
|
||||
{
|
||||
++mWorsenings;
|
||||
if(!mWorsenings)
|
||||
mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp();
|
||||
mNextWorsening += CorprusStats::sWorseningPeriod;
|
||||
}
|
||||
|
||||
bool ActiveSpells::ActiveSpellParams::shouldWorsen() const
|
||||
{
|
||||
return mWorsenings >= 0 && MWBase::Environment::get().getWorld()->getTimeStamp() >= mNextWorsening;
|
||||
}
|
||||
|
||||
void ActiveSpells::ActiveSpellParams::resetWorsenings()
|
||||
{
|
||||
mWorsenings = -1;
|
||||
}
|
||||
|
||||
void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration)
|
||||
{
|
||||
const auto& creatureStats = ptr.getClass().getCreatureStats(ptr);
|
||||
assert(&creatureStats.getActiveSpells() == this);
|
||||
IterationGuard guard{*this};
|
||||
// Erase no longer active spells and effects
|
||||
for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
|
||||
{
|
||||
if(spellIt->mType != ESM::ActiveSpells::Type_Temporary && spellIt->mType != ESM::ActiveSpells::Type_Consumable)
|
||||
{
|
||||
if (!timeToExpire (iter))
|
||||
++spellIt;
|
||||
continue;
|
||||
}
|
||||
bool removedSpell = false;
|
||||
for(auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();)
|
||||
{
|
||||
if(effectIt->mFlags & ESM::ActiveEffect::Flag_Remove && effectIt->mTimeLeft <= 0.f)
|
||||
{
|
||||
mSpells.erase (iter++);
|
||||
rebuild = true;
|
||||
auto effect = *effectIt;
|
||||
effectIt = spellIt->mEffects.erase(effectIt);
|
||||
onMagicEffectRemoved(ptr, *spellIt, effect);
|
||||
removedSpell = applyPurges(ptr, &spellIt, &effectIt);
|
||||
if(removedSpell)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool interrupt = false;
|
||||
std::vector<ActiveEffect>& effects = iter->second.mEffects;
|
||||
for (std::vector<ActiveEffect>::iterator effectIt = effects.begin(); effectIt != effects.end();)
|
||||
++effectIt;
|
||||
}
|
||||
}
|
||||
if(removedSpell)
|
||||
continue;
|
||||
if(spellIt->mEffects.empty())
|
||||
spellIt = mSpells.erase(spellIt);
|
||||
else
|
||||
++spellIt;
|
||||
}
|
||||
|
||||
for(const auto& spell : mQueue)
|
||||
addToSpells(ptr, spell);
|
||||
mQueue.clear();
|
||||
|
||||
// Vanilla only does this on cell change I think
|
||||
const auto& spells = creatureStats.getSpells();
|
||||
for(const ESM::Spell* spell : spells)
|
||||
{
|
||||
if(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power && !isSpellActive(spell->mId))
|
||||
mSpells.emplace_back(ActiveSpellParams{spell, ptr});
|
||||
}
|
||||
|
||||
if(ptr.getClass().hasInventoryStore(ptr) && !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished()))
|
||||
{
|
||||
auto& store = ptr.getClass().getInventoryStore(ptr);
|
||||
if(store.getInvListener() != nullptr)
|
||||
{
|
||||
bool playNonLooping = !store.isFirstEquip();
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
for(int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++)
|
||||
{
|
||||
auto slot = store.getSlot(slotIndex);
|
||||
if(slot == store.end())
|
||||
continue;
|
||||
const auto& enchantmentId = slot->getClass().getEnchantment(*slot);
|
||||
if(enchantmentId.empty())
|
||||
continue;
|
||||
const ESM::Enchantment* enchantment = world->getStore().get<ESM::Enchantment>().find(enchantmentId);
|
||||
if(enchantment->mData.mType != ESM::Enchantment::ConstantEffect)
|
||||
continue;
|
||||
if(std::find_if(mSpells.begin(), mSpells.end(), [&] (const ActiveSpellParams& params)
|
||||
{
|
||||
if (effectIt->mTimeLeft <= 0)
|
||||
{
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mSpellsChanged)
|
||||
// Update effects
|
||||
for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
|
||||
{
|
||||
mSpellsChanged = false;
|
||||
rebuild = true;
|
||||
}
|
||||
const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid?
|
||||
bool removedSpell = false;
|
||||
for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
|
||||
{
|
||||
bool remove = applyMagicEffect(ptr, caster, *spellIt, *it, duration);
|
||||
if(remove)
|
||||
it = spellIt->mEffects.erase(it);
|
||||
else
|
||||
++it;
|
||||
removedSpell = applyPurges(ptr, &spellIt, &it);
|
||||
if(removedSpell)
|
||||
break;
|
||||
}
|
||||
if(removedSpell)
|
||||
continue;
|
||||
|
||||
if (rebuild)
|
||||
rebuildEffects();
|
||||
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)
|
||||
{
|
||||
const auto& store = ptr.getClass().getInventoryStore(ptr);
|
||||
auto slot = store.getSlot(spellIt->mSlot);
|
||||
remove = slot == store.end() || slot->getCellRef().getRefId() != spellIt->mId;
|
||||
}
|
||||
if(remove)
|
||||
{
|
||||
auto params = *spellIt;
|
||||
spellIt = mSpells.erase(spellIt);
|
||||
for(const auto& effect : params.mEffects)
|
||||
onMagicEffectRemoved(ptr, params, effect);
|
||||
applyPurges(ptr, &spellIt);
|
||||
continue;
|
||||
}
|
||||
++spellIt;
|
||||
}
|
||||
}
|
||||
|
||||
void ActiveSpells::rebuildEffects() const
|
||||
void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell)
|
||||
{
|
||||
mEffects = MagicEffects();
|
||||
|
||||
for (TIterator iter (begin()); iter!=end(); ++iter)
|
||||
if(spell.mType != ESM::ActiveSpells::Type_Consumable)
|
||||
{
|
||||
const std::vector<ActiveEffect>& effects = iter->second.mEffects;
|
||||
|
||||
for (std::vector<ActiveEffect>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt)
|
||||
auto found = std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& existing)
|
||||
{
|
||||
if (effectIt->mTimeLeft > 0)
|
||||
mEffects.add(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), MWMechanics::EffectParam(effectIt->mMagnitude));
|
||||
return spell.mId == existing.mId && spell.mCasterActorId == existing.mCasterActorId && spell.mSlot == existing.mSlot;
|
||||
});
|
||||
if(found != mSpells.end())
|
||||
{
|
||||
if(merge(found->mEffects, spell.mEffects))
|
||||
return;
|
||||
auto params = *found;
|
||||
mSpells.erase(found);
|
||||
for(const auto& effect : params.mEffects)
|
||||
onMagicEffectRemoved(ptr, params, effect);
|
||||
}
|
||||
}
|
||||
mSpells.emplace_back(spell);
|
||||
}
|
||||
|
||||
ActiveSpells::ActiveSpells()
|
||||
: mSpellsChanged (false)
|
||||
ActiveSpells::ActiveSpells() : mIterating(false)
|
||||
{}
|
||||
|
||||
const MagicEffects& ActiveSpells::getMagicEffects() const
|
||||
{
|
||||
update(0.f);
|
||||
return mEffects;
|
||||
}
|
||||
|
||||
ActiveSpells::TIterator ActiveSpells::begin() const
|
||||
{
|
||||
return mSpells.begin();
|
||||
|
@ -106,246 +288,159 @@ namespace MWMechanics
|
|||
return mSpells.end();
|
||||
}
|
||||
|
||||
double ActiveSpells::timeToExpire (const TIterator& iterator) const
|
||||
{
|
||||
const std::vector<ActiveEffect>& effects = iterator->second.mEffects;
|
||||
|
||||
float duration = 0;
|
||||
|
||||
for (std::vector<ActiveEffect>::const_iterator iter (effects.begin());
|
||||
iter!=effects.end(); ++iter)
|
||||
{
|
||||
if (iter->mTimeLeft > duration)
|
||||
duration = iter->mTimeLeft;
|
||||
}
|
||||
|
||||
if (duration < 0)
|
||||
return 0;
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
bool ActiveSpells::isSpellActive(const std::string& id) const
|
||||
{
|
||||
for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter)
|
||||
return std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& spell)
|
||||
{
|
||||
if (Misc::StringUtils::ciEqual(iter->first, id))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return Misc::StringUtils::ciEqual(spell.mId, id);
|
||||
}) != mSpells.end();
|
||||
}
|
||||
|
||||
const ActiveSpells::TContainer& ActiveSpells::getActiveSpells() const
|
||||
void ActiveSpells::addSpell(const ActiveSpellParams& params)
|
||||
{
|
||||
return mSpells;
|
||||
mQueue.emplace_back(params);
|
||||
}
|
||||
|
||||
void ActiveSpells::addSpell(const std::string &id, bool stack, const std::vector<ActiveEffect>& effects,
|
||||
const std::string &displayName, int casterActorId)
|
||||
void ActiveSpells::addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor)
|
||||
{
|
||||
TContainer::iterator it(mSpells.find(id));
|
||||
|
||||
ActiveSpellParams params;
|
||||
params.mEffects = effects;
|
||||
params.mDisplayName = displayName;
|
||||
params.mCasterActorId = casterActorId;
|
||||
|
||||
if (it == end() || stack)
|
||||
{
|
||||
mSpells.insert(std::make_pair(id, params));
|
||||
}
|
||||
else
|
||||
{
|
||||
// addSpell() is called with effects for a range.
|
||||
// but a spell may have effects with different ranges (e.g. Touch & Target)
|
||||
// so, if we see new effects for same spell assume additional
|
||||
// spell effects and add to existing effects of spell
|
||||
mergeEffects(params.mEffects, it->second.mEffects);
|
||||
it->second = params;
|
||||
}
|
||||
|
||||
mSpellsChanged = true;
|
||||
mQueue.emplace_back(ActiveSpellParams{spell, actor, true});
|
||||
}
|
||||
|
||||
void ActiveSpells::mergeEffects(std::vector<ActiveEffect>& addTo, const std::vector<ActiveEffect>& from)
|
||||
void ActiveSpells::purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr)
|
||||
{
|
||||
for (std::vector<ActiveEffect>::const_iterator effect(from.begin()); effect != from.end(); ++effect)
|
||||
assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this);
|
||||
mPurges.emplace(predicate);
|
||||
if(!mIterating)
|
||||
{
|
||||
// if effect is not in addTo, add it
|
||||
bool missing = true;
|
||||
for (std::vector<ActiveEffect>::const_iterator iter(addTo.begin()); iter != addTo.end(); ++iter)
|
||||
IterationGuard guard{*this};
|
||||
applyPurges(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void ActiveSpells::purge(EffectPredicate predicate, const MWWorld::Ptr& ptr)
|
||||
{
|
||||
assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this);
|
||||
mPurges.emplace(predicate);
|
||||
if(!mIterating)
|
||||
{
|
||||
IterationGuard guard{*this};
|
||||
applyPurges(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool ActiveSpells::applyPurges(const MWWorld::Ptr& ptr, std::list<ActiveSpellParams>::iterator* currentSpell, std::vector<ActiveEffect>::iterator* currentEffect)
|
||||
{
|
||||
bool removedCurrentSpell = false;
|
||||
while(!mPurges.empty())
|
||||
{
|
||||
auto predicate = mPurges.front();
|
||||
mPurges.pop();
|
||||
for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
|
||||
{
|
||||
if ((effect->mEffectId == iter->mEffectId) && (effect->mArg == iter->mArg))
|
||||
bool isCurrentSpell = currentSpell && *currentSpell == spellIt;
|
||||
std::visit([&] (auto&& variant)
|
||||
{
|
||||
missing = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (missing)
|
||||
{
|
||||
addTo.push_back(*effect);
|
||||
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);
|
||||
}
|
||||
}
|
||||
return removedCurrentSpell;
|
||||
}
|
||||
|
||||
void ActiveSpells::removeEffects(const std::string &id)
|
||||
void ActiveSpells::removeEffects(const MWWorld::Ptr& ptr, const std::string &id)
|
||||
{
|
||||
for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell)
|
||||
purge([=] (const ActiveSpellParams& params)
|
||||
{
|
||||
if (spell->first == id)
|
||||
{
|
||||
spell->second.mEffects.clear();
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
}
|
||||
return params.mId == id;
|
||||
}, ptr);
|
||||
}
|
||||
|
||||
void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const
|
||||
void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, short effectId)
|
||||
{
|
||||
for (TContainer::const_iterator it = begin(); it != end(); ++it)
|
||||
purge([=] (const ActiveSpellParams&, const ESM::ActiveEffect& effect)
|
||||
{
|
||||
for (std::vector<ActiveEffect>::const_iterator effectIt = it->second.mEffects.begin();
|
||||
effectIt != it->second.mEffects.end(); ++effectIt)
|
||||
{
|
||||
std::string name = it->second.mDisplayName;
|
||||
|
||||
float magnitude = effectIt->mMagnitude;
|
||||
if (magnitude)
|
||||
visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), effectIt->mEffectIndex, name, it->first, it->second.mCasterActorId, magnitude, effectIt->mTimeLeft, effectIt->mDuration);
|
||||
}
|
||||
}
|
||||
return effect.mEffectId == effectId;
|
||||
}, ptr);
|
||||
}
|
||||
|
||||
void ActiveSpells::purgeAll(float chance, bool spellOnly)
|
||||
void ActiveSpells::purge(const MWWorld::Ptr& ptr, int casterActorId)
|
||||
{
|
||||
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); )
|
||||
purge([=] (const ActiveSpellParams& params)
|
||||
{
|
||||
const std::string spellId = it->first;
|
||||
|
||||
// if spellOnly is true, dispell only spells. Leave potions, enchanted items etc.
|
||||
if (spellOnly)
|
||||
{
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellId);
|
||||
if (!spell || spell->mData.mType != ESM::Spell::ST_Spell)
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (Misc::Rng::roll0to99() < chance)
|
||||
mSpells.erase(it++);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
mSpellsChanged = true;
|
||||
return params.mCasterActorId == casterActorId;
|
||||
}, ptr);
|
||||
}
|
||||
|
||||
void ActiveSpells::purgeEffect(short effectId)
|
||||
void ActiveSpells::clear(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
|
||||
mQueue.clear();
|
||||
purge([] (const ActiveSpellParams& params) { return true; }, ptr);
|
||||
}
|
||||
|
||||
void ActiveSpells::skipWorsenings(double hours)
|
||||
{
|
||||
for(auto& spell : mSpells)
|
||||
{
|
||||
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
|
||||
effectIt != it->second.mEffects.end();)
|
||||
{
|
||||
if (effectIt->mEffectId == effectId)
|
||||
effectIt = it->second.mEffects.erase(effectIt);
|
||||
else
|
||||
++effectIt;
|
||||
}
|
||||
if(spell.mWorsenings >= 0)
|
||||
spell.mNextWorsening += hours;
|
||||
}
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
|
||||
void ActiveSpells::purgeEffect(short effectId, const std::string& sourceId, int effectIndex)
|
||||
{
|
||||
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
|
||||
{
|
||||
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
|
||||
effectIt != it->second.mEffects.end();)
|
||||
{
|
||||
if (effectIt->mEffectId == effectId && it->first == sourceId && (effectIndex < 0 || effectIndex == effectIt->mEffectIndex))
|
||||
effectIt = it->second.mEffects.erase(effectIt);
|
||||
else
|
||||
++effectIt;
|
||||
}
|
||||
}
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
|
||||
void ActiveSpells::purge(int casterActorId)
|
||||
{
|
||||
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
|
||||
{
|
||||
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
|
||||
effectIt != it->second.mEffects.end();)
|
||||
{
|
||||
if (it->second.mCasterActorId == casterActorId)
|
||||
effectIt = it->second.mEffects.erase(effectIt);
|
||||
else
|
||||
++effectIt;
|
||||
}
|
||||
}
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
|
||||
void ActiveSpells::purgeCorprusDisease()
|
||||
{
|
||||
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
|
||||
{
|
||||
bool hasCorprusEffect = false;
|
||||
for (std::vector<ActiveEffect>::iterator effectIt = iter->second.mEffects.begin();
|
||||
effectIt != iter->second.mEffects.end();++effectIt)
|
||||
{
|
||||
if (effectIt->mEffectId == ESM::MagicEffect::Corprus)
|
||||
{
|
||||
hasCorprusEffect = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCorprusEffect)
|
||||
{
|
||||
mSpells.erase(iter++);
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
void ActiveSpells::clear()
|
||||
{
|
||||
mSpells.clear();
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
|
||||
void ActiveSpells::writeState(ESM::ActiveSpells &state) const
|
||||
{
|
||||
for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it)
|
||||
{
|
||||
// Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp
|
||||
ESM::ActiveSpells::ActiveSpellParams params;
|
||||
params.mEffects = it->second.mEffects;
|
||||
params.mCasterActorId = it->second.mCasterActorId;
|
||||
params.mDisplayName = it->second.mDisplayName;
|
||||
|
||||
state.mSpells.insert (std::make_pair(it->first, params));
|
||||
}
|
||||
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 (ESM::ActiveSpells::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it)
|
||||
{
|
||||
// Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp
|
||||
ActiveSpellParams params;
|
||||
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;
|
||||
}
|
||||
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
|
||||
#define GAME_MWMECHANICS_ACTIVESPELLS_H
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <components/esm/activespells.hpp>
|
||||
|
||||
#include "../mwworld/timestamp.hpp"
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
#include "magiceffects.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct Enchantment;
|
||||
struct Spell;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
/// \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.
|
||||
class ActiveSpells
|
||||
{
|
||||
public:
|
||||
|
||||
typedef ESM::ActiveEffect ActiveEffect;
|
||||
|
||||
struct ActiveSpellParams
|
||||
using ActiveEffect = ESM::ActiveEffect;
|
||||
class ActiveSpellParams
|
||||
{
|
||||
std::vector<ActiveEffect> mEffects;
|
||||
MWWorld::TimeStamp mTimeStamp;
|
||||
std::string mDisplayName;
|
||||
std::string mId;
|
||||
std::vector<ActiveEffect> mEffects;
|
||||
std::string mDisplayName;
|
||||
int mCasterActorId;
|
||||
int mSlot;
|
||||
ESM::ActiveSpells::EffectType mType;
|
||||
int mWorsenings;
|
||||
MWWorld::TimeStamp mNextWorsening;
|
||||
|
||||
// The caster that inflicted this spell on us
|
||||
int mCasterActorId;
|
||||
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; }
|
||||
|
||||
ESM::ActiveSpells::EffectType getType() const { return mType; }
|
||||
|
||||
int getCasterActorId() const { return mCasterActorId; }
|
||||
|
||||
int getWorsenings() const { return mWorsenings; }
|
||||
|
||||
const std::string& getDisplayName() const { return mDisplayName; }
|
||||
|
||||
// Increments worsenings count and sets the next timestamp
|
||||
void worsen();
|
||||
|
||||
bool shouldWorsen() const;
|
||||
|
||||
void resetWorsenings();
|
||||
};
|
||||
|
||||
typedef std::multimap<std::string, ActiveSpellParams > TContainer;
|
||||
typedef TContainer::const_iterator TIterator;
|
||||
typedef std::list<ActiveSpellParams>::const_iterator TIterator;
|
||||
|
||||
void readState (const ESM::ActiveSpells& state);
|
||||
void writeState (ESM::ActiveSpells& state) const;
|
||||
|
@ -43,24 +86,29 @@ namespace MWMechanics
|
|||
|
||||
TIterator end() const;
|
||||
|
||||
void update(float duration) const;
|
||||
void update(const MWWorld::Ptr& ptr, float duration);
|
||||
|
||||
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;
|
||||
mutable MagicEffects mEffects;
|
||||
mutable bool mSpellsChanged;
|
||||
struct IterationGuard
|
||||
{
|
||||
ActiveSpells& mActiveSpells;
|
||||
|
||||
void rebuildEffects() const;
|
||||
IterationGuard(ActiveSpells& spells);
|
||||
~IterationGuard();
|
||||
};
|
||||
|
||||
/// Add any effects that are in "from" and not in "addTo" to "addTo"
|
||||
void mergeEffects(std::vector<ActiveEffect>& addTo, const std::vector<ActiveEffect>& from);
|
||||
std::list<ActiveSpellParams> mSpells;
|
||||
std::vector<ActiveSpellParams> mQueue;
|
||||
std::queue<Predicate> mPurges;
|
||||
bool mIterating;
|
||||
|
||||
double timeToExpire (const TIterator& iterator) const;
|
||||
///< Returns time (in in-game hours) until the spell pointed to by \a iterator
|
||||
/// expires.
|
||||
void addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell);
|
||||
|
||||
const TContainer& getActiveSpells() const;
|
||||
bool applyPurges(const MWWorld::Ptr& ptr, std::list<ActiveSpellParams>::iterator* currentSpell = nullptr, std::vector<ActiveEffect>::iterator* currentEffect = nullptr);
|
||||
|
||||
public:
|
||||
|
||||
|
@ -70,40 +118,31 @@ namespace MWMechanics
|
|||
///
|
||||
/// \brief addSpell
|
||||
/// \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,
|
||||
const std::string& displayName, int casterActorId);
|
||||
void addSpell (const ActiveSpellParams& params);
|
||||
|
||||
/// Bypasses resistances
|
||||
void addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor);
|
||||
|
||||
/// 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
|
||||
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);
|
||||
void purge(EffectPredicate predicate, const MWWorld::Ptr& ptr);
|
||||
void purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr);
|
||||
|
||||
/// Remove all active effects, if roll succeeds (for each effect)
|
||||
void purgeAll(float chance, bool spellOnly = false);
|
||||
|
||||
/// Remove all effects with CASTER_LINKED flag that were cast by \a casterActorId
|
||||
void purge (int casterActorId);
|
||||
/// Remove all effects that were cast by \a casterActorId
|
||||
void purge (const MWWorld::Ptr& ptr, int casterActorId);
|
||||
|
||||
/// Remove all spells
|
||||
void clear();
|
||||
void clear(const MWWorld::Ptr& ptr);
|
||||
|
||||
bool isSpellActive (const std::string& id) const;
|
||||
///< case insensitive
|
||||
|
||||
void purgeCorprusDisease();
|
||||
|
||||
const MagicEffects& getMagicEffects() const;
|
||||
|
||||
void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const;
|
||||
|
||||
void skipWorsenings(double hours);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
namespace MWMechanics
|
||||
{
|
||||
Actor::Actor(const MWWorld::Ptr &ptr, MWRender::Animation *animation)
|
||||
: mPositionAdjusted(false)
|
||||
{
|
||||
mCharacterController.reset(new CharacterController(ptr, animation));
|
||||
}
|
||||
|
@ -58,4 +59,14 @@ namespace MWMechanics
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
||||
void setPositionAdjusted(bool adjusted);
|
||||
bool getPositionAdjusted() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<CharacterController> mCharacterController;
|
||||
int mGreetingTimer{0};
|
||||
|
@ -55,6 +58,7 @@ namespace MWMechanics
|
|||
GreetingState mGreetingState{Greet_None};
|
||||
bool mIsTurningToPlayer{false};
|
||||
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;
|
||||
|
||||
void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor);
|
||||
void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor);
|
||||
|
||||
void adjustMagicEffects (const MWWorld::Ptr& creature);
|
||||
void adjustMagicEffects (const MWWorld::Ptr& creature, float duration);
|
||||
|
||||
void calculateDynamicStats (const MWWorld::Ptr& ptr);
|
||||
|
||||
void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration);
|
||||
void calculateNpcStatModifiers (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.
|
||||
|
||||
void removeActor (const MWWorld::Ptr& ptr);
|
||||
void removeActor (const MWWorld::Ptr& ptr, bool keepActive);
|
||||
///< Deregister an actor for stats management
|
||||
///
|
||||
/// \note Ignored, if \a ptr is not a registered actor.
|
||||
|
@ -208,7 +204,6 @@ namespace MWMechanics
|
|||
|
||||
private:
|
||||
void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl);
|
||||
void applyCureEffects (const MWWorld::Ptr& actor);
|
||||
|
||||
PtrActorMap mActors;
|
||||
float mTimerDisposeSummonsCorpses;
|
||||
|
|
|
@ -348,7 +348,7 @@ namespace MWMechanics
|
|||
|
||||
bool runFallback = true;
|
||||
|
||||
if (pathgrid && !actor.getClass().isPureWaterCreature(actor))
|
||||
if (pathgrid != nullptr && !pathgrid->mPoints.empty() && !actor.getClass().isPureWaterCreature(actor))
|
||||
{
|
||||
ESM::Pathgrid::PointList points;
|
||||
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)
|
||||
{
|
||||
bestActionRating = rating;
|
||||
bestAction.reset(new ActionSpell(it->first->mId));
|
||||
antiFleeRating = vanillaRateSpell(it->first, actor, enemy);
|
||||
bestAction.reset(new ActionSpell(spell->mId));
|
||||
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)
|
||||
{
|
||||
bestActionRating = rating;
|
||||
|
|
|
@ -34,6 +34,11 @@ namespace
|
|||
const float actorTolerance = 2 * speed * duration + 1.2 * std::max(halfExtents.x(), halfExtents.y());
|
||||
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) :
|
||||
|
@ -118,7 +123,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
|||
|
||||
if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed)
|
||||
{
|
||||
if (actor.getClass().isBipedal(actor))
|
||||
if (canOpenDoors(actor))
|
||||
openDoors(actor);
|
||||
|
||||
const bool wasShortcutting = mIsShortcutting;
|
||||
|
@ -232,7 +237,7 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor)
|
|||
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
|
||||
|
||||
const MWWorld::Ptr door = getNearbyDoor(actor, distance);
|
||||
if (!door.isEmpty() && actor.getClass().isBipedal(actor))
|
||||
if (!door.isEmpty() && canOpenDoors(actor))
|
||||
{
|
||||
openDoors(actor);
|
||||
}
|
||||
|
@ -443,9 +448,13 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::
|
|||
result |= DetourNavigator::Flag_swim;
|
||||
|
||||
if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0)
|
||||
{
|
||||
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;
|
||||
|
||||
return result;
|
||||
|
@ -457,20 +466,31 @@ DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::P
|
|||
const DetourNavigator::Flags flags = getNavigatorFlags(actor);
|
||||
const MWWorld::Class& actorClass = actor.getClass();
|
||||
|
||||
if (flags & DetourNavigator::Flag_swim)
|
||||
costs.mWater = divOrMax(costs.mWater, actorClass.getSwimSpeed(actor));
|
||||
const float swimSpeed = (flags & DetourNavigator::Flag_swim) == 0
|
||||
? 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)
|
||||
walkCost = divOrMax(1.0, actorClass.getWalkSpeed(actor));
|
||||
else
|
||||
walkCost = divOrMax(1.0, actorClass.getRunSpeed(actor));
|
||||
costs.mDoor = costs.mDoor * walkCost;
|
||||
costs.mPathgrid = costs.mPathgrid * walkCost;
|
||||
costs.mGround = costs.mGround * walkCost;
|
||||
}
|
||||
return actorClass.getWalkSpeed(actor);
|
||||
return actorClass.getRunSpeed(actor);
|
||||
} ();
|
||||
|
||||
const float maxSpeed = std::max(swimSpeed, walkSpeed);
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "aitravel.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <components/esm/aisequence.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;
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -70,16 +77,24 @@ namespace MWMechanics
|
|||
|
||||
// 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.
|
||||
int destinationTolerance = 64;
|
||||
if (distance(actorPos, targetPos) <= destinationTolerance)
|
||||
if (mDestinationCheck.update(duration) == Misc::TimerStatus::Elapsed)
|
||||
{
|
||||
std::vector<MWWorld::Ptr> targetActors;
|
||||
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors);
|
||||
|
||||
if (!result.first.isEmpty())
|
||||
std::vector<MWWorld::Ptr> occupyingActors;
|
||||
if (isAreaOccupiedByOtherActor(actor, targetPos, &occupyingActors))
|
||||
{
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||
return true;
|
||||
const float actorRadius = getActorRadius(actor);
|
||||
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 bool mHidden;
|
||||
|
||||
AiReactionTimer mDestinationCheck;
|
||||
};
|
||||
|
||||
struct AiInternalTravel final : public AiTravel
|
||||
|
|
|
@ -85,14 +85,6 @@ namespace MWMechanics
|
|||
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)
|
||||
{
|
||||
actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
|
||||
|
@ -758,6 +750,9 @@ namespace MWMechanics
|
|||
const ESM::Pathgrid *pathgrid =
|
||||
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));
|
||||
|
||||
getPathGridGraph(currentCell).getNeighbouringPoints(index, points);
|
||||
|
|
|
@ -545,20 +545,12 @@ namespace MWMechanics
|
|||
mAiSequence.writeState(state.mAiSequence);
|
||||
mMagicEffects.writeState(state.mMagicEffects);
|
||||
|
||||
state.mSummonedCreatureMap = mSummonedCreatures;
|
||||
state.mSummonedCreatures = mSummonedCreatures;
|
||||
state.mSummonGraveyard = mSummonGraveyard;
|
||||
|
||||
state.mHasAiSettings = true;
|
||||
for (int i=0; i<4; ++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)
|
||||
|
@ -618,7 +610,7 @@ namespace MWMechanics
|
|||
// 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)
|
||||
{
|
||||
const auto& effects = spell.second.mEffects;
|
||||
const auto& effects = spell.getEffects();
|
||||
return std::find_if(effects.begin(), effects.end(), [&] (const auto& effect)
|
||||
{
|
||||
return effect.mEffectId == effectId;
|
||||
|
@ -629,21 +621,12 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
mSummonedCreatures = state.mSummonedCreatureMap;
|
||||
mSummonedCreatures = state.mSummonedCreatures;
|
||||
mSummonGraveyard = state.mSummonGraveyard;
|
||||
|
||||
if (state.mHasAiSettings)
|
||||
for (int i=0; i<4; ++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)
|
||||
|
@ -710,7 +693,7 @@ namespace MWMechanics
|
|||
return mTimeOfDeath;
|
||||
}
|
||||
|
||||
std::map<ESM::SummonKey, int>& CreatureStats::getSummonedCreatureMap()
|
||||
std::multimap<int, int>& CreatureStats::getSummonedCreatureMap()
|
||||
{
|
||||
return mSummonedCreatures;
|
||||
}
|
||||
|
@ -719,23 +702,4 @@ namespace MWMechanics
|
|||
{
|
||||
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
|
||||
#define GAME_MWMECHANICS_CREATURESTATS_H
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
@ -87,14 +88,12 @@ namespace MWMechanics
|
|||
float mSideMovementAngle;
|
||||
|
||||
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.
|
||||
// This may be necessary when the creature is in an inactive cell.
|
||||
std::vector<int> mSummonGraveyard;
|
||||
|
||||
std::map<std::string, CorprusStats> mCorprusSpells;
|
||||
|
||||
protected:
|
||||
int mLevel;
|
||||
|
||||
|
@ -236,7 +235,7 @@ namespace MWMechanics
|
|||
void setBlock(bool value);
|
||||
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
|
||||
|
||||
enum Flag
|
||||
|
@ -297,12 +296,6 @@ namespace MWMechanics
|
|||
|
||||
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; }
|
||||
void setSideMovementAngle(float angle) { mSideMovementAngle = angle; }
|
||||
};
|
||||
|
|
|
@ -30,12 +30,11 @@ namespace MWMechanics
|
|||
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
"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();
|
||||
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))
|
||||
continue;
|
||||
|
||||
|
@ -56,7 +55,7 @@ namespace MWMechanics
|
|||
if (Misc::Rng::rollDice(10000) < x)
|
||||
{
|
||||
// Contracted disease!
|
||||
actor.getClass().getCreatureStats(actor).getSpells().add(it->first);
|
||||
actor.getClass().getCreatureStats(actor).getSpells().add(spell);
|
||||
MWBase::Environment::get().getWorld()->applyLoopingParticles(actor);
|
||||
|
||||
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
|
||||
{
|
||||
// Don't need to save Modifiers, they are recalculated every frame anyway.
|
||||
for (Collection::const_iterator iter (begin()); iter!=end(); ++iter)
|
||||
for (const auto& [key, params] : mCollection)
|
||||
{
|
||||
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
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
class MagicEffects
|
||||
{
|
||||
|
|
|
@ -84,7 +84,7 @@ namespace MWMechanics
|
|||
// reset
|
||||
creatureStats.setLevel(player->mNpdt.mLevel);
|
||||
creatureStats.getSpells().clear(true);
|
||||
creatureStats.modifyMagicEffects(MagicEffects());
|
||||
creatureStats.getActiveSpells().clear(ptr);
|
||||
|
||||
for (int i=0; i<27; ++i)
|
||||
npcStats.getSkill (i).setBase (player->mNpdt.mSkills[i]);
|
||||
|
@ -213,6 +213,7 @@ namespace MWMechanics
|
|||
int attributes[ESM::Attribute::Length];
|
||||
for (int i=0; i<ESM::Attribute::Length; ++i)
|
||||
attributes[i] = npcStats.getAttribute(i).getBase();
|
||||
npcStats.updateHealth();
|
||||
|
||||
std::vector<std::string> selectedSpells = autoCalcPlayerSpells(skills, attributes, race);
|
||||
|
||||
|
@ -221,6 +222,7 @@ namespace MWMechanics
|
|||
|
||||
// forced update and current value adjustments
|
||||
mActors.updateActor (ptr, 0);
|
||||
mActors.updateActor (ptr, 0);
|
||||
|
||||
for (int i=0; i<3; ++i)
|
||||
{
|
||||
|
@ -257,11 +259,11 @@ namespace MWMechanics
|
|||
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())
|
||||
MWBase::Environment::get().getWindowManager()->watchActor(MWWorld::Ptr());
|
||||
mActors.removeActor(ptr);
|
||||
mActors.removeActor(ptr, keepActive);
|
||||
mObjects.removeObject(ptr);
|
||||
}
|
||||
|
||||
|
@ -282,24 +284,6 @@ namespace MWMechanics
|
|||
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)
|
||||
{
|
||||
// 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
|
||||
// have been made for them. Make sure they're properly updated.
|
||||
mActors.removeActor(ptr);
|
||||
mActors.removeActor(ptr, true);
|
||||
mActors.addActor(ptr, true);
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ namespace MWMechanics
|
|||
void add (const MWWorld::Ptr& ptr) override;
|
||||
///< 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
|
||||
|
||||
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;
|
||||
bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override;
|
||||
|
||||
void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) override;
|
||||
|
||||
private:
|
||||
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);
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "movement.hpp"
|
||||
|
||||
|
@ -72,6 +74,15 @@ namespace MWMechanics
|
|||
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()
|
||||
: mWalkState(WalkState::Initial)
|
||||
, mStateDuration(0)
|
||||
|
|
|
@ -3,9 +3,12 @@
|
|||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
class ConstPtr;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
|
@ -21,6 +24,9 @@ namespace MWMechanics
|
|||
/** \return Pointer to the door, or empty pointer if none exists **/
|
||||
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
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -397,7 +397,7 @@ namespace MWMechanics
|
|||
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,
|
||||
flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType, std::back_inserter(mPath));
|
||||
|
|
|
@ -16,33 +16,30 @@
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor
|
||||
float getProbability(const MWMechanics::ActiveSpells& activeSpells)
|
||||
{
|
||||
public:
|
||||
float mProbability{0.f};
|
||||
|
||||
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
|
||||
float probability = 0.f;
|
||||
for(const auto& params : activeSpells)
|
||||
{
|
||||
if (key.mId == ESM::MagicEffect::SpellAbsorption)
|
||||
for(const auto& effect : params.getEffects())
|
||||
{
|
||||
if (mProbability == 0.f)
|
||||
mProbability = magnitude / 100;
|
||||
else
|
||||
if(effect.mEffectId == ESM::MagicEffect::SpellAbsorption)
|
||||
{
|
||||
// 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 - mProbability;
|
||||
failProbability *= 1.f - magnitude / 100;
|
||||
mProbability = 1.f - failProbability;
|
||||
if(probability == 0.f)
|
||||
probability = effect.mMagnitude / 100;
|
||||
else
|
||||
{
|
||||
// If there are different sources of SpellAbsorption effect, multiply failing probability for all effects.
|
||||
// Real absorption probability will be the (1 - total fail chance) in this case.
|
||||
float failProbability = 1.f - probability;
|
||||
failProbability *= 1.f - effect.mMagnitude / 100;
|
||||
probability = 1.f - failProbability;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return static_cast<int>(probability * 100);
|
||||
}
|
||||
|
||||
bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
|
||||
{
|
||||
|
@ -53,13 +50,7 @@ namespace MWMechanics
|
|||
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f)
|
||||
return false;
|
||||
|
||||
GetAbsorptionProbability check;
|
||||
stats.getActiveSpells().visitEffectSources(check);
|
||||
stats.getSpells().visitEffectSources(check);
|
||||
if (target.getClass().hasInventoryStore(target))
|
||||
target.getClass().getInventoryStore(target).visitEffectSources(check);
|
||||
|
||||
int chance = check.mProbability * 100;
|
||||
int chance = getProbability(stats.getActiveSpells());
|
||||
if (Misc::Rng::roll0to99() >= chance)
|
||||
return false;
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue