Merge remote-tracking branch 'upstream/master' into detain-hash

Tests which no longer work are commented out.
Some of these don't work because they're effectively testing for the
presence of bugs in the old implementation.
Others don't work because we're no longer accidentally disabling the
boost::program_options feature where it generates an error if only part
of a token gets consumed.
These will be fixed by later commits.
pull/3225/head
AnyOldName3 3 years ago
commit 1b83b08d80

@ -0,0 +1,50 @@
name: CMake
on:
pull_request:
branches: [ master ]
env:
BUILD_TYPE: RelWithDebInfo
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Add OpenMW PPA Dependancies
run: sudo add-apt-repository ppa:openmw/openmw; sudo apt-get update
- name: Install Building Dependancies
run: sudo CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
- name: Prime ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ matrix.os }}-${{ env.BUILD_TYPE }}
max-size: 1000M
- name: Configure
run: cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_FLAGS='-Werror' -DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy"
- name: Build
run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3
- name: Install
shell: bash
run: cmake --install .
- name: Create Artifact
shell: bash
working-directory: install
run: |
ls -laR
7z a ../build_artifact.7z .
- name: Upload Artifact
uses: actions/upload-artifact@v1
with:
path: ./build_artifact.7z
name: build_artifact.7z

@ -80,6 +80,7 @@ Programmers
Federico Guerra (FedeWar)
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
-------------

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

@ -0,0 +1,64 @@
#include "tableheadermouseeventhandler.hpp"
#include "dragrecordtable.hpp"
#include <QMenu>
#include <QPoint>
namespace CSVWorld
{
TableHeaderMouseEventHandler::TableHeaderMouseEventHandler(DragRecordTable * parent)
: QWidget(parent)
, table(*parent)
, header(*table.horizontalHeader())
{
header.setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
connect(
&header, &QHeaderView::customContextMenuRequested, [=](const QPoint & position) { showContextMenu(position); });
header.viewport()->installEventFilter(this);
}
bool TableHeaderMouseEventHandler::eventFilter(QObject * tableWatched, QEvent * event)
{
if (event->type() == QEvent::Type::MouseButtonPress)
{
auto & clickEvent = static_cast<QMouseEvent &>(*event);
if ((clickEvent.button() == Qt::MiddleButton))
{
const auto & index = table.indexAt(clickEvent.pos());
table.setColumnHidden(index.column(), true);
clickEvent.accept();
return true;
}
}
return false;
}
void TableHeaderMouseEventHandler::showContextMenu(const QPoint & position)
{
auto & menu{createContextMenu()};
menu.popup(header.viewport()->mapToGlobal(position));
}
QMenu & TableHeaderMouseEventHandler::createContextMenu()
{
auto * menu = new QMenu(this);
for (int i = 0; i < table.model()->columnCount(); ++i)
{
const auto & name = table.model()->headerData(i, Qt::Horizontal, Qt::DisplayRole);
QAction * action{new QAction(name.toString(), this)};
action->setCheckable(true);
action->setChecked(!table.isColumnHidden(i));
menu->addAction(action);
connect(action, &QAction::triggered, [=]() {
table.setColumnHidden(i, !action->isChecked());
action->setChecked(!action->isChecked());
action->toggle();
});
}
return *menu;
}
} // namespace CSVWorld

@ -0,0 +1,25 @@
#pragma once
#include <QHeaderView>
#include <QtGui>
namespace CSVWorld
{
class DragRecordTable;
class TableHeaderMouseEventHandler : public QWidget
{
public:
explicit TableHeaderMouseEventHandler(DragRecordTable * parent);
void showContextMenu(const QPoint &);
private:
DragRecordTable & table;
QHeaderView & header;
QMenu & createContextMenu();
bool eventFilter(QObject *, QEvent *) override;
}; // class TableHeaderMouseEventHandler
} // namespace CSVWorld

@ -2,6 +2,7 @@
set(GAME
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;

@ -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,347 +5,442 @@
#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 MWMechanics
namespace
{
void ActiveSpells::update(float duration) const
bool merge(std::vector<ESM::ActiveEffect>& present, const std::vector<ESM::ActiveEffect>& queued)
{
bool rebuild = false;
// Erase no longer active spells and effects
if (duration > 0)
// 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)
{
TContainer::iterator iter (mSpells.begin());
while (iter!=mSpells.end())
{
if (!timeToExpire (iter))
{
mSpells.erase (iter++);
rebuild = true;
}
else
{
bool interrupt = false;
std::vector<ActiveEffect>& effects = iter->second.mEffects;
for (std::vector<ActiveEffect>::iterator effectIt = effects.begin(); effectIt != effects.end();)
{
if (effectIt->mTimeLeft <= 0)
{
rebuild = true;
// Note: it we expire a Corprus effect, we should remove the whole spell.
if (effectIt->mEffectId == ESM::MagicEffect::Corprus)
{
iter = mSpells.erase (iter);
interrupt = true;
break;
}
effectIt = effects.erase(effectIt);
}
else
{
effectIt->mTimeLeft -= duration;
++effectIt;
}
}
if (!interrupt)
++iter;
}
}
}
if (mSpellsChanged)
{
mSpellsChanged = false;
rebuild = true;
}
if (rebuild)
rebuildEffects();
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 ActiveSpells::rebuildEffects() const
void addEffects(std::vector<ESM::ActiveEffect>& effects, const ESM::EffectList& list, bool ignoreResistances = false)
{
mEffects = MagicEffects();
for (TIterator iter (begin()); iter!=end(); ++iter)
int currentEffectIndex = 0;
for(const auto& enam : list.mList)
{
const std::vector<ActiveEffect>& effects = iter->second.mEffects;
for (std::vector<ActiveEffect>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt)
{
if (effectIt->mTimeLeft > 0)
mEffects.add(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), MWMechanics::EffectParam(effectIt->mMagnitude));
}
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);
}
}
}
ActiveSpells::ActiveSpells()
: mSpellsChanged (false)
{}
namespace MWMechanics
{
ActiveSpells::IterationGuard::IterationGuard(ActiveSpells& spells) : mActiveSpells(spells)
{
mActiveSpells.mIterating = true;
}
const MagicEffects& ActiveSpells::getMagicEffects() const
ActiveSpells::IterationGuard::~IterationGuard()
{
update(0.f);
return mEffects;
mActiveSpells.mIterating = false;
}
ActiveSpells::TIterator ActiveSpells::begin() const
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster)
: mId(cast.mId), mDisplayName(cast.mSourceName), mCasterActorId(-1), mSlot(cast.mSlot), mType(cast.mType), mWorsenings(-1)
{
return mSpells.begin();
if(!caster.isEmpty() && caster.getClass().isActor())
mCasterActorId = caster.getClass().getCreatureStats(caster).getActorId();
}
ActiveSpells::TIterator ActiveSpells::end() const
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances)
: mId(spell->mId), mDisplayName(spell->mName), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()), mSlot(0)
, mType(spell->mData.mType == ESM::Spell::ST_Ability ? ESM::ActiveSpells::Type_Ability : ESM::ActiveSpells::Type_Permanent), mWorsenings(-1)
{
return mSpells.end();
assert(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power);
addEffects(mEffects, spell->mEffects, ignoreResistances);
}
double ActiveSpells::timeToExpire (const TIterator& iterator) const
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor)
: mId(item.getCellRef().getRefId()), mDisplayName(item.getClass().getName(item)), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
, mSlot(slotIndex), mType(ESM::ActiveSpells::Type_Enchantment), mWorsenings(-1)
{
const std::vector<ActiveEffect>& effects = iterator->second.mEffects;
assert(enchantment->mData.mType == ESM::Enchantment::ConstantEffect);
addEffects(mEffects, enchantment->mEffects);
}
float duration = 0;
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params)
: mId(params.mId), mEffects(params.mEffects), mDisplayName(params.mDisplayName), mCasterActorId(params.mCasterActorId)
, mSlot(params.mItem.isSet() ? params.mItem.mIndex : 0)
, mType(params.mType), mWorsenings(params.mWorsenings), mNextWorsening({params.mNextWorsening})
{}
for (std::vector<ActiveEffect>::const_iterator iter (effects.begin());
iter!=effects.end(); ++iter)
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)
{
if (iter->mTimeLeft > duration)
duration = iter->mTimeLeft;
// 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;
}
if (duration < 0)
return 0;
return duration;
void ActiveSpells::ActiveSpellParams::worsen()
{
++mWorsenings;
if(!mWorsenings)
mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp();
mNextWorsening += CorprusStats::sWorseningPeriod;
}
bool ActiveSpells::isSpellActive(const std::string& id) const
bool ActiveSpells::ActiveSpellParams::shouldWorsen() const
{
for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter)
{
if (Misc::StringUtils::ciEqual(iter->first, id))
return true;
}
return false;
return mWorsenings >= 0 && MWBase::Environment::get().getWorld()->getTimeStamp() >= mNextWorsening;
}
const ActiveSpells::TContainer& ActiveSpells::getActiveSpells() const
void ActiveSpells::ActiveSpellParams::resetWorsenings()
{
return mSpells;
mWorsenings = -1;
}
void ActiveSpells::addSpell(const std::string &id, bool stack, const std::vector<ActiveEffect>& effects,
const std::string &displayName, int casterActorId)
void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration)
{
TContainer::iterator it(mSpells.find(id));
const auto& creatureStats = ptr.getClass().getCreatureStats(ptr);
assert(&creatureStats.getActiveSpells() == this);
IterationGuard guard{*this};
// Erase no longer active spells and effects
for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
{
if(spellIt->mType != ESM::ActiveSpells::Type_Temporary && spellIt->mType != ESM::ActiveSpells::Type_Consumable)
{
++spellIt;
continue;
}
bool removedSpell = false;
for(auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();)
{
if(effectIt->mFlags & ESM::ActiveEffect::Flag_Remove && effectIt->mTimeLeft <= 0.f)
{
auto effect = *effectIt;
effectIt = spellIt->mEffects.erase(effectIt);
onMagicEffectRemoved(ptr, *spellIt, effect);
removedSpell = applyPurges(ptr, &spellIt, &effectIt);
if(removedSpell)
break;
}
else
{
++effectIt;
}
}
if(removedSpell)
continue;
if(spellIt->mEffects.empty())
spellIt = mSpells.erase(spellIt);
else
++spellIt;
}
ActiveSpellParams params;
params.mEffects = effects;
params.mDisplayName = displayName;
params.mCasterActorId = casterActorId;
for(const auto& spell : mQueue)
addToSpells(ptr, spell);
mQueue.clear();
if (it == end() || stack)
// Vanilla only does this on cell change I think
const auto& spells = creatureStats.getSpells();
for(const ESM::Spell* spell : spells)
{
mSpells.insert(std::make_pair(id, params));
if(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power && !isSpellActive(spell->mId))
mSpells.emplace_back(ActiveSpellParams{spell, ptr});
}
else
if(ptr.getClass().hasInventoryStore(ptr) && !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished()))
{
// addSpell() is called with effects for a range.
// 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;
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)
{
return params.mSlot == slotIndex && params.mType == ESM::ActiveSpells::Type_Enchantment && params.mId == slot->getCellRef().getRefId();
}) != mSpells.end())
continue;
ActiveSpellParams params(*slot, enchantment, slotIndex, ptr);
mSpells.emplace_back(params);
for(const auto& effect : params.mEffects)
MWMechanics::playEffects(ptr, *world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId), playNonLooping);
}
}
}
mSpellsChanged = true;
}
void ActiveSpells::mergeEffects(std::vector<ActiveEffect>& addTo, const std::vector<ActiveEffect>& from)
{
for (std::vector<ActiveEffect>::const_iterator effect(from.begin()); effect != from.end(); ++effect)
// Update effects
for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
{
// if effect is not in addTo, add it
bool missing = true;
for (std::vector<ActiveEffect>::const_iterator iter(addTo.begin()); iter != addTo.end(); ++iter)
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();)
{
if ((effect->mEffectId == iter->mEffectId) && (effect->mArg == iter->mArg))
{
missing = false;
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 (missing)
if(removedSpell)
continue;
bool remove = false;
if(spellIt->mType == ESM::ActiveSpells::Type_Ability || spellIt->mType == ESM::ActiveSpells::Type_Permanent)
remove = !spells.hasSpell(spellIt->mId);
else if(spellIt->mType == ESM::ActiveSpells::Type_Enchantment)
{
addTo.push_back(*effect);
const auto& store = ptr.getClass().getInventoryStore(ptr);
auto slot = store.getSlot(spellIt->mSlot);
remove = slot == store.end() || slot->getCellRef().getRefId() != spellIt->mId;
}
if(remove)
{
auto params = *spellIt;
spellIt = mSpells.erase(spellIt);
for(const auto& effect : params.mEffects)
onMagicEffectRemoved(ptr, params, effect);
applyPurges(ptr, &spellIt);
continue;
}
++spellIt;
}
}
void ActiveSpells::removeEffects(const std::string &id)
void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell)
{
for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell)
if(spell.mType != ESM::ActiveSpells::Type_Consumable)
{
if (spell->first == id)
auto found = std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& existing)
{
return spell.mId == existing.mId && spell.mCasterActorId == existing.mCasterActorId && spell.mSlot == existing.mSlot;
});
if(found != mSpells.end())
{
spell->second.mEffects.clear();
mSpellsChanged = true;
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);
}
void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const
ActiveSpells::ActiveSpells() : mIterating(false)
{}
ActiveSpells::TIterator ActiveSpells::begin() const
{
for (TContainer::const_iterator it = begin(); it != end(); ++it)
{
for (std::vector<ActiveEffect>::const_iterator effectIt = it->second.mEffects.begin();
effectIt != it->second.mEffects.end(); ++effectIt)
{
std::string name = it->second.mDisplayName;
return mSpells.begin();
}
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);
}
}
ActiveSpells::TIterator ActiveSpells::end() const
{
return mSpells.end();
}
void ActiveSpells::purgeAll(float chance, bool spellOnly)
bool ActiveSpells::isSpellActive(const std::string& id) const
{
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); )
return std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& spell)
{
const std::string spellId = it->first;
return Misc::StringUtils::ciEqual(spell.mId, id);
}) != mSpells.end();
}
// if spellOnly is true, dispell only spells. Leave potions, enchanted items etc.
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;
}
}
void ActiveSpells::addSpell(const ActiveSpellParams& params)
{
mQueue.emplace_back(params);
}
if (Misc::Rng::roll0to99() < chance)
mSpells.erase(it++);
else
++it;
}
mSpellsChanged = true;
void ActiveSpells::addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor)
{
mQueue.emplace_back(ActiveSpellParams{spell, actor, true});
}
void ActiveSpells::purgeEffect(short effectId)
void ActiveSpells::purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr)
{
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this);
mPurges.emplace(predicate);
if(!mIterating)
{
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
effectIt != it->second.mEffects.end();)
{
if (effectIt->mEffectId == effectId)
effectIt = it->second.mEffects.erase(effectIt);
else
++effectIt;
}
IterationGuard guard{*this};
applyPurges(ptr);
}
mSpellsChanged = true;
}
void ActiveSpells::purgeEffect(short effectId, const std::string& sourceId, int effectIndex)
void ActiveSpells::purge(EffectPredicate predicate, const MWWorld::Ptr& ptr)
{
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this);
mPurges.emplace(predicate);
if(!mIterating)
{
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
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;
}
IterationGuard guard{*this};
applyPurges(ptr);
}
mSpellsChanged = true;
}
void ActiveSpells::purge(int casterActorId)
bool ActiveSpells::applyPurges(const MWWorld::Ptr& ptr, std::list<ActiveSpellParams>::iterator* currentSpell, std::vector<ActiveEffect>::iterator* currentEffect)
{
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
bool removedCurrentSpell = false;
while(!mPurges.empty())
{
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
effectIt != it->second.mEffects.end();)
auto predicate = mPurges.front();
mPurges.pop();
for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
{
if (it->second.mCasterActorId == casterActorId)
effectIt = it->second.mEffects.erase(effectIt);
else
++effectIt;
bool isCurrentSpell = currentSpell && *currentSpell == spellIt;
std::visit([&] (auto&& variant)
{
using T = std::decay_t<decltype(variant)>;
if constexpr (std::is_same_v<T, ParamsPredicate>)
{
if(variant(*spellIt))
{
auto params = *spellIt;
spellIt = mSpells.erase(spellIt);
if(isCurrentSpell)
{
*currentSpell = spellIt;
removedCurrentSpell = true;
}
for(const auto& effect : params.mEffects)
onMagicEffectRemoved(ptr, params, effect);
}
else
++spellIt;
}
else
{
static_assert(std::is_same_v<T, EffectPredicate>, "Non-exhaustive visitor");
for(auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();)
{
if(variant(*spellIt, *effectIt))
{
auto effect = *effectIt;
if(isCurrentSpell && currentEffect)
{
auto distance = std::distance(spellIt->mEffects.begin(), *currentEffect);
if(effectIt <= *currentEffect)
distance--;
effectIt = spellIt->mEffects.erase(effectIt);
*currentEffect = spellIt->mEffects.begin() + distance;
}
else
effectIt = spellIt->mEffects.erase(effectIt);
onMagicEffectRemoved(ptr, *spellIt, effect);
}
else
++effectIt;
}
++spellIt;
}
}, predicate);
}
}
mSpellsChanged = true;
return removedCurrentSpell;
}
void ActiveSpells::purgeCorprusDisease()
void ActiveSpells::removeEffects(const MWWorld::Ptr& ptr, const std::string &id)
{
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
purge([=] (const ActiveSpellParams& params)
{
bool hasCorprusEffect = false;
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;
}
return params.mId == id;
}, ptr);
}
void ActiveSpells::clear()
void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, short effectId)
{
mSpells.clear();
mSpellsChanged = true;
purge([=] (const ActiveSpellParams&, const ESM::ActiveEffect& effect)
{
return effect.mEffectId == effectId;
}, ptr);
}
void ActiveSpells::writeState(ESM::ActiveSpells &state) const
void ActiveSpells::purge(const MWWorld::Ptr& ptr, int casterActorId)
{
for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it)
purge([=] (const ActiveSpellParams& params)
{
// Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp
ESM::ActiveSpells::ActiveSpellParams params;
params.mEffects = it->second.mEffects;
params.mCasterActorId = it->second.mCasterActorId;
params.mDisplayName = it->second.mDisplayName;
return params.mCasterActorId == casterActorId;
}, ptr);
}
state.mSpells.insert (std::make_pair(it->first, params));
}
void ActiveSpells::clear(const MWWorld::Ptr& ptr)
{
mQueue.clear();
purge([] (const ActiveSpellParams& params) { return true; }, ptr);
}
void ActiveSpells::readState(const ESM::ActiveSpells &state)
void ActiveSpells::skipWorsenings(double hours)
{
for (ESM::ActiveSpells::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it)
for(auto& spell : mSpells)
{
// Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp
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;
if(spell.mWorsenings >= 0)
spell.mNextWorsening += hours;
}
}
void ActiveSpells::writeState(ESM::ActiveSpells &state) const
{
for(const auto& spell : mSpells)
state.mSpells.emplace_back(spell.toEsm());
for(const auto& spell : mQueue)
state.mQueue.emplace_back(spell.toEsm());
}
void ActiveSpells::readState(const ESM::ActiveSpells &state)
{
for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mSpells)
mSpells.emplace_back(ActiveSpellParams{spell});
for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue)
mQueue.emplace_back(ActiveSpellParams{spell});
}
}

@ -1,40 +1,83 @@
#ifndef GAME_MWMECHANICS_ACTIVESPELLS_H
#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;
ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params);
ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances = false);
ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor);
ESM::ActiveSpells::ActiveSpellParams toEsm() const;
friend class ActiveSpells;
public:
ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster);
const std::string& getId() const { return mId; }
const std::vector<ActiveEffect>& getEffects() const { return mEffects; }
std::vector<ActiveEffect>& getEffects() { return mEffects; }
// The caster that inflicted this spell on us
int mCasterActorId;
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);
/// Remove all active effects with this effect id and source id
void purgeEffect (short effectId, const std::string& sourceId, int effectIndex=-1);
void purgeEffect (const MWWorld::Ptr& ptr, short effectId);
/// Remove all active effects, if roll succeeds (for each effect)
void purgeAll(float chance, bool spellOnly = false);
void purge(EffectPredicate predicate, const MWWorld::Ptr& ptr);
void purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr);
/// 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…
Cancel
Save