Add OpenMW commits up to 11 Jun 2020

# Conflicts:
#	apps/openmw/mwbase/world.hpp
#	apps/openmw/mwgui/jailscreen.cpp
#	apps/openmw/mwmechanics/activespells.cpp
#	apps/openmw/mwmechanics/aiactivate.cpp
#	apps/openmw/mwmechanics/aiactivate.hpp
#	apps/openmw/mwmechanics/creaturestats.cpp
#	apps/openmw/mwscript/aiextensions.cpp
#	apps/openmw/mwscript/statsextensions.cpp
#	apps/openmw/mwworld/worldimp.cpp
#	apps/openmw/mwworld/worldimp.hpp
pull/593/head
David Cernat 5 years ago
commit e5b1843089

@ -4,6 +4,9 @@
Bug #1952: Incorrect particle lighting
Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs
Bug #3676: NiParticleColorModifier isn't applied properly
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
Bug #4021: Attributes and skills are not stored as floats
Bug #4623: Corprus implementation is incorrect
Bug #4774: Guards are ignorant of an invisible player that tries to attack them
Bug #5108: Savegame bloating due to inefficient fog textures format
Bug #5165: Active spells should use real time intead of timestamps
@ -15,6 +18,7 @@
Bug #5370: Opening an unlocked but trapped door uses the key
Bug #5397: NPC greeting does not reset if you leave + reenter area
Bug #5400: Editor: Verifier checks race of non-skin bodyparts
Bug #5403: Enchantment effect doesn't show on an enemy during death animation
Bug #5415: Environment maps in ebony cuirass and HiRez Armors Indoril cuirass don't work
Bug #5416: Junk non-node records before the root node are not handled gracefully
Bug #5424: Creatures do not headtrack player
@ -22,7 +26,10 @@
Bug #5427: GetDistance unknown ID error is misleading
Bug #5435: Enemies can't hurt the player when collision is off
Bug #5441: Enemies can't push a player character when in critical strike stance
Bug #5451: Magic projectiles don't disappear with the caster
Bug #5452: Autowalk is being included in savegames
Feature #5362: Show the soul gems' trapped soul in count dialog
Feature #5445: Handle NiLines
0.46.0
------
@ -294,7 +301,7 @@
Feature #5147: Show spell magicka cost in spell buying window
Feature #5170: Editor: Land shape editing, land selection
Feature #5172: Editor: Delete instances/references with keypress in scene window
Feature #5193: Weapon sheathing
Feature #5193: Shields sheathing
Feature #5201: Editor: Show tool outline in scene view, when using editmodes
Feature #5219: Impelement TestCells console command
Feature #5224: Handle NiKeyframeController for NiTriShape

@ -13,7 +13,16 @@ MISSINGTOOLS=0
command -v 7z >/dev/null 2>&1 || { echo "Error: 7z (7zip) is not on the path."; MISSINGTOOLS=1; }
command -v cmake >/dev/null 2>&1 || { echo "Error: cmake (CMake) is not on the path."; MISSINGTOOLS=1; }
command -v python >/dev/null 2>&1 || { echo "Warning: Python is not on the path, automatic Qt installation impossible."; }
MISSINGPYTHON=0
if ! command -v python >/dev/null 2>&1; then
echo "Warning: Python is not on the path, automatic Qt installation impossible."
MISSINGPYTHON=1
elif ! python --version >/dev/null 2>&1; then
echo "Warning: Python is (probably) fake stub Python that comes bundled with newer versions of Windows, automatic Qt installation impossible."
echo "If you think you have Python installed, try changing the order of your PATH environment variable in Advanced System Settings."
MISSINGPYTHON=1
fi
if [ $MISSINGTOOLS -ne 0 ]; then
wrappedExit 1
@ -745,6 +754,11 @@ fi
if [ -d 'Qt/5.15.0' ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
if [ $MISSINGPYTHON -ne 0 ]; then
echo "Can't be automatically installed without Python."
wrappedExit 1
fi
pushd "$DEPS" > /dev/null
if ! [ -d 'aqt-venv' ]; then
echo " Creating Virtualenv for aqt..."
@ -988,34 +1002,36 @@ RET=$?
if [ -z $VERBOSE ]; then
if [ $RET -eq 0 ]; then
echo Done.
if [ -n $ACTIVATE_MSVC ]; then
echo
echo "Note: you must manually activate MSVC for the shell in which you want to do the build."
echo
echo "Some scripts have been created in the build directory to do so in an existing shell."
echo "Bash: source activate_msvc.sh"
echo "CMD: ActivateMSVC.bat"
echo "PowerShell: ActivateMSVC.ps1"
echo
echo "You may find options to launch a Development/Native Tools/Cross Tools shell in your start menu or Visual Studio."
echo
if [ $(uname -m) == 'x86_64' ]; then
if [ $BITS -eq 64 ]; then
inheritEnvironments=msvc_x64_x64
else
inheritEnvironments=msvc_x64
fi
else
if [ $BITS -eq 64 ]; then
inheritEnvironments=msvc_x86_x64
else
inheritEnvironments=msvc_x86
fi
fi
echo "In Visual Studio 15.3 (2017 Update 3) or later, try setting '\"inheritEnvironments\": [ \"$inheritEnvironments\" ]' in CMakeSettings.json to build in the IDE."
fi
else
echo Failed.
fi
fi
if [ -n $ACTIVATE_MSVC ]; then
echo
echo "Note: you must manually activate MSVC for the shell in which you want to do the build."
echo
echo "Some scripts have been created in the build directory to do so in an existing shell."
echo "Bash: source activate_msvc.sh"
echo "CMD: ActivateMSVC.bat"
echo "PowerShell: ActivateMSVC.ps1"
echo
echo "You may find options to launch a Development/Native Tools/Cross Tools shell in your start menu or Visual Studio."
echo
if [ $(uname -m) == 'x86_64' ]; then
if [ $BITS -eq 64 ]; then
inheritEnvironments=msvc_x64_x64
else
inheritEnvironments=msvc_x64
fi
else
if [ $BITS -eq 64 ]; then
inheritEnvironments=msvc_x86_x64
else
inheritEnvironments=msvc_x86
fi
fi
echo "In Visual Studio 15.3 (2017 Update 3) or later, try setting '\"inheritEnvironments\": [ \"$inheritEnvironments\" ]' in CMakeSettings.json to build in the IDE."
fi
wrappedExit $RET

@ -183,19 +183,19 @@ int list(Bsa::BSAFile& bsa, Arguments& info)
{
// List all files
const Bsa::BSAFile::FileList &files = bsa.getList();
for(unsigned int i=0; i<files.size(); i++)
for (const auto& file : files)
{
if(info.longformat)
{
// Long format
std::ios::fmtflags f(std::cout.flags());
std::cout << std::setw(50) << std::left << files[i].name;
std::cout << std::setw(8) << std::left << std::dec << files[i].fileSize;
std::cout << "@ 0x" << std::hex << files[i].offset << std::endl;
std::cout << std::setw(50) << std::left << file.name;
std::cout << std::setw(8) << std::left << std::dec << file.fileSize;
std::cout << "@ 0x" << std::hex << file.offset << std::endl;
std::cout.flags(f);
}
else
std::cout << files[i].name << std::endl;
std::cout << file.name << std::endl;
}
return 0;
@ -252,14 +252,9 @@ int extract(Bsa::BSAFile& bsa, Arguments& info)
int extractAll(Bsa::BSAFile& bsa, Arguments& info)
{
// Get the list of files present in the archive
Bsa::BSAFile::FileList list = bsa.getList();
// Iter on the list
for(Bsa::BSAFile::FileList::iterator it = list.begin(); it != list.end(); ++it) {
const char* archivePath = it->name;
std::string extractPath (archivePath);
for (const auto &file : bsa.getList())
{
std::string extractPath(file.name);
replaceAll(extractPath, "\\", "/");
// Get the target path (the path the file will be extracted to)
@ -278,7 +273,7 @@ int extractAll(Bsa::BSAFile& bsa, Arguments& info)
// Get a stream for the file to extract
// (inefficient because getFile iter on the list again)
Files::IStreamPtr data = bsa.getFile(archivePath);
Files::IStreamPtr data = bsa.getFile(file.name);
bfs::ofstream out(target, std::ios::binary);
// Write the file to disk

@ -352,12 +352,12 @@ int load(Arguments& info)
std::cout << "Author: " << esm.getAuthor() << std::endl
<< "Description: " << esm.getDesc() << std::endl
<< "File format version: " << esm.getFVer() << std::endl;
std::vector<ESM::Header::MasterData> m = esm.getGameFiles();
if (!m.empty())
std::vector<ESM::Header::MasterData> masterData = esm.getGameFiles();
if (!masterData.empty())
{
std::cout << "Masters:" << std::endl;
for(unsigned int i=0;i<m.size();i++)
std::cout << " " << m[i].name << ", " << m[i].size << " bytes" << std::endl;
for(const auto& master : masterData)
std::cout << " " << master.name << ", " << master.size << " bytes" << std::endl;
}
}
@ -369,7 +369,7 @@ int load(Arguments& info)
esm.getRecHeader(flags);
EsmTool::RecordBase *record = EsmTool::RecordBase::create(n);
if (record == 0)
if (record == nullptr)
{
if (std::find(skipped.begin(), skipped.end(), n.intval) == skipped.end())
{
@ -538,8 +538,8 @@ int comp(Arguments& info)
Arguments fileOne;
Arguments fileTwo;
fileOne.raw_given = 0;
fileTwo.raw_given = 0;
fileOne.raw_given = false;
fileTwo.raw_given = false;
fileOne.mode = "clone";
fileTwo.mode = "clone";

@ -779,7 +779,7 @@ std::string creatureListFlags(int flags)
std::string lightFlags(int flags)
{
std::string properties = "";
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags & ESM::Light::Dynamic) properties += "Dynamic ";
if (flags & ESM::Light::Fire) properties += "Fire ";

@ -9,7 +9,7 @@
namespace
{
void printAIPackage(ESM::AIPackage p)
void printAIPackage(const ESM::AIPackage& p)
{
std::cout << " AI Type: " << aiTypeLabel(p.mType)
<< " (" << Misc::StringUtils::format("0x%08X", p.mType) << ")" << std::endl;
@ -53,7 +53,7 @@ void printAIPackage(ESM::AIPackage p)
std::cout << " Cell Name: " << p.mCellName << std::endl;
}
std::string ruleString(ESM::DialInfo::SelectStruct ss)
std::string ruleString(const ESM::DialInfo::SelectStruct& ss)
{
std::string rule = ss.mSelectRule;
@ -126,7 +126,7 @@ std::string ruleString(ESM::DialInfo::SelectStruct ss)
return result;
}
void printEffectList(ESM::EffectList effects)
void printEffectList(const ESM::EffectList& effects)
{
int i = 0;
for (const ESM::ENAMstruct& effect : effects.mList)
@ -174,7 +174,7 @@ namespace EsmTool {
RecordBase *
RecordBase::create(ESM::NAME type)
{
RecordBase *record = 0;
RecordBase *record = nullptr;
switch (type.intval) {
case ESM::REC_ACTI:
@ -388,7 +388,7 @@ RecordBase::create(ESM::NAME type)
break;
}
default:
record = 0;
record = nullptr;
}
if (record) {
record->mType = type;
@ -728,10 +728,9 @@ void Record<ESM::Faction>::print()
<< " (" << mData.mData.mAttribute[0] << ")" << std::endl;
std::cout << " Attribute2: " << attributeLabel(mData.mData.mAttribute[1])
<< " (" << mData.mData.mAttribute[1] << ")" << std::endl;
for (int i = 0; i < 7; i++)
if (mData.mData.mSkills[i] != -1)
std::cout << " Skill: " << skillLabel(mData.mData.mSkills[i])
<< " (" << mData.mData.mSkills[i] << ")" << std::endl;
for (int skill : mData.mData.mSkills)
if (skill != -1)
std::cout << " Skill: " << skillLabel(skill) << " (" << skill << ")" << std::endl;
for (int i = 0; i != 10; i++)
if (!mData.mRanks[i].empty())
{

@ -74,7 +74,7 @@ namespace EsmTool
: mIsDeleted(false)
{}
std::string getId() const {
std::string getId() const override {
return mData.mId;
}
@ -82,15 +82,15 @@ namespace EsmTool
return mData;
}
void save(ESM::ESMWriter &esm) {
void save(ESM::ESMWriter &esm) override {
mData.save(esm, mIsDeleted);
}
void load(ESM::ESMReader &esm) {
void load(ESM::ESMReader &esm) override {
mData.load(esm, mIsDeleted);
}
void print();
void print() override;
};
template<> std::string Record<ESM::Cell>::getId() const;

@ -52,9 +52,7 @@ namespace
// a dynamically created record e.g. player-enchanted weapon
std::string index = indexedRefId.substr(indexedRefId.size()-8);
if(index.find_first_not_of("0123456789ABCDEF") == std::string::npos )
return true;
return false;
return index.find_first_not_of("0123456789ABCDEF") == std::string::npos;
}
void splitIndexedRefId(const std::string& indexedRefId, int& refIndex, std::string& refId)
@ -139,12 +137,12 @@ namespace ESSImport
image2->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE);
memcpy(image2->data(), &data[0], data.size());
for (std::set<std::pair<int, int> >::const_iterator it = mContext->mExploredCells.begin(); it != mContext->mExploredCells.end(); ++it)
for (const auto & exploredCell : mContext->mExploredCells)
{
if (it->first > mContext->mGlobalMapState.mBounds.mMaxX
|| it->first < mContext->mGlobalMapState.mBounds.mMinX
|| it->second > mContext->mGlobalMapState.mBounds.mMaxY
|| it->second < mContext->mGlobalMapState.mBounds.mMinY)
if (exploredCell.first > mContext->mGlobalMapState.mBounds.mMaxX
|| exploredCell.first < mContext->mGlobalMapState.mBounds.mMinX
|| exploredCell.second > mContext->mGlobalMapState.mBounds.mMaxY
|| exploredCell.second < mContext->mGlobalMapState.mBounds.mMinY)
{
// out of bounds, I think this could happen, since the original engine had a fixed-size map
continue;
@ -152,12 +150,12 @@ namespace ESSImport
int imageLeftSrc = mGlobalMapImage->s()/2;
int imageTopSrc = mGlobalMapImage->t()/2;
imageLeftSrc += it->first * cellSize;
imageTopSrc -= it->second * cellSize;
imageLeftSrc += exploredCell.first * cellSize;
imageTopSrc -= exploredCell.second * cellSize;
int imageLeftDst = width/2;
int imageTopDst = height/2;
imageLeftDst += it->first * cellSize;
imageTopDst -= it->second * cellSize;
imageLeftDst += exploredCell.first * cellSize;
imageTopDst -= exploredCell.second * cellSize;
for (int x=0; x<cellSize; ++x)
for (int y=0; y<cellSize; ++y)
{
@ -329,9 +327,8 @@ namespace ESSImport
csta.mWaterLevel = esmcell.mWater;
csta.save(esm);
for (std::vector<CellRef>::const_iterator refIt = cell.mRefs.begin(); refIt != cell.mRefs.end(); ++refIt)
for (const auto & cellref : cell.mRefs)
{
const CellRef& cellref = *refIt;
ESM::CellRef out (cellref);
// TODO: use mContext->mCreatures/mNpcs
@ -437,16 +434,16 @@ namespace ESSImport
void ConvertCell::write(ESM::ESMWriter &esm)
{
for (std::map<std::string, Cell>::const_iterator it = mIntCells.begin(); it != mIntCells.end(); ++it)
writeCell(it->second, esm);
for (const auto & cell : mIntCells)
writeCell(cell.second, esm);
for (std::map<std::pair<int, int>, Cell>::const_iterator it = mExtCells.begin(); it != mExtCells.end(); ++it)
writeCell(it->second, esm);
for (const auto & cell : mExtCells)
writeCell(cell.second, esm);
for (std::vector<ESM::CustomMarker>::const_iterator it = mMarkers.begin(); it != mMarkers.end(); ++it)
for (const auto & marker : mMarkers)
{
esm.startRecord(ESM::REC_MARK);
it->save(esm);
marker.save(esm);
esm.endRecord(ESM::REC_MARK);
}
}

@ -79,9 +79,9 @@ template <typename T>
class DefaultConverter : public Converter
{
public:
virtual int getStage() { return 0; }
int getStage() override { return 0; }
virtual void read(ESM::ESMReader& esm)
void read(ESM::ESMReader& esm) override
{
T record;
bool isDeleted = false;
@ -90,7 +90,7 @@ public:
mRecords[record.mId] = record;
}
virtual void write(ESM::ESMWriter& esm)
void write(ESM::ESMWriter& esm) override
{
for (typename std::map<std::string, T>::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it)
{
@ -107,7 +107,7 @@ protected:
class ConvertNPC : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
void read(ESM::ESMReader &esm) override
{
ESM::NPC npc;
bool isDeleted = false;
@ -127,8 +127,8 @@ public:
ESM::SpellState::SpellParams empty;
// FIXME: player start spells and birthsign spells aren't listed here,
// need to fix openmw to account for this
for (std::vector<std::string>::const_iterator it = npc.mSpells.mList.begin(); it != npc.mSpells.mList.end(); ++it)
mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[*it] = empty;
for (const auto & spell : npc.mSpells.mList)
mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[spell] = empty;
// Clear the list now that we've written it, this prevents issues cropping up with
// ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal.
@ -144,7 +144,7 @@ public:
class ConvertCREA : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
void read(ESM::ESMReader &esm) override
{
// See comment in ConvertNPC
ESM::Creature creature;
@ -162,7 +162,7 @@ public:
class ConvertGlobal : public DefaultConverter<ESM::Global>
{
public:
virtual void read(ESM::ESMReader &esm)
void read(ESM::ESMReader &esm) override
{
ESM::Global global;
bool isDeleted = false;
@ -183,7 +183,7 @@ public:
class ConvertClass : public DefaultConverter<ESM::Class>
{
public:
virtual void read(ESM::ESMReader &esm)
void read(ESM::ESMReader &esm) override
{
ESM::Class class_;
bool isDeleted = false;
@ -199,7 +199,7 @@ public:
class ConvertBook : public DefaultConverter<ESM::Book>
{
public:
virtual void read(ESM::ESMReader &esm)
void read(ESM::ESMReader &esm) override
{
ESM::Book book;
bool isDeleted = false;
@ -215,7 +215,7 @@ public:
class ConvertNPCC : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
void read(ESM::ESMReader &esm) override
{
std::string id = esm.getHNString("NAME");
NPCC npcc;
@ -235,7 +235,7 @@ public:
class ConvertREFR : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
void read(ESM::ESMReader &esm) override
{
REFR refr;
refr.load(esm);
@ -261,7 +261,7 @@ public:
}
}
}
virtual void write(ESM::ESMWriter& esm)
void write(ESM::ESMWriter& esm) override
{
esm.startRecord(ESM::REC_ASPL);
esm.writeHNString("ID__", mSelectedSpell);
@ -280,14 +280,14 @@ public:
mLevitationEnabled(true)
{}
virtual void read(ESM::ESMReader &esm)
void read(ESM::ESMReader &esm) override
{
PCDT pcdt;
pcdt.load(esm);
convertPCDT(pcdt, mContext->mPlayer, mContext->mDialogueState.mKnownTopics, mFirstPersonCam, mTeleportingEnabled, mLevitationEnabled, mContext->mControlsState);
}
virtual void write(ESM::ESMWriter &esm)
void write(ESM::ESMWriter &esm) override
{
esm.startRecord(ESM::REC_ENAB);
esm.writeHNT("TELE", mTeleportingEnabled);
@ -306,7 +306,7 @@ private:
class ConvertCNTC : public Converter
{
virtual void read(ESM::ESMReader &esm)
void read(ESM::ESMReader &esm) override
{
std::string id = esm.getHNString("NAME");
CNTC cntc;
@ -318,7 +318,7 @@ class ConvertCNTC : public Converter
class ConvertCREC : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
void read(ESM::ESMReader &esm) override
{
std::string id = esm.getHNString("NAME");
CREC crec;
@ -330,8 +330,8 @@ public:
class ConvertFMAP : public Converter
{
public:
virtual void read(ESM::ESMReader &esm);
virtual void write(ESM::ESMWriter &esm);
void read(ESM::ESMReader &esm) override;
void write(ESM::ESMWriter &esm) override;
private:
osg::ref_ptr<osg::Image> mGlobalMapImage;
@ -340,8 +340,8 @@ private:
class ConvertCell : public Converter
{
public:
virtual void read(ESM::ESMReader& esm);
virtual void write(ESM::ESMWriter& esm);
void read(ESM::ESMReader& esm) override;
void write(ESM::ESMWriter& esm) override;
private:
struct Cell
@ -362,7 +362,7 @@ private:
class ConvertKLST : public Converter
{
public:
virtual void read(ESM::ESMReader& esm)
void read(ESM::ESMReader& esm) override
{
KLST klst;
klst.load(esm);
@ -371,7 +371,7 @@ public:
mContext->mPlayer.mObject.mNpcStats.mWerewolfKills = klst.mWerewolfKills;
}
virtual void write(ESM::ESMWriter &esm)
void write(ESM::ESMWriter &esm) override
{
esm.startRecord(ESM::REC_DCOU);
for (std::map<std::string, int>::const_iterator it = mKillCounter.begin(); it != mKillCounter.end(); ++it)
@ -389,7 +389,7 @@ private:
class ConvertFACT : public Converter
{
public:
virtual void read(ESM::ESMReader& esm)
void read(ESM::ESMReader& esm) override
{
ESM::Faction faction;
bool isDeleted = false;
@ -409,7 +409,7 @@ public:
class ConvertSTLN : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
void read(ESM::ESMReader &esm) override
{
std::string itemid = esm.getHNString("NAME");
Misc::StringUtils::lowerCaseInPlace(itemid);
@ -428,15 +428,15 @@ public:
}
}
}
virtual void write(ESM::ESMWriter &esm)
void write(ESM::ESMWriter &esm) override
{
ESM::StolenItems items;
for (std::map<std::string, std::set<Owner> >::const_iterator it = mStolenItems.begin(); it != mStolenItems.end(); ++it)
{
std::map<std::pair<std::string, bool>, int> owners;
for (std::set<Owner>::const_iterator ownerIt = it->second.begin(); ownerIt != it->second.end(); ++ownerIt)
for (const auto & ownerIt : it->second)
{
owners.insert(std::make_pair(std::make_pair(ownerIt->first, ownerIt->second)
owners.insert(std::make_pair(std::make_pair(ownerIt.first, ownerIt.second)
// Since OpenMW doesn't suffer from the owner contamination bug,
// it needs a count argument. But for legacy savegames, we don't know
// this count, so must assume all items of that ID are stolen,
@ -467,7 +467,7 @@ private:
class ConvertINFO : public Converter
{
public:
virtual void read(ESM::ESMReader& esm)
void read(ESM::ESMReader& esm) override
{
INFO info;
info.load(esm);
@ -477,7 +477,7 @@ public:
class ConvertDIAL : public Converter
{
public:
virtual void read(ESM::ESMReader& esm)
void read(ESM::ESMReader& esm) override
{
std::string id = esm.getHNString("NAME");
DIAL dial;
@ -485,7 +485,7 @@ public:
if (dial.mIndex > 0)
mDials[id] = dial;
}
virtual void write(ESM::ESMWriter &esm)
void write(ESM::ESMWriter &esm) override
{
for (std::map<std::string, DIAL>::const_iterator it = mDials.begin(); it != mDials.end(); ++it)
{
@ -505,7 +505,7 @@ private:
class ConvertQUES : public Converter
{
public:
virtual void read(ESM::ESMReader& esm)
void read(ESM::ESMReader& esm) override
{
std::string id = esm.getHNString("NAME");
QUES quest;
@ -516,7 +516,7 @@ public:
class ConvertJOUR : public Converter
{
public:
virtual void read(ESM::ESMReader& esm)
void read(ESM::ESMReader& esm) override
{
JOUR journal;
journal.load(esm);
@ -531,7 +531,7 @@ public:
{
}
virtual void read(ESM::ESMReader &esm)
void read(ESM::ESMReader &esm) override
{
mGame.load(esm);
mHasGame = true;
@ -551,7 +551,7 @@ public:
}
}
virtual void write(ESM::ESMWriter &esm)
void write(ESM::ESMWriter &esm) override
{
if (!mHasGame)
return;
@ -578,7 +578,7 @@ private:
class ConvertSCPT : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
void read(ESM::ESMReader &esm) override
{
SCPT script;
script.load(esm);
@ -586,12 +586,12 @@ public:
convertSCPT(script, out);
mScripts.push_back(out);
}
virtual void write(ESM::ESMWriter &esm)
void write(ESM::ESMWriter &esm) override
{
for (std::vector<ESM::GlobalScript>::const_iterator it = mScripts.begin(); it != mScripts.end(); ++it)
for (const auto & script : mScripts)
{
esm.startRecord(ESM::REC_GSCR);
it->save(esm);
script.save(esm);
esm.endRecord(ESM::REC_GSCR);
}
}
@ -603,9 +603,9 @@ private:
class ConvertPROJ : public Converter
{
public:
virtual int getStage() override { return 2; }
virtual void read(ESM::ESMReader& esm) override;
virtual void write(ESM::ESMWriter& esm) override;
int getStage() override { return 2; }
void read(ESM::ESMReader& esm) override;
void write(ESM::ESMWriter& esm) override;
private:
void convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam);
PROJ mProj;
@ -614,8 +614,8 @@ private:
class ConvertSPLM : public Converter
{
public:
virtual void read(ESM::ESMReader& esm) override;
virtual void write(ESM::ESMWriter& esm) override;
void read(ESM::ESMReader& esm) override;
void write(ESM::ESMWriter& esm) override;
private:
SPLM mSPLM;
};

@ -9,21 +9,20 @@ namespace ESSImport
void convertInventory(const Inventory &inventory, ESM::InventoryState &state)
{
int index = 0;
for (std::vector<Inventory::InventoryItem>::const_iterator it = inventory.mItems.begin();
it != inventory.mItems.end(); ++it)
for (const auto & item : inventory.mItems)
{
ESM::ObjectState objstate;
objstate.blank();
objstate.mRef = *it;
objstate.mRef.mRefID = Misc::StringUtils::lowerCase(it->mId);
objstate.mCount = std::abs(it->mCount); // restocking items have negative count in the savefile
objstate.mRef = item;
objstate.mRef.mRefID = Misc::StringUtils::lowerCase(item.mId);
objstate.mCount = std::abs(item.mCount); // restocking items have negative count in the savefile
// openmw handles them differently, so no need to set any flags
state.mItems.push_back(objstate);
if (it->mRelativeEquipmentSlot != -1)
if (item.mRelativeEquipmentSlot != -1)
// Note we should really write the absolute slot here, which we do not know about
// Not a big deal, OpenMW will auto-correct to a valid slot, the only problem is when
// an item could be equipped in two different slots (e.g. equipped two rings)
state.mEquipmentSlots[index] = it->mRelativeEquipmentSlot;
state.mEquipmentSlots[index] = item.mRelativeEquipmentSlot;
++index;
}
}

@ -10,13 +10,13 @@ namespace ESSImport
{
out.mBirthsign = pcdt.mBirthsign;
out.mObject.mNpcStats.mBounty = pcdt.mBounty;
for (std::vector<PCDT::FNAM>::const_iterator it = pcdt.mFactions.begin(); it != pcdt.mFactions.end(); ++it)
for (const auto & essFaction : pcdt.mFactions)
{
ESM::NpcStats::Faction faction;
faction.mExpelled = (it->mFlags & 0x2) != 0;
faction.mRank = it->mRank;
faction.mReputation = it->mReputation;
out.mObject.mNpcStats.mFactions[Misc::StringUtils::lowerCase(it->mFactionName.toString())] = faction;
faction.mExpelled = (essFaction.mFlags & 0x2) != 0;
faction.mRank = essFaction.mRank;
faction.mReputation = essFaction.mReputation;
out.mObject.mNpcStats.mFactions[Misc::StringUtils::lowerCase(essFaction.mFactionName.toString())] = faction;
}
for (int i=0; i<3; ++i)
out.mObject.mNpcStats.mSpecIncreases[i] = pcdt.mPNAM.mSpecIncreases[i];
@ -35,10 +35,9 @@ namespace ESSImport
teleportingEnabled = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_TeleportingDisabled);
levitationEnabled = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_LevitationDisabled);
for (std::vector<std::string>::const_iterator it = pcdt.mKnownDialogueTopics.begin();
it != pcdt.mKnownDialogueTopics.end(); ++it)
for (const auto & knownDialogueTopic : pcdt.mKnownDialogueTopics)
{
outDialogueTopics.push_back(Misc::StringUtils::lowerCase(*it));
outDialogueTopics.push_back(Misc::StringUtils::lowerCase(knownDialogueTopic));
}
controls.mViewSwitchDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_ViewSwitchDisabled;

@ -1,18 +1,16 @@
#include "convertscri.hpp"
#include <iostream>
namespace
{
template <typename T, ESM::VarType VariantType>
void storeVariables(const std::vector<T>& variables, ESM::Locals& locals, const std::string& scriptname)
{
for (typename std::vector<T>::const_iterator it = variables.begin(); it != variables.end(); ++it)
for (const auto& variable : variables)
{
ESM::Variant val(*it);
ESM::Variant val(variable);
val.setType(VariantType);
locals.mVariables.push_back(std::make_pair(std::string(), val));
locals.mVariables.emplace_back(std::string(), val);
}
}

@ -86,7 +86,9 @@ namespace ESSImport
bool mHasANIS;
ANIS mANIS; // scripted animation state
void load(ESM::ESMReader& esm);
virtual void load(ESM::ESMReader& esm);
virtual ~ActorData() = default;
};
}

@ -25,7 +25,9 @@ namespace ESSImport
bool mDeleted;
void load(ESM::ESMReader& esm);
void load(ESM::ESMReader& esm) override;
virtual ~CellRef() = default;
};
}

@ -16,15 +16,12 @@
#include <components/esm/player.hpp>
#include <components/esm/loadalch.hpp>
#include <components/esm/loadclas.hpp>
#include <components/esm/loadspel.hpp>
#include <components/esm/loadarmo.hpp>
#include <components/esm/loadweap.hpp>
#include <components/esm/loadclot.hpp>
#include <components/esm/loadench.hpp>
#include <components/esm/loadweap.hpp>
#include <components/esm/loadlevlist.hpp>
#include <components/esm/loadglob.hpp>
#include <components/misc/constants.hpp>
@ -49,7 +46,7 @@ namespace
image->allocateImage(128, 128, 1, GL_RGB, GL_UNSIGNED_BYTE);
// need to convert pixel format from BGRA to RGB as the jpg readerwriter doesn't support it otherwise
std::vector<unsigned char>::const_iterator it = fileHeader.mSCRS.begin();
auto it = fileHeader.mSCRS.begin();
for (int y=0; y<128; ++y)
{
for (int x=0; x<128; ++x)
@ -317,10 +314,9 @@ namespace ESSImport
std::set<unsigned int> unknownRecords;
for (std::map<unsigned int, std::shared_ptr<Converter> >::const_iterator it = converters.begin();
it != converters.end(); ++it)
for (const auto & converter : converters)
{
it->second->setContext(context);
converter.second->setContext(context);
}
while (esm.hasMoreRecs())
@ -328,7 +324,7 @@ namespace ESSImport
ESM::NAME n = esm.getRecName();
esm.getRecHeader();
std::map<unsigned int, std::shared_ptr<Converter> >::iterator it = converters.find(n.intval);
auto it = converters.find(n.intval);
if (it != converters.end())
{
it->second->read(esm);
@ -358,17 +354,15 @@ namespace ESSImport
writer.setDescription("");
writer.setRecordCount (0);
for (std::vector<ESM::Header::MasterData>::const_iterator it = header.mMaster.begin();
it != header.mMaster.end(); ++it)
writer.addMaster (it->name, 0); // not using the size information anyway -> use value of 0
for (const auto & master : header.mMaster)
writer.addMaster(master.name, 0); // not using the size information anyway -> use value of 0
writer.save (stream);
ESM::SavedGame profile;
for (std::vector<ESM::Header::MasterData>::const_iterator it = header.mMaster.begin();
it != header.mMaster.end(); ++it)
for (const auto & master : header.mMaster)
{
profile.mContentFiles.push_back(it->name);
profile.mContentFiles.push_back(master.name);
}
profile.mDescription = esm.getDesc();
profile.mInGameTime.mDay = context.mDay;

@ -63,7 +63,6 @@ namespace ESSImport
, mHour(0.f)
, mNextActorId(0)
{
mPlayer.mAutoMove = 0;
ESM::CellId playerCellId;
playerCellId.mPaged = true;
playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0;

@ -4,8 +4,6 @@
#include <components/esm/esmreader.hpp>
#include <components/esm/loadcont.hpp>
namespace ESSImport
{

@ -15,17 +15,13 @@ set(GAME_HEADER
engine.hpp
)
if (BULLET_USE_DOUBLES)
add_definitions(-DBT_USE_DOUBLE_PRECISION)
endif()
source_group(game FILES ${GAME} ${GAME_HEADER})
add_openmw_dir (mwrender
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
renderbin actoranimation landmanager navmesh actorspaths recastmesh
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager
)
add_openmw_dir (mwinput
@ -71,7 +67,7 @@ add_openmw_dir (mwworld
actionequip timestamp actionalchemy cellstore actionapply actioneat
store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor
contentloader esmloader actiontrap cellreflist cellref physicssystem weather projectilemanager
cellpreloader
cellpreloader datetimemanager
)
add_openmw_dir (mwphysics

@ -311,6 +311,8 @@ 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;
};
}

@ -36,6 +36,7 @@ namespace ESM
struct Position;
struct Cell;
struct Class;
struct Creature;
struct Potion;
struct Spell;
struct NPC;
@ -47,7 +48,7 @@ namespace ESM
struct EffectList;
struct CreatureLevList;
struct ItemLevList;
struct Creature;
struct TimeStamp;
}
namespace MWRender
@ -234,54 +235,14 @@ namespace MWBase
virtual void advanceTime (double hours, bool incremental = false) = 0;
///< Advance in-game time.
virtual void setHour (double hour) = 0;
///< Set in-game time hour.
virtual void setMonth (int month) = 0;
///< Set in-game time month.
virtual void setDay (int day) = 0;
///< Set in-game time day.
/*
Start of tes3mp addition
Make it possible to set the year from elsewhere
*/
virtual void setYear(int year) = 0;
/*
End of tes3mp addition
*/
/*
Start of tes3mp addition
Make it possible to set the number of days passed from elsewhere
*/
virtual void setDaysPassed(int daysPassed) = 0;
/*
End of tes3mp addition
*/
/*
Start of tes3mp addition
Make it possible to set a custom timeScale from elsewhere
*/
virtual void setTimeScale(float timeScale) = 0;
/*
End of tes3mp addition
*/
virtual int getDay() const = 0;
virtual int getMonth() const = 0;
virtual int getYear() const = 0;
virtual std::string getMonthName (int month = -1) const = 0;
///< Return name of month (-1: current month)
virtual MWWorld::TimeStamp getTimeStamp() const = 0;
///< Return current in-game time stamp.
///< Return current in-game time and number of day since new game start.
virtual ESM::EpochTimeStamp getEpochTimeStamp() const = 0;
///< Return current in-game date and time.
virtual bool toggleSky() = 0;
///< \return Resulting mode
@ -530,6 +491,14 @@ namespace MWBase
///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID.
/// \return pointer to created record
virtual const ESM::Creature *createOverrideRecord (const ESM::Creature& record) = 0;
///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID.
/// \return pointer to created record
virtual const ESM::NPC *createOverrideRecord (const ESM::NPC& record) = 0;
///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID.
/// \return pointer to created record
virtual void update (float duration, bool paused) = 0;
virtual void updatePhysics (float duration, bool paused) = 0;

@ -322,7 +322,7 @@ namespace MWClass
const MWWorld::LiveCellRef<ESM::Armor> *ref = ptr.get<ESM::Armor>();
int armorSkillType = getEquipmentSkill(ptr);
int armorSkill = actor.getClass().getSkill(actor, armorSkillType);
float armorSkill = actor.getClass().getSkill(actor, armorSkillType);
const MWBase::World *world = MWBase::Environment::get().getWorld();
int iBaseArmorSkill = world->getStore().get<ESM::GameSetting>().find("iBaseArmorSkill")->mValue.getInteger();

@ -793,7 +793,7 @@ namespace MWClass
float Creature::getCapacity (const MWWorld::Ptr& ptr) const
{
const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
return static_cast<float>(stats.getAttribute(ESM::Attribute::Strength).getModified() * 5);
return stats.getAttribute(ESM::Attribute::Strength).getModified() * 5;
}
int Creature::getServices(const MWWorld::ConstPtr &actor) const
@ -933,7 +933,7 @@ namespace MWClass
throw std::runtime_error(std::string("Unexpected soundgen type: ")+name);
}
int Creature::getSkill(const MWWorld::Ptr &ptr, int skill) const
float Creature::getSkill(const MWWorld::Ptr &ptr, int skill) const
{
MWWorld::LiveCellRef<ESM::Creature> *ref =
ptr.get<ESM::Creature>();
@ -997,6 +997,12 @@ namespace MWClass
return;
}
if (ptr.getRefData().getCount() <= 0)
{
state.mHasCustomState = false;
return;
}
const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
ESM::CreatureState& creatureState = state.asCreatureState();
customData.mContainerStore->writeState (creatureState.mInventory);
@ -1066,4 +1072,9 @@ namespace MWClass
const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
scale *= ref->mBase->mScale;
}
void Creature::setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const
{
MWMechanics::setBaseAISetting<ESM::Creature>(id, setting, value);
}
}

@ -118,7 +118,7 @@ namespace MWClass
virtual bool canSwim (const MWWorld::ConstPtr &ptr) const;
virtual bool canWalk (const MWWorld::ConstPtr &ptr) const;
virtual int getSkill(const MWWorld::Ptr &ptr, int skill) const;
virtual float getSkill(const MWWorld::Ptr &ptr, int skill) const;
/// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini)
virtual int getBloodTexture (const MWWorld::ConstPtr& ptr) const;
@ -139,6 +139,8 @@ namespace MWClass
virtual void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const;
/// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh
virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const;
};
}

@ -158,7 +158,7 @@ namespace MWClass
}
MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
int alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy);
float alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy);
static const float fWortChanceValue =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fWortChanceValue")->mValue.getFloat();

@ -144,8 +144,8 @@ namespace
}
// initial health
int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase();
int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase();
float strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase();
float endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase();
int multiplier = 3;
@ -1234,7 +1234,7 @@ namespace MWClass
gmst.fJumpEncumbranceMultiplier->mValue.getFloat() *
(1.0f - Npc::getNormalizedEncumbrance(ptr));
float a = static_cast<float>(getSkill(ptr, ESM::Skill::Acrobatics));
float a = getSkill(ptr, ESM::Skill::Acrobatics);
float b = 0.0f;
if(a > 50.0f)
{
@ -1359,7 +1359,7 @@ namespace MWClass
float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat();
float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat();
int unarmoredSkill = getSkill(ptr, ESM::Skill::Unarmored);
float unarmoredSkill = getSkill(ptr, ESM::Skill::Unarmored);
float ratings[MWWorld::InventoryStore::Slots];
for(int i = 0;i < MWWorld::InventoryStore::Slots;i++)
@ -1506,7 +1506,7 @@ namespace MWClass
return MWWorld::Ptr(cell.insert(ref), &cell);
}
int Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const
float Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const
{
return getNpcStats(ptr).getSkill(skill).getModified();
}
@ -1550,6 +1550,12 @@ namespace MWClass
return;
}
if (ptr.getRefData().getCount() <= 0)
{
state.mHasCustomState = false;
return;
}
const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
ESM::NpcState& npcState = state.asNpcState();
customData.mInventoryStore.writeState (npcState.mInventory);
@ -1660,4 +1666,9 @@ namespace MWClass
const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
return ref->mBase->getFactionRank();
}
void Npc::setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const
{
MWMechanics::setBaseAISetting<ESM::NPC>(id, setting, value);
}
}

@ -139,7 +139,7 @@ namespace MWClass
virtual std::string getModel(const MWWorld::ConstPtr &ptr) const;
virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const;
virtual float getSkill(const MWWorld::Ptr& ptr, int skill) const;
/// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini)
virtual int getBloodTexture (const MWWorld::ConstPtr& ptr) const;
@ -174,6 +174,8 @@ namespace MWClass
virtual std::string getPrimaryFaction(const MWWorld::ConstPtr &ptr) const;
virtual int getPrimaryFactionRank(const MWWorld::ConstPtr &ptr) const;
virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const;
};
}

@ -134,6 +134,12 @@ namespace MWGui
End of tes3mp change (major)
*/
// We should not worsen corprus when in prison
for (auto& spell : player.getClass().getCreatureStats(player).getCorprusSpells())
{
spell.second.mNextWorsening += mDays * 24;
}
std::set<int> skills;
for (int day=0; day<mDays; ++day)
{
@ -148,14 +154,14 @@ namespace MWGui
Disable increases for Security and Sneak when using ignoreJailSkillIncreases
*/
if (localPlayer->ignoreJailSkillIncreases)
value.setBase(std::max(0, value.getBase()-1));
value.setBase(std::max(0.f, value.getBase()-1));
else if (skill == ESM::Skill::Security || skill == ESM::Skill::Sneak)
/*
End of tes3mp change (minor)
*/
value.setBase(std::min(100, value.getBase()+1));
value.setBase(std::min(100.f, value.getBase() + 1));
else
value.setBase(std::max(0, value.getBase()-1));
value.setBase(std::max(0.f, value.getBase()-1));
}
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();

@ -157,7 +157,7 @@ namespace MWGui
mAttributeValues[i]->setEnabled(true);
availableAttributes++;
int mult = pcStats.getLevelupAttributeMultiplier (i);
float mult = pcStats.getLevelupAttributeMultiplier (i);
mult = std::min(mult, 100-pcStats.getAttribute(i).getBase());
text->setCaption(mult <= 1 ? "" : "x" + MyGUI::utility::toString(mult));
}

@ -22,7 +22,7 @@ namespace MWGui
{
MWWorld::Ptr player = MWMechanics::getPlayer();
mSourceModel = sourceModel;
int chance = player.getClass().getSkill(player, ESM::Skill::Sneak);
float chance = player.getClass().getSkill(player, ESM::Skill::Sneak);
mSourceModel->update();

@ -159,7 +159,7 @@ namespace MWGui
for (int i=0; ids[i]; ++i)
if (ids[i]==id)
{
setText (id, std::to_string(value.getModified()));
setText (id, std::to_string(static_cast<int>(value.getModified())));
MyGUI::TextBox* box;
getWidget(box, id);

@ -86,11 +86,11 @@ namespace MWGui
mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold));
// NPC can train you in his best 3 skills
std::vector< std::pair<int, int> > skills;
std::vector< std::pair<int, float> > skills;
for (int i=0; i<ESM::Skill::Length; ++i)
{
int value = actor.getClass().getSkill(actor, i);
float value = actor.getClass().getSkill(actor, i);
skills.push_back(std::make_pair(i, value));
}

@ -180,11 +180,10 @@ namespace MWGui
if (hour >= 13) hour -= 12;
if (hour == 0) hour = 12;
std::string dateTimeText =
MyGUI::utility::toString(MWBase::Environment::get().getWorld ()->getDay ()) + " "
+ month + " (#{sDay} " + MyGUI::utility::toString(MWBase::Environment::get().getWorld ()->getTimeStamp ().getDay())
+ ") " + MyGUI::utility::toString(hour) + " " + (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}");
ESM::EpochTimeStamp currentDate = MWBase::Environment::get().getWorld()->getEpochTimeStamp();
int daysPassed = MWBase::Environment::get().getWorld()->getTimeStamp().getDay();
std::string formattedHour = pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}";
std::string dateTimeText = Misc::StringUtils::format("%i %s (#{sDay} %i) %i %s", currentDate.mDay, month, daysPassed, hour, formattedHour);
mDateTimeText->setCaptionWithReplacing (dateTimeText);
}

@ -29,13 +29,23 @@ namespace MWMechanics
}
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)
{
effectIt = effects.erase(effectIt);
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
{
@ -43,7 +53,9 @@ namespace MWMechanics
++effectIt;
}
}
++iter;
if (!interrupt)
++iter;
}
}
}
@ -327,6 +339,31 @@ namespace MWMechanics
End of tes3mp addition
*/
void ActiveSpells::purgeCorprusDisease()
{
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
{
bool hasCorprusEffect = false;
for (std::vector<ActiveEffect>::iterator effectIt = iter->second.mEffects.begin();
effectIt != iter->second.mEffects.end();++effectIt)
{
if (effectIt->mEffectId == ESM::MagicEffect::Corprus)
{
hasCorprusEffect = true;
break;
}
}
if (hasCorprusEffect)
{
mSpells.erase(iter++);
mSpellsChanged = true;
}
else
++iter;
}
}
void ActiveSpells::clear()
{
mSpells.clear();

@ -119,6 +119,8 @@ namespace MWMechanics
bool isSpellActive (const std::string& id) const;
///< case insensitive
void purgeCorprusDisease();
const MagicEffects& getMagicEffects() const;
void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const;

@ -131,11 +131,11 @@ void adjustCommandedActor (const MWWorld::Ptr& actor)
bool hasCommandPackage = false;
std::list<MWMechanics::AiPackage*>::const_iterator it;
for (it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it)
auto it = stats.getAiSequence().begin();
for (; it != stats.getAiSequence().end(); ++it)
{
if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow &&
static_cast<MWMechanics::AiFollow*>(*it)->isCommanded())
static_cast<const MWMechanics::AiFollow*>(it->get())->isCommanded())
{
hasCommandPackage = true;
break;
@ -151,7 +151,7 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
const MWWorld::Store<ESM::GameSetting>& settings = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified ();
float endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified ();
health = 0.1f * endurance;
float fRestMagicMult = settings.find("fRestMagicMult")->mValue.getFloat ();
@ -195,6 +195,49 @@ namespace MWMechanics
}
};
class GetCurrentMagnitudes : public MWMechanics::EffectSourceVisitor
{
std::string mSpellId;
public:
GetCurrentMagnitudes(const std::string& spellId)
: mSpellId(spellId)
{
}
virtual void visit (MWMechanics::EffectKey key,
const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1)
{
if (magnitude <= 0)
return;
if (sourceId != mSpellId)
return;
mMagnitudes.push_back(std::make_pair(key, magnitude));
}
std::vector<std::pair<MWMechanics::EffectKey, float>> mMagnitudes;
};
class GetCorprusSpells : public MWMechanics::EffectSourceVisitor
{
public:
virtual void visit (MWMechanics::EffectKey key,
const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1)
{
if (key.mId != ESM::MagicEffect::Corprus)
return;
mSpells.push_back(sourceId);
}
std::vector<std::string> mSpells;
};
class SoulTrap : public MWMechanics::EffectSourceVisitor
{
MWWorld::Ptr mCreature;
@ -452,7 +495,7 @@ namespace MWMechanics
if (world->isSwimming(actor) || (playerPos - actorPos).length2() >= 3000 * 3000)
return;
// Our implementation is not FPS-dependent unlike Morrowind's so it needs to be recalibrated.
// Our implementation is not FPS-dependent unlike Morrowind's so it needs to be recalibrated.
// We chose to use the chance MW would have when run at 60 FPS with the default value of the GMST.
const float delta = MWBase::Environment::get().getFrameDuration() * 6.f;
static const float fVoiceIdleOdds = world->getStore().get<ESM::GameSetting>().find("fVoiceIdleOdds")->mValue.getFloat();
@ -468,9 +511,9 @@ namespace MWMechanics
CreatureStats &stats = actor.getClass().getCreatureStats(actor);
MWMechanics::AiSequence& seq = stats.getAiSequence();
if (!seq.isEmpty() && seq.getActivePackage()->useVariableSpeed())
if (!seq.isEmpty() && seq.getActivePackage().useVariableSpeed())
{
osg::Vec3f targetPos = seq.getActivePackage()->getDestination();
osg::Vec3f targetPos = seq.getActivePackage().getDestination();
osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
float distance = (targetPos - actorPos).length();
if (distance < DECELERATE_DISTANCE)
@ -637,7 +680,7 @@ namespace MWMechanics
bool isPlayerFollowerOrEscorter = playerAllies.find(actor1) != playerAllies.end();
// If actor2 and at least one actor2 are in combat with actor1, actor1 and its allies start combat with them
// Doesn't apply for player followers/escorters
// Doesn't apply for player followers/escorters
if (!aggressive && !isPlayerFollowerOrEscorter)
{
// Check that actor2 is in combat with actor1
@ -718,7 +761,7 @@ namespace MWMechanics
return;
bool followerOrEscorter = false;
for (const AiPackage* package : creatureStats2.getAiSequence())
for (const auto& package : creatureStats2.getAiSequence())
{
// The follow package must be first or have nothing but combat before it
if (package->sideWithTarget())
@ -767,7 +810,7 @@ namespace MWMechanics
{
CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr);
int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified();
float intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified();
float base = 1.f;
if (ptr == getPlayer())
@ -846,7 +889,7 @@ namespace MWMechanics
float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat ();
float fEndFatigueMult = settings.find("fEndFatigueMult")->mValue.getFloat ();
int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified ();
float endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified ();
float normalizedEncumbrance = ptr.getClass().getNormalizedEncumbrance(ptr);
if (normalizedEncumbrance > 1)
@ -873,7 +916,7 @@ namespace MWMechanics
return;
// Restore fatigue
int endurance = stats.getAttribute(ESM::Attribute::Endurance).getModified();
float endurance = stats.getAttribute(ESM::Attribute::Endurance).getModified();
const MWWorld::Store<ESM::GameSetting>& settings = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
static const float fFatigueReturnBase = settings.find("fFatigueReturnBase")->mValue.getFloat ();
static const float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat ();
@ -987,21 +1030,75 @@ namespace MWMechanics
if (creatureStats.needToRecalcDynamicStats())
calculateDynamicStats(ptr);
if (ptr == getPlayer())
{
Spells & spells = creatureStats.getSpells();
for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
GetCorprusSpells getCorprusSpellsVisitor;
creatureStats.getSpells().visitEffectSources(getCorprusSpellsVisitor);
creatureStats.getActiveSpells().visitEffectSources(getCorprusSpellsVisitor);
ptr.getClass().getInventoryStore(ptr).visitEffectSources(getCorprusSpellsVisitor);
std::vector<std::string> corprusSpells = getCorprusSpellsVisitor.mSpells;
std::vector<std::string> corprusSpellsToRemove;
for (auto it = creatureStats.getCorprusSpells().begin(); it != creatureStats.getCorprusSpells().end(); ++it)
{
if (spells.getCorprusSpells().find(it->first) != spells.getCorprusSpells().end())
if(std::find(corprusSpells.begin(), corprusSpells.end(), it->first) == corprusSpells.end())
{
if (MWBase::Environment::get().getWorld()->getTimeStamp() >= spells.getCorprusSpells().at(it->first).mNextWorsening)
{
spells.worsenCorprus(it->first);
// Corprus effect expired, remove entry and restore stats.
MWBase::Environment::get().getMechanicsManager()->restoreStatsAfterCorprus(ptr, it->first);
corprusSpellsToRemove.push_back(it->first);
corprusSpells.erase(std::remove(corprusSpells.begin(), corprusSpells.end(), it->first), corprusSpells.end());
continue;
}
if (ptr == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}");
corprusSpells.erase(std::remove(corprusSpells.begin(), corprusSpells.end(), it->first), corprusSpells.end());
if (MWBase::Environment::get().getWorld()->getTimeStamp() >= it->second.mNextWorsening)
{
it->second.mNextWorsening += CorprusStats::sWorseningPeriod;
GetCurrentMagnitudes getMagnitudesVisitor (it->first);
creatureStats.getSpells().visitEffectSources(getMagnitudesVisitor);
creatureStats.getActiveSpells().visitEffectSources(getMagnitudesVisitor);
ptr.getClass().getInventoryStore(ptr).visitEffectSources(getMagnitudesVisitor);
for (auto& effectMagnitude : getMagnitudesVisitor.mMagnitudes)
{
if (effectMagnitude.first.mId == ESM::MagicEffect::FortifyAttribute)
{
AttributeValue attr = creatureStats.getAttribute(effectMagnitude.first.mArg);
attr.damage(-effectMagnitude.second);
creatureStats.setAttribute(effectMagnitude.first.mArg, attr);
it->second.mWorsenings[effectMagnitude.first.mArg] = 0;
}
else if (effectMagnitude.first.mId == ESM::MagicEffect::DrainAttribute)
{
AttributeValue attr = creatureStats.getAttribute(effectMagnitude.first.mArg);
int currentDamage = attr.getDamage();
if (currentDamage >= 0)
it->second.mWorsenings[effectMagnitude.first.mArg] = std::min(it->second.mWorsenings[effectMagnitude.first.mArg], currentDamage);
it->second.mWorsenings[effectMagnitude.first.mArg] += effectMagnitude.second;
attr.damage(effectMagnitude.second);
creatureStats.setAttribute(effectMagnitude.first.mArg, attr);
}
}
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}");
}
}
for (std::string& oldCorprusSpell : corprusSpellsToRemove)
{
creatureStats.removeCorprusSpell(oldCorprusSpell);
}
for (std::string& newCorprusSpell : corprusSpells)
{
CorprusStats corprus;
for (int i=0; i<ESM::Attribute::Length; ++i)
corprus.mWorsenings[i] = 0;
corprus.mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod;
creatureStats.addCorprusSpell(newCorprusSpell, corprus);
}
}
// AI setting modifiers
@ -1786,9 +1883,14 @@ namespace MWMechanics
iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration);
// For dead actors we need to remove looping spell particles
// For dead actors we need to update looping spell particles
if (iter->first.getClass().getCreatureStats(iter->first).isDead())
{
// They can be added during the death animation
if (!iter->first.getClass().getCreatureStats(iter->first).isDeathAnimationFinished())
adjustMagicEffects(iter->first);
ctrl->updateContinuousVfx();
}
else
{
bool cellChanged = world->hasCellChanged();
@ -1913,7 +2015,7 @@ namespace MWMechanics
if (!isPlayer && isConscious(iter->first) && !stats.isParalyzed())
{
MWMechanics::AiSequence& seq = stats.getAiSequence();
alwaysActive = !seq.isEmpty() && seq.getActivePackage()->alwaysActive();
alwaysActive = !seq.isEmpty() && seq.getActivePackage().alwaysActive();
}
bool inRange = isPlayer || dist <= mActorsProcessingRange || alwaysActive;
int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower)
@ -2179,6 +2281,8 @@ namespace MWMechanics
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
{
iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration);
if (iter->first.getClass().getCreatureStats(iter->first).isDead())
continue;
@ -2434,7 +2538,7 @@ namespace MWMechanics
// An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Combat and Wander packages before the Follow/Escort package
// Actors that are targeted by this actor's Follow or Escort packages also side with them
for (const AiPackage* package : stats.getAiSequence())
for (const auto& package : stats.getAiSequence())
{
if (package->sideWithTarget() && !package->getTarget().isEmpty())
{
@ -2468,9 +2572,9 @@ namespace MWMechanics
if (stats.isDead())
continue;
// An actor counts as following if AiFollow is the current AiPackage,
// An actor counts as following if AiFollow is the current AiPackage,
// or there are only Combat and Wander packages before the AiFollow package
for (const AiPackage* package : stats.getAiSequence())
for (const auto& package : stats.getAiSequence())
{
if (package->followTargetThroughDoors() && package->getTarget() == actor)
list.push_back(iteratedActor);
@ -2533,11 +2637,11 @@ namespace MWMechanics
// An actor counts as following if AiFollow is the current AiPackage,
// or there are only Combat and Wander packages before the AiFollow package
for (AiPackage* package : stats.getAiSequence())
for (const auto& package : stats.getAiSequence())
{
if (package->followTargetThroughDoors() && package->getTarget() == actor)
{
list.push_back(static_cast<AiFollow*>(package)->getFollowIndex());
list.push_back(static_cast<const AiFollow*>(package.get())->getFollowIndex());
break;
}
else if (package->getTypeId() != AiPackage::TypeIdCombat && package->getTypeId() != AiPackage::TypeIdWander)

@ -1,6 +1,16 @@
#ifndef OPENMW_MWMECHANICS_ACTORUTIL_H
#define OPENMW_MWMECHANICS_ACTORUTIL_H
#include <components/esm/loadcrea.hpp>
#include <components/esm/loadnpc.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/esmstore.hpp"
#include "./creaturestats.hpp"
namespace MWWorld
{
class Ptr;
@ -18,6 +28,33 @@ namespace MWMechanics
MWWorld::Ptr getPlayer();
bool isPlayerInCombat();
bool canActorMoveByZAxis(const MWWorld::Ptr& actor);
template<class T>
void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value)
{
T copy = *MWBase::Environment::get().getWorld()->getStore().get<T>().find(id);
switch(setting)
{
case MWMechanics::CreatureStats::AiSetting::AI_Hello:
copy.mAiData.mHello = value;
break;
case MWMechanics::CreatureStats::AiSetting::AI_Fight:
copy.mAiData.mFight = value;
break;
case MWMechanics::CreatureStats::AiSetting::AI_Flee:
copy.mAiData.mFlee = value;
break;
case MWMechanics::CreatureStats::AiSetting::AI_Alarm:
copy.mAiData.mAlarm = value;
break;
default:
assert(0);
}
MWBase::Environment::get().getWorld()->createOverrideRecord(copy);
}
template void setBaseAISetting<ESM::Creature>(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value);
template void setBaseAISetting<ESM::NPC>(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value);
}
#endif

@ -44,11 +44,6 @@ namespace MWMechanics
End of tes3mp addition
*/
AiActivate *MWMechanics::AiActivate::clone() const
{
return new AiActivate(*this);
}
bool AiActivate::execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
{
/*
@ -109,11 +104,6 @@ namespace MWMechanics
return false;
}
int AiActivate::getTypeId() const
{
return TypeIdActivate;
}
void AiActivate::writeState(ESM::AiSequence::AiSequence &sequence) const
{
std::unique_ptr<ESM::AiSequence::AiActivate> activate(new ESM::AiSequence::AiActivate());

@ -1,7 +1,7 @@
#ifndef GAME_MWMECHANICS_AIACTIVATE_H
#define GAME_MWMECHANICS_AIACTIVATE_H
#include "aipackage.hpp"
#include "typedaipackage.hpp"
/*
Start of tes3mp addition
@ -29,7 +29,7 @@ namespace MWMechanics
{
/// \brief Causes actor to walk to activatable object and activate it
/** Will activate when close to object **/
class AiActivate final : public AiPackage
class AiActivate final : public TypedAiPackage<AiActivate>
{
public:
/// Constructor
@ -49,14 +49,14 @@ namespace MWMechanics
AiActivate(const ESM::AiSequence::AiActivate* activate);
AiActivate *clone() const final;
bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final;
int getTypeId() const final;
static constexpr TypeId getTypeId() { return TypeIdActivate; }
void writeState(ESM::AiSequence::AiSequence& sequence) const final;
private:
std::string mObjectId;
const std::string mObjectId;
/*
Start of tes3mp addition

@ -16,7 +16,7 @@
static const int MAX_DIRECTIONS = 4;
MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::ConstPtr& doorPtr)
: AiPackage(), mDuration(1), mDoorPtr(doorPtr), mDirection(0)
: mDuration(1), mDoorPtr(doorPtr), mDirection(0)
{
}
@ -72,21 +72,6 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterCont
return false;
}
MWMechanics::AiAvoidDoor *MWMechanics::AiAvoidDoor::clone() const
{
return new AiAvoidDoor(*this);
}
int MWMechanics::AiAvoidDoor::getTypeId() const
{
return TypeIdAvoidDoor;
}
unsigned int MWMechanics::AiAvoidDoor::getPriority() const
{
return 2;
}
bool MWMechanics::AiAvoidDoor::isStuck(const osg::Vec3f& actorPos) const
{
return (actorPos - mLastPos).length2() < 10 * 10;

@ -1,7 +1,7 @@
#ifndef GAME_MWMECHANICS_AIAVOIDDOOR_H
#define GAME_MWMECHANICS_AIAVOIDDOOR_H
#include "aipackage.hpp"
#include "typedaipackage.hpp"
#include <string>
@ -16,26 +16,28 @@ namespace MWMechanics
/// \brief AiPackage to have an actor avoid an opening door
/** The AI will retreat from the door until it has finished opening, walked far away from it, or one second has passed, in an attempt to avoid it
**/
class AiAvoidDoor final : public AiPackage
class AiAvoidDoor final : public TypedAiPackage<AiAvoidDoor>
{
public:
/// Avoid door until the door is fully open
AiAvoidDoor(const MWWorld::ConstPtr& doorPtr);
AiAvoidDoor *clone() const final;
bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final;
int getTypeId() const final;
unsigned int getPriority() const final;
static constexpr TypeId getTypeId() { return TypeIdAvoidDoor; }
bool canCancel() const final { return false; }
bool shouldCancelPreviousAi() const final { return false; }
static constexpr Options makeDefaultOptions()
{
AiPackage::Options options;
options.mPriority = 2;
options.mCanCancel = false;
options.mShouldCancelPreviousAi = false;
return options;
}
private:
float mDuration;
MWWorld::ConstPtr mDoorPtr;
const MWWorld::ConstPtr mDoorPtr;
osg::Vec3f mLastPos;
int mDirection;

@ -11,12 +11,6 @@
#include "movement.hpp"
#include "steering.hpp"
MWMechanics::AiBreathe::AiBreathe()
: AiPackage()
{
}
bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
{
static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fHoldBreathTime")->mValue.getFloat();
@ -37,18 +31,3 @@ bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterContro
return true;
}
MWMechanics::AiBreathe *MWMechanics::AiBreathe::clone() const
{
return new AiBreathe(*this);
}
int MWMechanics::AiBreathe::getTypeId() const
{
return TypeIdBreathe;
}
unsigned int MWMechanics::AiBreathe::getPriority() const
{
return 2;
}

@ -1,28 +1,27 @@
#ifndef GAME_MWMECHANICS_AIBREATHE_H
#define GAME_MWMECHANICS_AIBREATHE_H
#include "aipackage.hpp"
#include "typedaipackage.hpp"
namespace MWMechanics
{
/// \brief AiPackage to have an actor resurface to breathe
// The AI will go up if lesser than half breath left
class AiBreathe final : public AiPackage
class AiBreathe final : public TypedAiPackage<AiBreathe>
{
public:
AiBreathe();
AiBreathe *clone() const final;
bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final;
int getTypeId() const final;
static constexpr TypeId getTypeId() { return TypeIdBreathe; }
unsigned int getPriority() const final;
bool canCancel() const final { return false; }
bool shouldCancelPreviousAi() const final { return false; }
static constexpr Options makeDefaultOptions()
{
AiPackage::Options options;
options.mPriority = 2;
options.mCanCancel = false;
options.mShouldCancelPreviousAi = false;
return options;
}
};
}
#endif

@ -10,17 +10,22 @@
#include "creaturestats.hpp"
#include "steering.hpp"
MWMechanics::AiCast::AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell)
: mTargetId(targetId), mSpellId(spellId), mCasting(false), mManual(manualSpell), mDistance(0)
namespace MWMechanics
{
ActionSpell action = ActionSpell(spellId);
bool isRanged;
mDistance = action.getCombatRange(isRanged);
namespace
{
float getInitialDistance(const std::string& spellId)
{
ActionSpell action = ActionSpell(spellId);
bool isRanged;
return action.getCombatRange(isRanged);
}
}
}
MWMechanics::AiPackage *MWMechanics::AiCast::clone() const
MWMechanics::AiCast::AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell)
: mTargetId(targetId), mSpellId(spellId), mCasting(false), mManual(manualSpell), mDistance(getInitialDistance(spellId))
{
return new AiCast(*this);
}
bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::CharacterController& characterController, MWMechanics::AiState& state, float duration)
@ -84,13 +89,3 @@ MWWorld::Ptr MWMechanics::AiCast::getTarget() const
return target;
}
int MWMechanics::AiCast::getTypeId() const
{
return AiPackage::TypeIdCast;
}
unsigned int MWMechanics::AiCast::getPriority() const
{
return 3;
}

@ -1,7 +1,7 @@
#ifndef GAME_MWMECHANICS_AICAST_H
#define GAME_MWMECHANICS_AICAST_H
#include "aipackage.hpp"
#include "typedaipackage.hpp"
namespace MWWorld
{
@ -11,29 +11,31 @@ namespace MWWorld
namespace MWMechanics
{
/// AiPackage which makes an actor to cast given spell.
class AiCast final : public AiPackage {
class AiCast final : public TypedAiPackage<AiCast> {
public:
AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell=false);
AiPackage *clone() const final;
bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final;
int getTypeId() const final;
static constexpr TypeId getTypeId() { return TypeIdCast; }
MWWorld::Ptr getTarget() const final;
unsigned int getPriority() const final;
bool canCancel() const final { return false; }
bool shouldCancelPreviousAi() const final { return false; }
static constexpr Options makeDefaultOptions()
{
AiPackage::Options options;
options.mPriority = 3;
options.mCanCancel = false;
options.mShouldCancelPreviousAi = false;
return options;
}
private:
std::string mTargetId;
std::string mSpellId;
const std::string mTargetId;
const std::string mSpellId;
bool mCasting;
bool mManual;
float mDistance;
const bool mManual;
const float mDistance;
};
}

@ -474,26 +474,11 @@ namespace MWMechanics
}
}
int AiCombat::getTypeId() const
{
return TypeIdCombat;
}
unsigned int AiCombat::getPriority() const
{
return 1;
}
MWWorld::Ptr AiCombat::getTarget() const
{
return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
}
AiCombat *MWMechanics::AiCombat::clone() const
{
return new AiCombat(*this);
}
void AiCombat::writeState(ESM::AiSequence::AiSequence &sequence) const
{
std::unique_ptr<ESM::AiSequence::AiCombat> combat(new ESM::AiSequence::AiCombat());

@ -1,7 +1,7 @@
#ifndef GAME_MWMECHANICS_AICOMBAT_H
#define GAME_MWMECHANICS_AICOMBAT_H
#include "aipackage.hpp"
#include "typedaipackage.hpp"
#include "../mwworld/cellstore.hpp" // for Doors
@ -91,7 +91,7 @@ namespace MWMechanics
};
/// \brief Causes the actor to fight another actor
class AiCombat final : public AiPackage
class AiCombat final : public TypedAiPackage<AiCombat>
{
public:
///Constructor
@ -102,22 +102,24 @@ namespace MWMechanics
void init();
AiCombat *clone() const final;
bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final;
int getTypeId() const final;
static constexpr TypeId getTypeId() { return TypeIdCombat; }
unsigned int getPriority() const final;
static constexpr Options makeDefaultOptions()
{
AiPackage::Options options;
options.mPriority = 1;
options.mCanCancel = false;
options.mShouldCancelPreviousAi = false;
return options;
}
///Returns target ID
MWWorld::Ptr getTarget() const final;
void writeState(ESM::AiSequence::AiSequence &sequence) const final;
bool canCancel() const final { return false; }
bool shouldCancelPreviousAi() const final { return false; }
private:
/// Returns true if combat should end
bool attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController);

@ -26,7 +26,6 @@ namespace MWMechanics
, mCellY(std::numeric_limits<int>::max())
{
mTargetActorRefId = actorId;
mMaxDist = 450;
}
AiEscort::AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z)
@ -35,30 +34,20 @@ namespace MWMechanics
, mCellY(std::numeric_limits<int>::max())
{
mTargetActorRefId = actorId;
mMaxDist = 450;
}
AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort)
: mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ)
, mMaxDist(450)
// mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration.
// The exact value of mDuration only matters for repeating packages.
// Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves.
, mDuration(escort->mRemainingDuration > 0)
, mRemainingDuration(escort->mRemainingDuration)
, mCellX(std::numeric_limits<int>::max())
, mCellY(std::numeric_limits<int>::max())
{
mTargetActorRefId = escort->mTargetId;
mTargetActorId = escort->mTargetActorId;
// mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration.
// The exact value of mDuration only matters for repeating packages.
if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves.
mDuration = 1;
else
mDuration = 0;
}
AiEscort *MWMechanics::AiEscort::clone() const
{
return new AiEscort(*this);
}
bool AiEscort::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
@ -106,11 +95,6 @@ namespace MWMechanics
return false;
}
int AiEscort::getTypeId() const
{
return TypeIdEscort;
}
void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const
{
std::unique_ptr<ESM::AiSequence::AiEscort> escort(new ESM::AiSequence::AiEscort());

@ -1,7 +1,7 @@
#ifndef GAME_MWMECHANICS_AIESCORT_H
#define GAME_MWMECHANICS_AIESCORT_H
#include "aipackage.hpp"
#include "typedaipackage.hpp"
#include <string>
@ -16,7 +16,7 @@ namespace AiSequence
namespace MWMechanics
{
/// \brief AI Package to have an NPC lead the player to a specific point
class AiEscort final : public AiPackage
class AiEscort final : public TypedAiPackage<AiEscort>
{
public:
/// Implementation of AiEscort
@ -30,15 +30,17 @@ namespace MWMechanics
AiEscort(const ESM::AiSequence::AiEscort* escort);
AiEscort *clone() const final;
bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final;
int getTypeId() const final;
bool useVariableSpeed() const final { return true; }
static constexpr TypeId getTypeId() { return TypeIdEscort; }
bool sideWithTarget() const final { return true; }
static constexpr Options makeDefaultOptions()
{
AiPackage::Options options;
options.mUseVariableSpeed = true;
options.mSideWithTarget = true;
return options;
}
void writeState(ESM::AiSequence::AiSequence &sequence) const final;
@ -47,16 +49,16 @@ namespace MWMechanics
osg::Vec3f getDestination() const final { return osg::Vec3f(mX, mY, mZ); }
private:
std::string mCellId;
float mX;
float mY;
float mZ;
float mMaxDist;
float mDuration; // In hours
const std::string mCellId;
const float mX;
const float mY;
const float mZ;
float mMaxDist = 450;
const float mDuration; // In hours
float mRemainingDuration; // In hours
int mCellX;
int mCellY;
const int mCellX;
const int mCellY;
};
}
#endif

@ -9,23 +9,8 @@ MWMechanics::AiFace::AiFace(float targetX, float targetY)
{
}
MWMechanics::AiPackage *MWMechanics::AiFace::clone() const
{
return new AiFace(*this);
}
bool MWMechanics::AiFace::execute(const MWWorld::Ptr& actor, MWMechanics::CharacterController& /*characterController*/, MWMechanics::AiState& /*state*/, float /*duration*/)
{
osg::Vec3f dir = osg::Vec3f(mTargetX, mTargetY, 0) - actor.getRefData().getPosition().asVec3();
return zTurn(actor, std::atan2(dir.x(), dir.y()), osg::DegreesToRadians(3.f));
}
int MWMechanics::AiFace::getTypeId() const
{
return AiPackage::TypeIdFace;
}
unsigned int MWMechanics::AiFace::getPriority() const
{
return 2;
}

@ -1,28 +1,31 @@
#ifndef GAME_MWMECHANICS_AIFACE_H
#define GAME_MWMECHANICS_AIFACE_H
#include "aipackage.hpp"
#include "typedaipackage.hpp"
namespace MWMechanics
{
/// AiPackage which makes an actor face a certain direction.
class AiFace final : public AiPackage {
class AiFace final : public TypedAiPackage<AiFace> {
public:
AiFace(float targetX, float targetY);
AiPackage *clone() const final;
bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final;
int getTypeId() const final;
unsigned int getPriority() const final;
static constexpr TypeId getTypeId() { return TypeIdFace; }
bool canCancel() const final { return false; }
bool shouldCancelPreviousAi() const final { return false; }
static constexpr Options makeDefaultOptions()
{
AiPackage::Options options;
options.mPriority = 2;
options.mCanCancel = false;
options.mShouldCancelPreviousAi = false;
return options;
}
private:
float mTargetX, mTargetY;
const float mTargetX;
const float mTargetY;
};
}

@ -16,25 +16,24 @@
namespace MWMechanics
{
int AiFollow::mFollowIndexCounter = 0;
AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z)
: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++)
{
mTargetActorRefId = actorId;
}
AiFollow::AiFollow(const std::string &actorId, const std::string &cellId, float duration, float x, float y, float z)
: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
, mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++)
{
mTargetActorRefId = actorId;
}
AiFollow::AiFollow(const MWWorld::Ptr& actor, float duration, float x, float y, float z)
: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++)
{
mTargetActorRefId = actor.getCellRef().getRefId();
@ -42,7 +41,7 @@ AiFollow::AiFollow(const MWWorld::Ptr& actor, float duration, float x, float y,
}
AiFollow::AiFollow(const MWWorld::Ptr& actor, const std::string &cellId, float duration, float x, float y, float z)
: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
, mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++)
{
mTargetActorRefId = actor.getCellRef().getRefId();
@ -50,7 +49,8 @@ AiFollow::AiFollow(const MWWorld::Ptr& actor, const std::string &cellId, float d
}
AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded)
: mAlwaysFollow(true), mCommanded(commanded), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0)
: TypedAiPackage<AiFollow>(makeDefaultOptions().withShouldCancelPreviousAi(!commanded))
, mAlwaysFollow(true), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0)
, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++)
{
mTargetActorRefId = actor.getCellRef().getRefId();
@ -58,18 +58,18 @@ AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded)
}
AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow)
: mAlwaysFollow(follow->mAlwaysFollow), mCommanded(follow->mCommanded), mRemainingDuration(follow->mRemainingDuration)
: TypedAiPackage<AiFollow>(makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded))
, mAlwaysFollow(follow->mAlwaysFollow)
// mDuration isn't saved in the save file, so just giving it "1" for now if the package had a duration.
// The exact value of mDuration only matters for repeating packages.
// Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves.
, mDuration(follow->mRemainingDuration)
, mRemainingDuration(follow->mRemainingDuration)
, mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ)
, mCellId(follow->mCellId), mActive(follow->mActive), mFollowIndex(mFollowIndexCounter++)
{
mTargetActorRefId = follow->mTargetId;
mTargetActorId = follow->mTargetActorId;
// mDuration isn't saved in the save file, so just giving it "1" for now if the package had a duration.
// The exact value of mDuration only matters for repeating packages.
if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves.
mDuration = 1;
else
mDuration = 0;
}
bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
@ -212,19 +212,9 @@ std::string AiFollow::getFollowedActor()
return mTargetActorRefId;
}
AiFollow *MWMechanics::AiFollow::clone() const
{
return new AiFollow(*this);
}
int AiFollow::getTypeId() const
{
return TypeIdFollow;
}
bool AiFollow::isCommanded() const
{
return mCommanded;
return !mOptions.mShouldCancelPreviousAi;
}
void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const
@ -238,7 +228,7 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const
follow->mRemainingDuration = mRemainingDuration;
follow->mCellId = mCellId;
follow->mAlwaysFollow = mAlwaysFollow;
follow->mCommanded = mCommanded;
follow->mCommanded = isCommanded();
follow->mActive = mActive;
ESM::AiSequence::AiPackageContainer package;

@ -1,7 +1,7 @@
#ifndef GAME_MWMECHANICS_AIFOLLOW_H
#define GAME_MWMECHANICS_AIFOLLOW_H
#include "aipackage.hpp"
#include "typedaipackage.hpp"
#include <string>
@ -39,7 +39,7 @@ namespace MWMechanics
/// \brief AiPackage for an actor to follow another actor/the PC
/** The AI will follow the target until a condition (time, or position) are set. Both can be disabled to cause the actor to follow the other indefinitely
**/
class AiFollow final : public AiPackage
class AiFollow final : public TypedAiPackage<AiFollow>
{
public:
AiFollow(const std::string &actorId, float duration, float x, float y, float z);
@ -53,17 +53,18 @@ namespace MWMechanics
AiFollow(const ESM::AiSequence::AiFollow* follow);
bool sideWithTarget() const final { return true; }
bool followTargetThroughDoors() const final { return true; }
bool shouldCancelPreviousAi() const final { return !mCommanded; }
AiFollow *clone() const final;
bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final;
int getTypeId() const final;
static constexpr TypeId getTypeId() { return TypeIdFollow; }
bool useVariableSpeed() const final { return true; }
static constexpr Options makeDefaultOptions()
{
AiPackage::Options options;
options.mUseVariableSpeed = true;
options.mSideWithTarget = true;
options.mFollowTargetThroughDoors = true;
return options;
}
/// Returns the actor being followed
std::string getFollowedActor();
@ -98,16 +99,15 @@ namespace MWMechanics
private:
/// This will make the actor always follow.
/** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/
bool mAlwaysFollow;
bool mCommanded;
float mDuration; // Hours
const bool mAlwaysFollow;
const float mDuration; // Hours
float mRemainingDuration; // Hours
float mX;
float mY;
float mZ;
std::string mCellId;
const float mX;
const float mY;
const float mZ;
const std::string mCellId;
bool mActive; // have we spotted the target?
int mFollowIndex;
const int mFollowIndex;
static int mFollowIndexCounter;

@ -24,7 +24,9 @@
#include <osg/Quat>
MWMechanics::AiPackage::AiPackage() :
MWMechanics::AiPackage::AiPackage(TypeId typeId, const Options& options) :
mTypeId(typeId),
mOptions(options),
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
mTargetActorRefId(""),
mTargetActorId(-1),
@ -58,31 +60,6 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
return MWWorld::Ptr();
}
bool MWMechanics::AiPackage::sideWithTarget() const
{
return false;
}
bool MWMechanics::AiPackage::followTargetThroughDoors() const
{
return false;
}
bool MWMechanics::AiPackage::canCancel() const
{
return true;
}
bool MWMechanics::AiPackage::shouldCancelPreviousAi() const
{
return true;
}
bool MWMechanics::AiPackage::getRepeat() const
{
return false;
}
void MWMechanics::AiPackage::reset()
{
// reset all members

@ -1,6 +1,8 @@
#ifndef GAME_MWMECHANICS_AIPACKAGE_H
#define GAME_MWMECHANICS_AIPACKAGE_H
#include <memory>
#include <components/esm/defs.hpp>
#include "pathfinding.hpp"
@ -53,13 +55,41 @@ namespace MWMechanics
TypeIdCast = 11
};
///Default constructor
AiPackage();
struct Options
{
unsigned int mPriority = 0;
bool mUseVariableSpeed = false;
bool mSideWithTarget = false;
bool mFollowTargetThroughDoors = false;
bool mCanCancel = true;
bool mShouldCancelPreviousAi = true;
bool mRepeat = false;
bool mAlwaysActive = false;
constexpr Options withRepeat(bool value)
{
mRepeat = value;
return *this;
}
constexpr Options withShouldCancelPreviousAi(bool value)
{
mShouldCancelPreviousAi = value;
return *this;
}
};
AiPackage(TypeId typeId, const Options& options);
virtual ~AiPackage() = default;
static constexpr Options makeDefaultOptions()
{
return Options{};
}
///Clones the package
virtual AiPackage *clone() const = 0;
virtual std::unique_ptr<AiPackage> clone() const = 0;
/// Updates and runs the package (Should run every frame)
/// \return Package completed?
@ -67,13 +97,13 @@ namespace MWMechanics
/// Returns the TypeID of the AiPackage
/// \see enum TypeId
virtual int getTypeId() const = 0;
TypeId getTypeId() const { return mTypeId; }
/// Higher number is higher priority (0 being the lowest)
virtual unsigned int getPriority() const {return 0;}
unsigned int getPriority() const { return mOptions.mPriority; }
/// Check if package use movement with variable speed
virtual bool useVariableSpeed() const { return false;}
bool useVariableSpeed() const { return mOptions.mUseVariableSpeed; }
virtual void writeState (ESM::AiSequence::AiSequence& sequence) const {}
@ -87,24 +117,24 @@ namespace MWMechanics
virtual osg::Vec3f getDestination(const MWWorld::Ptr& actor) const { return osg::Vec3f(0, 0, 0); };
/// Return true if having this AiPackage makes the actor side with the target in fights (default false)
virtual bool sideWithTarget() const;
bool sideWithTarget() const { return mOptions.mSideWithTarget; }
/// Return true if the actor should follow the target through teleport doors (default false)
virtual bool followTargetThroughDoors() const;
bool followTargetThroughDoors() const { return mOptions.mFollowTargetThroughDoors; }
/// Can this Ai package be canceled? (default true)
virtual bool canCancel() const;
bool canCancel() const { return mOptions.mCanCancel; }
/// Upon adding this Ai package, should the Ai Sequence attempt to cancel previous Ai packages (default true)?
virtual bool shouldCancelPreviousAi() const;
bool shouldCancelPreviousAi() const { return mOptions.mShouldCancelPreviousAi; }
/// Return true if this package should repeat. Currently only used for Wander packages.
virtual bool getRepeat() const;
bool getRepeat() const { return mOptions.mRepeat; }
virtual osg::Vec3f getDestination() const { return osg::Vec3f(0, 0, 0); }
// Return true if any loaded actor with this AI package must be active.
virtual bool alwaysActive() const { return false; }
/// Return true if any loaded actor with this AI package must be active.
bool alwaysActive() const { return mOptions.mAlwaysActive; }
/// Reset pathfinding state
void reset();
@ -137,6 +167,9 @@ namespace MWMechanics
DetourNavigator::Flags getNavigatorFlags(const MWWorld::Ptr& actor) const;
const TypeId mTypeId;
const Options mOptions;
// TODO: all this does not belong here, move into temporary storage
PathFinder mPathFinder;
ObstacleCheck mObstacleCheck;

@ -40,10 +40,6 @@ AiPursue::AiPursue(const ESM::AiSequence::AiPursue *pursue)
mTargetActorId = pursue->mTargetActorId;
}
AiPursue *MWMechanics::AiPursue::clone() const
{
return new AiPursue(*this);
}
bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
{
if(actor.getClass().getCreatureStats(actor).isDead())
@ -116,11 +112,6 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte
return false;
}
int AiPursue::getTypeId() const
{
return TypeIdPursue;
}
MWWorld::Ptr AiPursue::getTarget() const
{
return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);

@ -1,7 +1,7 @@
#ifndef GAME_MWMECHANICS_AIPURSUE_H
#define GAME_MWMECHANICS_AIPURSUE_H
#include "aipackage.hpp"
#include "typedaipackage.hpp"
namespace ESM
{
@ -17,7 +17,7 @@ namespace MWMechanics
/** Used for arresting players. Causes the actor to run to the pursued actor and activate them, to arrest them.
Note that while very similar to AiActivate, it will ONLY activate when evry close to target (Not also when the
path is completed). **/
class AiPursue final : public AiPackage
class AiPursue final : public TypedAiPackage<AiPursue>
{
public:
///Constructor
@ -26,16 +26,21 @@ namespace MWMechanics
AiPursue(const ESM::AiSequence::AiPursue* pursue);
AiPursue *clone() const final;
bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final;
int getTypeId() const final;
static constexpr TypeId getTypeId() { return TypeIdPursue; }
static constexpr Options makeDefaultOptions()
{
AiPackage::Options options;
options.mCanCancel = false;
options.mShouldCancelPreviousAi = false;
return options;
}
MWWorld::Ptr getTarget() const final;
void writeState (ESM::AiSequence::AiSequence& sequence) const final;
bool canCancel() const final { return false; }
bool shouldCancelPreviousAi() const final { return false; }
};
}
#endif

@ -25,9 +25,8 @@ namespace MWMechanics
void AiSequence::copy (const AiSequence& sequence)
{
for (std::list<AiPackage *>::const_iterator iter (sequence.mPackages.begin());
iter!=sequence.mPackages.end(); ++iter)
mPackages.push_back ((*iter)->clone());
for (const auto& package : sequence.mPackages)
mPackages.push_back(package->clone());
// We need to keep an AiWander storage, if present - it has a state machine.
// Not sure about another temporary storages
@ -74,7 +73,7 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const
{
if (getTypeId() != AiPackage::TypeIdCombat)
return false;
targetActor = mPackages.front()->getTarget();
return !targetActor.isEmpty();
@ -82,7 +81,7 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const
bool AiSequence::getCombatTargets(std::vector<MWWorld::Ptr> &targetActors) const
{
for (std::list<AiPackage*>::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it)
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
{
if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCombat)
targetActors.push_back((*it)->getTarget());
@ -91,24 +90,23 @@ bool AiSequence::getCombatTargets(std::vector<MWWorld::Ptr> &targetActors) const
return !targetActors.empty();
}
std::list<AiPackage*>::const_iterator AiSequence::begin() const
std::list<std::unique_ptr<AiPackage>>::const_iterator AiSequence::begin() const
{
return mPackages.begin();
}
std::list<AiPackage*>::const_iterator AiSequence::end() const
std::list<std::unique_ptr<AiPackage>>::const_iterator AiSequence::end() const
{
return mPackages.end();
}
void AiSequence::erase(std::list<AiPackage*>::const_iterator package)
void AiSequence::erase(std::list<std::unique_ptr<AiPackage>>::const_iterator package)
{
// Not sure if manually terminated packages should trigger mDone, probably not?
for(std::list<AiPackage*>::iterator it = mPackages.begin(); it != mPackages.end(); ++it)
for(auto it = mPackages.begin(); it != mPackages.end(); ++it)
{
if (package == it)
{
delete *it;
mPackages.erase(it);
return;
}
@ -118,7 +116,7 @@ void AiSequence::erase(std::list<AiPackage*>::const_iterator package)
bool AiSequence::isInCombat() const
{
for(std::list<AiPackage*>::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it)
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
{
if ((*it)->getTypeId() == AiPackage::TypeIdCombat)
return true;
@ -128,7 +126,7 @@ bool AiSequence::isInCombat() const
bool AiSequence::isEngagedWithActor() const
{
for (std::list<AiPackage *>::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it)
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
{
if ((*it)->getTypeId() == AiPackage::TypeIdCombat)
{
@ -142,7 +140,7 @@ bool AiSequence::isEngagedWithActor() const
bool AiSequence::hasPackage(int typeId) const
{
for (std::list<AiPackage*>::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it)
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
{
if ((*it)->getTypeId() == typeId)
return true;
@ -152,7 +150,7 @@ bool AiSequence::hasPackage(int typeId) const
bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const
{
for(std::list<AiPackage*>::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it)
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
{
if ((*it)->getTypeId() == AiPackage::TypeIdCombat)
{
@ -165,11 +163,10 @@ bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const
void AiSequence::stopCombat()
{
for(std::list<AiPackage*>::iterator it = mPackages.begin(); it != mPackages.end(); )
for(auto it = mPackages.begin(); it != mPackages.end(); )
{
if ((*it)->getTypeId() == AiPackage::TypeIdCombat)
{
delete *it;
it = mPackages.erase(it);
}
else
@ -179,11 +176,10 @@ void AiSequence::stopCombat()
void AiSequence::stopPursuit()
{
for(std::list<AiPackage*>::iterator it = mPackages.begin(); it != mPackages.end(); )
for(auto it = mPackages.begin(); it != mPackages.end(); )
{
if ((*it)->getTypeId() == AiPackage::TypeIdPursue)
{
delete *it;
it = mPackages.erase(it);
}
else
@ -213,7 +209,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac
}
auto packageIt = mPackages.begin();
MWMechanics::AiPackage* package = *packageIt;
MWMechanics::AiPackage* package = packageIt->get();
if (!package->alwaysActive() && outOfRange)
return;
@ -231,7 +227,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac
float bestRating = 0.f;
for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end();)
for (auto it = mPackages.begin(); it != mPackages.end();)
{
if ((*it)->getTypeId() != AiPackage::TypeIdCombat) break;
@ -240,7 +236,6 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac
// target disappeared (e.g. summoned creatures)
if (target.isEmpty())
{
delete *it;
it = mPackages.erase(it);
}
else
@ -276,13 +271,13 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac
}
packageIt = mPackages.begin();
package = *packageIt;
package = packageIt->get();
packageTypeId = package->getTypeId();
}
try
{
if (package->execute (actor, characterController, mAiState, duration))
if (package->execute(actor, characterController, mAiState, duration))
{
// Put repeating noncombat AI packages on the end of the stack so they can be used again
if (isActualAiPackage(packageTypeId) && (mRepeat || package->getRepeat()))
@ -293,7 +288,6 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac
// To account for the rare case where AiPackage::execute() queued another AI package
// (e.g. AiPursue executing a dialogue script that uses startCombat)
mPackages.erase(packageIt);
delete package;
if (isActualAiPackage(packageTypeId))
mDone = true;
}
@ -311,9 +305,6 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac
void AiSequence::clear()
{
for (std::list<AiPackage *>::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter)
delete *iter;
mPackages.clear();
}
@ -340,26 +331,24 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
osg::Vec3f dest;
if (currentTypeId == MWMechanics::AiPackage::TypeIdWander)
{
AiPackage* activePackage = getActivePackage();
dest = activePackage->getDestination(actor);
dest = getActivePackage().getDestination(actor);
}
else
{
dest = actor.getRefData().getPosition().asVec3();
}
MWMechanics::AiTravel travelPackage(dest.x(), dest.y(), dest.z(), true);
MWMechanics::AiInternalTravel travelPackage(dest.x(), dest.y(), dest.z());
stack(travelPackage, actor, false);
}
// remove previous packages if required
if (cancelOther && package.shouldCancelPreviousAi())
{
for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end();)
for (auto it = mPackages.begin(); it != mPackages.end();)
{
if((*it)->canCancel())
{
delete *it;
it = mPackages.erase(it);
}
else
@ -369,7 +358,7 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
}
// insert new package in correct place depending on priority
for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end(); ++it)
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
{
// We should keep current AiCast package, if we try to add a new one.
if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCast &&
@ -380,12 +369,12 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
if((*it)->getPriority() <= package.getPriority())
{
mPackages.insert(it,package.clone());
mPackages.insert(it, package.clone());
return;
}
}
mPackages.push_back (package.clone());
mPackages.push_back(package.clone());
// Make sure that temporary storage is empty
if (cancelOther)
@ -401,12 +390,11 @@ bool MWMechanics::AiSequence::isEmpty() const
return mPackages.empty();
}
AiPackage* MWMechanics::AiSequence::getActivePackage()
const AiPackage& MWMechanics::AiSequence::getActivePackage()
{
if(mPackages.empty())
throw std::runtime_error(std::string("No AI Package!"));
else
return mPackages.front();
return *mPackages.front();
}
void AiSequence::fill(const ESM::AIPackageList &list)
@ -417,7 +405,7 @@ void AiSequence::fill(const ESM::AIPackageList &list)
for (std::vector<ESM::AIPackage>::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it)
{
MWMechanics::AiPackage* package;
std::unique_ptr<MWMechanics::AiPackage> package;
if (it->mType == ESM::AI_Wander)
{
ESM::AIWander data = it->mWander;
@ -425,38 +413,36 @@ void AiSequence::fill(const ESM::AIPackageList &list)
idles.reserve(8);
for (int i=0; i<8; ++i)
idles.push_back(data.mIdle[i]);
package = new MWMechanics::AiWander(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mShouldRepeat != 0);
package = std::make_unique<MWMechanics::AiWander>(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mShouldRepeat != 0);
}
else if (it->mType == ESM::AI_Escort)
{
ESM::AITarget data = it->mTarget;
package = new MWMechanics::AiEscort(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ);
package = std::make_unique<MWMechanics::AiEscort>(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ);
}
else if (it->mType == ESM::AI_Travel)
{
ESM::AITravel data = it->mTravel;
package = new MWMechanics::AiTravel(data.mX, data.mY, data.mZ);
package = std::make_unique<MWMechanics::AiTravel>(data.mX, data.mY, data.mZ);
}
else if (it->mType == ESM::AI_Activate)
{
ESM::AIActivate data = it->mActivate;
package = new MWMechanics::AiActivate(data.mName.toString());
package = std::make_unique<MWMechanics::AiActivate>(data.mName.toString());
}
else //if (it->mType == ESM::AI_Follow)
{
ESM::AITarget data = it->mTarget;
package = new MWMechanics::AiFollow(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ);
package = std::make_unique<MWMechanics::AiFollow>(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ);
}
mPackages.push_back(package);
mPackages.push_back(std::move(package));
}
}
void AiSequence::writeState(ESM::AiSequence::AiSequence &sequence) const
{
for (std::list<AiPackage *>::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter)
{
(*iter)->writeState(sequence);
}
for (const auto& package : mPackages)
package->writeState(sequence);
sequence.mLastAiPackage = mLastAiPackage;
}
@ -492,7 +478,11 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
}
case ESM::AiSequence::Ai_Travel:
{
package.reset(new AiTravel(static_cast<ESM::AiSequence::AiTravel*>(it->mPackage)));
const auto source = static_cast<const ESM::AiSequence::AiTravel*>(it->mPackage);
if (source->mHidden)
package.reset(new AiInternalTravel(source));
else
package.reset(new AiTravel(source));
break;
}
case ESM::AiSequence::Ai_Escort:
@ -527,7 +517,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
if (!package.get())
continue;
mPackages.push_back(package.release());
mPackages.push_back(std::move(package));
}
mLastAiPackage = sequence.mLastAiPackage;
@ -537,8 +527,7 @@ void AiSequence::fastForward(const MWWorld::Ptr& actor)
{
if (!mPackages.empty())
{
MWMechanics::AiPackage* package = mPackages.front();
package->fastForward(actor, mAiState);
mPackages.front()->fastForward(actor, mAiState);
}
}

@ -2,6 +2,7 @@
#define GAME_MWMECHANICS_AISEQUENCE_H
#include <list>
#include <memory>
#include "aistate.hpp"
@ -36,7 +37,7 @@ namespace MWMechanics
class AiSequence
{
///AiPackages to run though
std::list<AiPackage *> mPackages;
std::list<std::unique_ptr<AiPackage>> mPackages;
///Finished with top AIPackage, set for one frame
bool mDone;
@ -64,10 +65,10 @@ namespace MWMechanics
virtual ~AiSequence();
/// Iterator may be invalidated by any function calls other than begin() or end().
std::list<AiPackage*>::const_iterator begin() const;
std::list<AiPackage*>::const_iterator end() const;
std::list<std::unique_ptr<AiPackage>>::const_iterator begin() const;
std::list<std::unique_ptr<AiPackage>>::const_iterator end() const;
void erase (std::list<AiPackage*>::const_iterator package);
void erase(std::list<std::unique_ptr<AiPackage>>::const_iterator package);
/// Returns currently executing AiPackage type
/** \see enum AiPackage::TypeId **/
@ -125,7 +126,7 @@ namespace MWMechanics
/// Return the current active package.
/** If there is no active package, it will throw an exception **/
AiPackage* getActivePackage();
const AiPackage& getActivePackage();
/// Fills the AiSequence with packages
/** Typically used for loading from the ESM

@ -27,19 +27,26 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2)
namespace MWMechanics
{
AiTravel::AiTravel(float x, float y, float z, bool hidden)
: mX(x),mY(y),mZ(z),mHidden(hidden)
AiTravel::AiTravel(float x, float y, float z, AiTravel*)
: mX(x), mY(y), mZ(z), mHidden(false)
{
}
AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel)
: mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(travel->mHidden)
AiTravel::AiTravel(float x, float y, float z, AiInternalTravel* derived)
: TypedAiPackage<AiTravel>(derived), mX(x), mY(y), mZ(z), mHidden(true)
{
}
AiTravel *MWMechanics::AiTravel::clone() const
AiTravel::AiTravel(float x, float y, float z)
: AiTravel(x, y, z, this)
{
}
AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel)
: mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(false)
{
return new AiTravel(*this);
// Hidden ESM::AiSequence::AiTravel package should be converted into MWMechanics::AiInternalTravel type
assert(!travel->mHidden);
}
bool AiTravel::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
@ -83,11 +90,6 @@ namespace MWMechanics
return false;
}
int AiTravel::getTypeId() const
{
return mHidden ? TypeIdInternalTravel : TypeIdTravel;
}
void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state)
{
if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), actor.getRefData().getPosition().asVec3()))
@ -112,5 +114,20 @@ namespace MWMechanics
package.mPackage = travel.release();
sequence.mPackages.push_back(package);
}
AiInternalTravel::AiInternalTravel(float x, float y, float z)
: AiTravel(x, y, z, this)
{
}
AiInternalTravel::AiInternalTravel(const ESM::AiSequence::AiTravel* travel)
: AiTravel(travel->mData.mX, travel->mData.mY, travel->mData.mZ, this)
{
}
std::unique_ptr<AiPackage> AiInternalTravel::clone() const
{
return std::make_unique<AiInternalTravel>(*this);
}
}

@ -1,7 +1,7 @@
#ifndef GAME_MWMECHANICS_AITRAVEL_H
#define GAME_MWMECHANICS_AITRAVEL_H
#include "aipackage.hpp"
#include "typedaipackage.hpp"
namespace ESM
{
@ -13,12 +13,18 @@ namespace AiSequence
namespace MWMechanics
{
struct AiInternalTravel;
/// \brief Causes the AI to travel to the specified point
class AiTravel final : public AiPackage
class AiTravel : public TypedAiPackage<AiTravel>
{
public:
/// Default constructor
AiTravel(float x, float y, float z, bool hidden = false);
AiTravel(float x, float y, float z, AiTravel* derived);
AiTravel(float x, float y, float z, AiInternalTravel* derived);
AiTravel(float x, float y, float z);
AiTravel(const ESM::AiSequence::AiTravel* travel);
/// Simulates the passing of time
@ -26,24 +32,37 @@ namespace MWMechanics
void writeState(ESM::AiSequence::AiSequence &sequence) const final;
AiTravel *clone() const final;
bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final;
int getTypeId() const final;
bool useVariableSpeed() const final { return true; }
static constexpr TypeId getTypeId() { return TypeIdTravel; }
bool alwaysActive() const final { return true; }
static constexpr Options makeDefaultOptions()
{
AiPackage::Options options;
options.mUseVariableSpeed = true;
options.mAlwaysActive = true;
return options;
}
osg::Vec3f getDestination() const final { return osg::Vec3f(mX, mY, mZ); }
private:
float mX;
float mY;
float mZ;
const float mX;
const float mY;
const float mZ;
const bool mHidden;
};
struct AiInternalTravel final : public AiTravel
{
AiInternalTravel(float x, float y, float z);
explicit AiInternalTravel(const ESM::AiSequence::AiTravel* travel);
static constexpr TypeId getTypeId() { return TypeIdInternalTravel; }
bool mHidden;
std::unique_ptr<AiPackage> clone() const final;
};
}

@ -1,5 +1,7 @@
#include "aiwander.hpp"
#include <algorithm>
#include <components/debug/debuglog.hpp>
#include <components/misc/rng.hpp>
#include <components/esm/aisequence.hpp>
@ -33,6 +35,8 @@ namespace MWMechanics
// distance must be long enough that NPC will need to move to get there.
static const int MINIMUM_WANDER_DISTANCE = DESTINATION_TOLERANCE * 2;
static const std::size_t MAX_IDLE_SIZE = 8;
const std::string AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] =
{
std::string("idle2"),
@ -94,30 +98,29 @@ namespace MWMechanics
{
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
}
}
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle),
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)),
mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)), mUsePathgrid(false)
{
mIdle.resize(8, 0);
init();
}
void AiWander::init()
{
// NOTE: mDistance and mDuration must be set already
std::vector<unsigned char> getInitialIdle(const std::vector<unsigned char>& idle)
{
std::vector<unsigned char> result(MAX_IDLE_SIZE, 0);
std::copy_n(idle.begin(), std::min(MAX_IDLE_SIZE, idle.size()), result.begin());
return result;
}
if(mDistance < 0)
mDistance = 0;
if(mDuration < 0)
mDuration = 0;
std::vector<unsigned char> getInitialIdle(const unsigned char (&idle)[MAX_IDLE_SIZE])
{
return std::vector<unsigned char>(std::begin(idle), std::end(idle));
}
}
AiPackage * MWMechanics::AiWander::clone() const
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
TypedAiPackage<AiWander>(makeDefaultOptions().withRepeat(repeat)),
mDistance(std::max(0, distance)),
mDuration(std::max(0, duration)),
mRemainingDuration(duration), mTimeOfDay(timeOfDay),
mIdle(getInitialIdle(idle)),
mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)),
mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)), mUsePathgrid(false)
{
return new AiWander(*this);
}
/*
@ -240,7 +243,6 @@ namespace MWMechanics
stopWalking(actor);
// Reset package so it can be used again
mRemainingDuration=mDuration;
init();
return true;
}
@ -308,11 +310,6 @@ namespace MWMechanics
return false; // AiWander package not yet completed
}
bool AiWander::getRepeat() const
{
return mRepeat;
}
osg::Vec3f AiWander::getDestination(const MWWorld::Ptr& actor) const
{
if (mHasDestination)
@ -598,11 +595,6 @@ namespace MWMechanics
}
}
int AiWander::getTypeId() const
{
return TypeIdWander;
}
void AiWander::stopWalking(const MWWorld::Ptr& actor)
{
mPathFinder.clearPath();
@ -872,7 +864,7 @@ namespace MWMechanics
assert (mIdle.size() == 8);
for (int i=0; i<8; ++i)
wander->mData.mIdle[i] = mIdle[i];
wander->mData.mShouldRepeat = mRepeat;
wander->mData.mShouldRepeat = mOptions.mRepeat;
wander->mStoredInitialActorPosition = mStoredInitialActorPosition;
if (mStoredInitialActorPosition)
wander->mInitialActorPosition = mInitialActorPosition;
@ -884,11 +876,12 @@ namespace MWMechanics
}
AiWander::AiWander (const ESM::AiSequence::AiWander* wander)
: mDistance(wander->mData.mDistance)
, mDuration(wander->mData.mDuration)
: TypedAiPackage<AiWander>(makeDefaultOptions().withRepeat(wander->mData.mShouldRepeat != 0))
, mDistance(std::max(static_cast<short>(0), wander->mData.mDistance))
, mDuration(std::max(static_cast<short>(0), wander->mData.mDuration))
, mRemainingDuration(wander->mDurationData.mRemainingDuration)
, mTimeOfDay(wander->mData.mTimeOfDay)
, mRepeat(wander->mData.mShouldRepeat != 0)
, mIdle(getInitialIdle(wander->mData.mIdle))
, mStoredInitialActorPosition(wander->mStoredInitialActorPosition)
, mHasDestination(false)
, mDestination(osg::Vec3f(0, 0, 0))
@ -896,11 +889,7 @@ namespace MWMechanics
{
if (mStoredInitialActorPosition)
mInitialActorPosition = wander->mInitialActorPosition;
for (int i=0; i<8; ++i)
mIdle.push_back(wander->mData.mIdle[i]);
if (mRemainingDuration <= 0 || mRemainingDuration >= 24)
mRemainingDuration = mDuration;
init();
}
}

@ -1,7 +1,7 @@
#ifndef GAME_MWMECHANICS_AIWANDER_H
#define GAME_MWMECHANICS_AIWANDER_H
#include "aipackage.hpp"
#include "typedaipackage.hpp"
#include <vector>
@ -78,7 +78,7 @@ namespace MWMechanics
};
/// \brief Causes the Actor to wander within a specified range
class AiWander final : public AiPackage
class AiWander final : public TypedAiPackage<AiWander>
{
public:
/// Constructor
@ -91,20 +91,22 @@ namespace MWMechanics
AiWander (const ESM::AiSequence::AiWander* wander);
AiPackage *clone() const final;
bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final;
int getTypeId() const final;
static constexpr TypeId getTypeId() { return TypeIdWander; }
bool useVariableSpeed() const final { return true; }
static constexpr Options makeDefaultOptions()
{
AiPackage::Options options;
options.mUseVariableSpeed = true;
options.mRepeat = false;
return options;
}
void writeState(ESM::AiSequence::AiSequence &sequence) const final;
void fastForward(const MWWorld::Ptr& actor, AiState& state) final;
bool getRepeat() const final;
osg::Vec3f getDestination(const MWWorld::Ptr& actor) const final;
osg::Vec3f getDestination() const final
@ -116,8 +118,6 @@ namespace MWMechanics
}
private:
// NOTE: mDistance and mDuration must be set already
void init();
void stopWalking(const MWWorld::Ptr& actor);
/// Have the given actor play an idle animation
@ -138,12 +138,11 @@ namespace MWMechanics
bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination);
void completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage);
int mDistance; // how far the actor can wander from the spawn point
int mDuration;
const int mDistance; // how far the actor can wander from the spawn point
const int mDuration;
float mRemainingDuration;
int mTimeOfDay;
std::vector<unsigned char> mIdle;
bool mRepeat;
const int mTimeOfDay;
const std::vector<unsigned char> mIdle;
bool mStoredInitialActorPosition;
osg::Vec3f mInitialActorPosition; // Note: an original engine does not reset coordinates even when actor changes a cell
@ -178,7 +177,7 @@ namespace MWMechanics
static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1];
static int OffsetToPreventOvercrowding();
};
};
}
#endif

@ -507,7 +507,7 @@ MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::endEffects() const
bool MWMechanics::Alchemy::knownEffect(unsigned int potionEffectIndex, const MWWorld::Ptr &npc)
{
int alchemySkill = npc.getClass().getSkill (npc, ESM::Skill::Alchemy);
float alchemySkill = npc.getClass().getSkill (npc, ESM::Skill::Alchemy);
static const float fWortChanceValue =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fWortChanceValue")->mValue.getFloat();
return (potionEffectIndex <= 1 && alchemySkill >= fWortChanceValue)

@ -2319,7 +2319,7 @@ void CharacterController::update(float duration, bool animationOnly)
cls.onHit(mPtr, realHealthLost, true, MWWorld::Ptr(), MWWorld::Ptr(), osg::Vec3f(), true);
}
const int acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics);
const float acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics);
if (healthLost > (acrobaticsSkill * fatigueTerm))
{
if (!godmode)

@ -116,7 +116,7 @@ namespace MWMechanics
blockerTerm *= gmst.find("fBlockStillBonus")->mValue.getFloat();
blockerTerm *= blockerStats.getFatigueTerm();
int attackerSkill = 0;
float attackerSkill = 0;
if (weapon.isEmpty())
attackerSkill = attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand);
else

@ -137,7 +137,7 @@ namespace MWMechanics
return mMagicEffects;
}
void CreatureStats::setAttribute(int index, int base)
void CreatureStats::setAttribute(int index, float base)
{
AttributeValue current = getAttribute(index);
current.setBase(base);
@ -163,10 +163,10 @@ namespace MWMechanics
index == ESM::Attribute::Agility ||
index == ESM::Attribute::Endurance)
{
int strength = getAttribute(ESM::Attribute::Strength).getModified();
int willpower = getAttribute(ESM::Attribute::Willpower).getModified();
int agility = getAttribute(ESM::Attribute::Agility).getModified();
int endurance = getAttribute(ESM::Attribute::Endurance).getModified();
float strength = getAttribute(ESM::Attribute::Strength).getModified();
float willpower = getAttribute(ESM::Attribute::Willpower).getModified();
float agility = getAttribute(ESM::Attribute::Agility).getModified();
float endurance = getAttribute(ESM::Attribute::Endurance).getModified();
DynamicStat<float> fatigue = getFatigue();
float diff = (strength+willpower+agility+endurance) - fatigue.getBase();
float currentToBaseRatio = fatigue.getBase() > 0 ? (fatigue.getCurrent() / fatigue.getBase()) : 0;
@ -575,6 +575,14 @@ namespace MWMechanics
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)
@ -613,7 +621,7 @@ namespace MWMechanics
mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath);
//mHitAttemptActorId = state.mHitAttemptActorId;
mSpells.readState(state.mSpells);
mSpells.readState(state.mSpells, this);
mActiveSpells.readState(state.mActiveSpells);
mAiSequence.readState(state.mAiSequence);
mMagicEffects.readState(state.mMagicEffects);
@ -624,6 +632,15 @@ namespace MWMechanics
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)
@ -722,4 +739,23 @@ namespace MWMechanics
/*
End of tes3mp addition
*/
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);
}
}
}

@ -12,6 +12,8 @@
#include "aisequence.hpp"
#include "drawstate.hpp"
#include <components/esm/attr.hpp>
namespace ESM
{
struct CreatureStats;
@ -19,6 +21,14 @@ namespace ESM
namespace MWMechanics
{
struct CorprusStats
{
static const int sWorseningPeriod = 24;
int mWorsenings[ESM::Attribute::Length];
MWWorld::TimeStamp mNextWorsening;
};
/// \brief Common creature stats
///
///
@ -26,7 +36,7 @@ namespace MWMechanics
{
static int sActorId;
DrawState_ mDrawState;
AttributeValue mAttributes[8];
AttributeValue mAttributes[ESM::Attribute::Length];
DynamicStat<float> mDynamic[3]; // health, magicka, fatigue
Spells mSpells;
ActiveSpells mActiveSpells;
@ -79,6 +89,8 @@ namespace MWMechanics
// 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;
@ -126,7 +138,7 @@ namespace MWMechanics
void setAttribute(int index, const AttributeValue &value);
// Shortcut to set only the base
void setAttribute(int index, int base);
void setAttribute(int index, float base);
void setHealth(const DynamicStat<float> &value);
@ -301,6 +313,12 @@ namespace MWMechanics
/// assigned this function will return false).
static void cleanup();
std::map<std::string, CorprusStats> & getCorprusSpells();
void addCorprusSpell(const std::string& sourceId, CorprusStats& stats);
void removeCorprusSpell(const std::string& sourceId);
};
}

@ -303,6 +303,24 @@ namespace MWMechanics
mWatched = ptr;
}
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)
{
if(!mWatched.isEmpty())
@ -684,10 +702,10 @@ namespace MWMechanics
// I suppose the temporary disposition change (second param to getDerivedDisposition()) _has_ to be considered here,
// otherwise one would get different prices when exiting and re-entering the dialogue window...
int clampedDisposition = getDerivedDisposition(ptr);
float a = static_cast<float>(std::min(playerPtr.getClass().getSkill(playerPtr, ESM::Skill::Mercantile), 100));
float a = std::min(playerPtr.getClass().getSkill(playerPtr, ESM::Skill::Mercantile), 100.f);
float b = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f);
float c = std::min(0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f);
float d = static_cast<float>(std::min(ptr.getClass().getSkill(ptr, ESM::Skill::Mercantile), 100));
float d = std::min(ptr.getClass().getSkill(ptr, ESM::Skill::Mercantile), 100.f);
float e = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f);
float f = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f);
float pcTerm = (clampedDisposition - 50 + a + b + c) * playerStats.getFatigueTerm();
@ -1262,7 +1280,7 @@ namespace MWMechanics
if (!Misc::StringUtils::ciEqual(item.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId))
{
if (victim.isEmpty() || (victim.getClass().isActor() && !victim.getClass().getCreatureStats(victim).isDead()))
if (victim.isEmpty() || (victim.getClass().isActor() && victim.getRefData().getCount() > 0 && !victim.getClass().getCreatureStats(victim).isDead()))
mStolenItems[Misc::StringUtils::lowerCase(item.getCellRef().getRefId())][owner] += count;
}
if (alarm)
@ -1743,8 +1761,8 @@ namespace MWMechanics
static float fSneakSkillMult = store.find("fSneakSkillMult")->mValue.getFloat();
static float fSneakBootMult = store.find("fSneakBootMult")->mValue.getFloat();
float sneak = static_cast<float>(ptr.getClass().getSkill(ptr, ESM::Skill::Sneak));
int agility = stats.getAttribute(ESM::Attribute::Agility).getModified();
int luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float agility = stats.getAttribute(ESM::Attribute::Agility).getModified();
float luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float bootWeight = 0;
if (ptr.getClass().isNpc() && MWBase::Environment::get().getWorld()->isOnGround(ptr))
{
@ -1767,10 +1785,10 @@ namespace MWMechanics
float x = sneakTerm * distTerm * stats.getFatigueTerm() + chameleon + invisibility;
CreatureStats& observerStats = observer.getClass().getCreatureStats(observer);
int obsAgility = observerStats.getAttribute(ESM::Attribute::Agility).getModified();
int obsLuck = observerStats.getAttribute(ESM::Attribute::Luck).getModified();
float obsAgility = observerStats.getAttribute(ESM::Attribute::Agility).getModified();
float obsLuck = observerStats.getAttribute(ESM::Attribute::Luck).getModified();
float obsBlind = observerStats.getMagicEffects().get(ESM::MagicEffect::Blind).getMagnitude();
int obsSneak = observer.getClass().getSkill(observer, ESM::Skill::Sneak);
float obsSneak = observer.getClass().getSkill(observer, ESM::Skill::Sneak);
float obsTerm = obsSneak + 0.2f * obsAgility + 0.1f * obsLuck - obsBlind;

@ -277,6 +277,8 @@ namespace MWMechanics
virtual GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override;
virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override;
virtual 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);

@ -226,9 +226,9 @@ void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_,
void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &class_, bool preserveProgress, bool readBook)
{
int base = getSkill (skillIndex).getBase();
float base = getSkill (skillIndex).getBase();
if (base >= 100)
if (base >= 100.f)
return;
base += 1;
@ -265,7 +265,7 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas
MWBase::Environment::get().getWindowManager()->playSound("skillraise");
std::string message = MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", "");
message = Misc::StringUtils::format(message, ("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}"), base);
message = Misc::StringUtils::format(message, ("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}"), static_cast<int>(base));
if (readBook)
message = "#{sBookSkillMessage}\n" + message;
@ -360,7 +360,7 @@ void MWMechanics::NpcStats::levelUp()
for (int i=0; i<ESM::Attribute::Length; ++i)
mSkillIncreases[i] = 0;
const int endurance = getAttribute(ESM::Attribute::Endurance).getBase();
const float endurance = getAttribute(ESM::Attribute::Endurance).getBase();
// "When you gain a level, in addition to increasing three primary attributes, your Health
// will automatically increase by 10% of your Endurance attribute. If you increased Endurance this level,
@ -377,8 +377,8 @@ void MWMechanics::NpcStats::levelUp()
void MWMechanics::NpcStats::updateHealth()
{
const int endurance = getAttribute(ESM::Attribute::Endurance).getBase();
const int strength = getAttribute(ESM::Attribute::Strength).getBase();
const float endurance = getAttribute(ESM::Attribute::Endurance).getBase();
const float strength = getAttribute(ESM::Attribute::Strength).getBase();
setHealth(floor(0.5f * (strength + endurance)));
}

@ -22,8 +22,8 @@ namespace MWMechanics
float Pickpocket::getChanceModifier(const MWWorld::Ptr &ptr, float add)
{
NpcStats& stats = ptr.getClass().getNpcStats(ptr);
float agility = static_cast<float>(stats.getAttribute(ESM::Attribute::Agility).getModified());
float luck = static_cast<float>(stats.getAttribute(ESM::Attribute::Luck).getModified());
float agility = stats.getAttribute(ESM::Attribute::Agility).getModified();
float luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float sneak = static_cast<float>(ptr.getClass().getSkill(ptr, ESM::Skill::Sneak));
return (add + 0.2f * agility + 0.1f * luck + sneak) * stats.getFatigueTerm();
}

@ -45,9 +45,9 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair)
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
float fatigueTerm = stats.getFatigueTerm();
int pcStrength = stats.getAttribute(ESM::Attribute::Strength).getModified();
int pcLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
int armorerSkill = player.getClass().getSkill(player, ESM::Skill::Armorer);
float pcStrength = stats.getAttribute(ESM::Attribute::Strength).getModified();
float pcLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float armorerSkill = player.getClass().getSkill(player, ESM::Skill::Armorer);
float fRepairAmountMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fRepairAmountMult")->mValue.getFloat();

@ -33,8 +33,8 @@ namespace MWMechanics
: mActor(actor)
{
CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor);
mAgility = static_cast<float>(creatureStats.getAttribute(ESM::Attribute::Agility).getModified());
mLuck = static_cast<float>(creatureStats.getAttribute(ESM::Attribute::Luck).getModified());
mAgility = creatureStats.getAttribute(ESM::Attribute::Agility).getModified();
mLuck = creatureStats.getAttribute(ESM::Attribute::Luck).getModified();
mSecuritySkill = static_cast<float>(actor.getClass().getSkill(actor, ESM::Skill::Security));
mFatigueTerm = creatureStats.getFatigueTerm();
}

@ -77,8 +77,13 @@ namespace MWMechanics
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded)
{
if (!target.isEmpty() && target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead())
return;
if (!target.isEmpty() && target.getClass().isActor())
{
// Early-out for characters that have departed.
const auto& stats = target.getClass().getCreatureStats(target);
if (stats.isDead() && stats.isDeathAnimationFinished())
return;
}
// If none of the effects need to apply, we can early-out
bool found = false;
@ -218,9 +223,9 @@ namespace MWMechanics
}
bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
if (castByPlayer && target != caster && effectAffectsHealth)
if (castByPlayer && target != caster && !target.getClass().getCreatureStats(target).isDead() && effectAffectsHealth)
{
// If player is attempting to cast a harmful spell or is healing someone, show the target's HP bar.
// If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar.
MWBase::Environment::get().getWindowManager()->setEnemy(target);
}

@ -40,8 +40,8 @@ namespace MWMechanics
float resistance = getEffectResistanceAttribute(effectId, magicEffects);
int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
float luck = static_cast<float>(stats.getAttribute(ESM::Attribute::Luck).getModified());
float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
float luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float x = (willpower + 0.1f * luck) * stats.getFatigueTerm();
// This makes spells that are easy to cast harder to resist and vice versa

@ -18,9 +18,13 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "actorutil.hpp"
#include "creaturestats.hpp"
#include "magiceffects.hpp"
#include "stat.hpp"
namespace MWMechanics
{
@ -74,12 +78,6 @@ namespace MWMechanics
}
}
}
for (std::map<SpellKey, MagicEffects>::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it)
{
mEffects += it->second;
mSourcedEffects[it->first] += it->second;
}
}
bool Spells::hasSpell(const std::string &spell) const
@ -112,15 +110,6 @@ namespace MWMechanics
}
}
if (hasCorprusEffect(spell))
{
CorprusStats corprus;
corprus.mWorsenings = 0;
corprus.mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod;
mCorprusSpells[spell] = corprus;
}
SpellParams params;
params.mEffectRands = random;
mSpells.insert (std::make_pair (spell, params));
@ -138,27 +127,6 @@ namespace MWMechanics
const ESM::Spell* spell = getSpell(spellId);
TContainer::iterator iter = mSpells.find (spell);
std::map<SpellKey, CorprusStats>::iterator corprusIt = mCorprusSpells.find(spell);
// if it's corprus, remove negative and keep positive effects
if (corprusIt != mCorprusSpells.end())
{
worsenCorprus(spell);
if (mPermanentSpellEffects.find(spell) != mPermanentSpellEffects.end())
{
MagicEffects & effects = mPermanentSpellEffects[spell];
for (MagicEffects::Collection::const_iterator effectIt = effects.begin(); effectIt != effects.end();)
{
const ESM::MagicEffect * magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectIt->first.mId);
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
effects.remove((effectIt++)->first);
else
++effectIt;
}
}
mCorprusSpells.erase(corprusIt);
}
if (iter!=mSpells.end())
{
mSpells.erase (iter);
@ -371,31 +339,6 @@ namespace MWMechanics
}
}
void Spells::worsenCorprus(const ESM::Spell* spell)
{
mCorprusSpells[spell].mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod;
mCorprusSpells[spell].mWorsenings++;
// update worsened effects
mPermanentSpellEffects[spell] = MagicEffects();
int i=0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt, ++i)
{
const ESM::MagicEffect * magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectIt->mEffectID);
if ((effectIt->mEffectID != ESM::MagicEffect::Corprus) && (magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce))
{
float random = 1.f;
if (mSpells[spell].mEffectRands.find(i) != mSpells[spell].mEffectRands.end())
random = mSpells[spell].mEffectRands.at(i);
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random;
magnitude *= std::max(1, mCorprusSpells[spell].mWorsenings);
mPermanentSpellEffects[spell].add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(magnitude));
mSpellsChanged = true;
}
}
}
bool Spells::hasCorprusEffect(const ESM::Spell *spell)
{
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt)
@ -408,11 +351,6 @@ namespace MWMechanics
return false;
}
const std::map<Spells::SpellKey, Spells::CorprusStats> &Spells::getCorprusSpells() const
{
return mCorprusSpells;
}
void Spells::purgeEffect(int effectId)
{
for (TContainer::iterator spellIt = mSpells.begin(); spellIt != mSpells.end(); ++spellIt)
@ -463,7 +401,7 @@ namespace MWMechanics
mUsedPowers[spell] = MWBase::Environment::get().getWorld()->getTimeStamp();
}
void Spells::readState(const ESM::SpellState &state)
void Spells::readState(const ESM::SpellState &state, CreatureStats* creatureStats)
{
for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it)
{
@ -487,31 +425,64 @@ namespace MWMechanics
mUsedPowers[spell] = MWWorld::TimeStamp(it->second);
}
for (std::map<std::string, std::vector<ESM::SpellState::PermanentSpellEffectInfo> >::const_iterator it =
state.mPermanentSpellEffects.begin(); it != state.mPermanentSpellEffects.end(); ++it)
for (std::map<std::string, ESM::SpellState::CorprusStats>::const_iterator it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it)
{
const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(it->first);
if (!spell)
continue;
mPermanentSpellEffects[spell] = MagicEffects();
for (std::vector<ESM::SpellState::PermanentSpellEffectInfo>::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt)
CorprusStats stats;
int worsening = state.mCorprusSpells.at(it->first).mWorsenings;
for (int i=0; i<ESM::Attribute::Length; ++i)
stats.mWorsenings[i] = 0;
for (auto& effect : spell->mEffects.mList)
{
mPermanentSpellEffects[spell].add(EffectKey(effectIt->mId, effectIt->mArg), effectIt->mMagnitude);
if (effect.mEffectID == ESM::MagicEffect::DrainAttribute)
stats.mWorsenings[effect.mAttribute] = worsening;
}
stats.mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening);
creatureStats->addCorprusSpell(it->first, stats);
}
mCorprusSpells.clear();
for (std::map<std::string, ESM::SpellState::CorprusStats>::const_iterator it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it)
mSpellsChanged = true;
// Permanent effects are used only to keep the custom magnitude of corprus spells effects (after cure too), and only in old saves. Convert data to the new approach.
for (std::map<std::string, std::vector<ESM::SpellState::PermanentSpellEffectInfo> >::const_iterator it =
state.mPermanentSpellEffects.begin(); it != state.mPermanentSpellEffects.end(); ++it)
{
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(it->first);
if (!spell) // Discard unavailable corprus spells
const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(it->first);
if (!spell)
continue;
mCorprusSpells[spell].mWorsenings = state.mCorprusSpells.at(it->first).mWorsenings;
mCorprusSpells[spell].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening);
}
mSpellsChanged = true;
// Import data only for player, other actors should not suffer from corprus worsening.
MWWorld::Ptr player = getPlayer();
if (creatureStats->getActorId() != player.getClass().getCreatureStats(player).getActorId())
return;
// Note: if target actor has the Restore attirbute effects, stats will be restored.
for (std::vector<ESM::SpellState::PermanentSpellEffectInfo>::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt)
{
// Applied corprus effects are already in loaded stats modifiers
if (effectIt->mId == ESM::MagicEffect::FortifyAttribute)
{
AttributeValue attr = creatureStats->getAttribute(effectIt->mArg);
attr.setModifier(attr.getModifier() - effectIt->mMagnitude);
attr.damage(-effectIt->mMagnitude);
creatureStats->setAttribute(effectIt->mArg, attr);
}
else if (effectIt->mId == ESM::MagicEffect::DrainAttribute)
{
AttributeValue attr = creatureStats->getAttribute(effectIt->mArg);
attr.setModifier(attr.getModifier() + effectIt->mMagnitude);
attr.damage(effectIt->mMagnitude);
creatureStats->setAttribute(effectIt->mArg, attr);
}
}
}
}
void Spells::writeState(ESM::SpellState &state) const
@ -528,26 +499,5 @@ namespace MWMechanics
for (std::map<SpellKey, MWWorld::TimeStamp>::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it)
state.mUsedPowers[it->first->mId] = it->second.toEsm();
for (std::map<SpellKey, MagicEffects>::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it)
{
std::vector<ESM::SpellState::PermanentSpellEffectInfo> effectList;
for (MagicEffects::Collection::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt)
{
ESM::SpellState::PermanentSpellEffectInfo info;
info.mId = effectIt->first.mId;
info.mArg = effectIt->first.mArg;
info.mMagnitude = effectIt->second.getModifier();
effectList.push_back(info);
}
state.mPermanentSpellEffects[it->first->mId] = effectList;
}
for (std::map<SpellKey, CorprusStats>::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it)
{
state.mCorprusSpells[it->first->mId].mWorsenings = mCorprusSpells.at(it->first).mWorsenings;
state.mCorprusSpells[it->first->mId].mNextWorsening = mCorprusSpells.at(it->first).mNextWorsening.toEsm();
}
}
}

@ -22,6 +22,8 @@ namespace ESM
namespace MWMechanics
{
class CreatureStats;
class MagicEffects;
/// \brief Spell list
@ -33,7 +35,8 @@ namespace MWMechanics
public:
typedef const ESM::Spell* SpellKey;
struct SpellParams {
struct SpellParams
{
std::map<int, float> mEffectRands; // <effect index, normalised random magnitude>
std::set<int> mPurgedEffects; // indices of purged effects
};
@ -41,27 +44,14 @@ namespace MWMechanics
typedef std::map<SpellKey, SpellParams> TContainer;
typedef TContainer::const_iterator TIterator;
struct CorprusStats
{
static const int sWorseningPeriod = 24;
int mWorsenings;
MWWorld::TimeStamp mNextWorsening;
};
private:
TContainer mSpells;
// spell-tied effects that will be applied even after removing the spell (currently used to keep positive effects when corprus is removed)
std::map<SpellKey, MagicEffects> mPermanentSpellEffects;
// Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different)
std::string mSelectedSpell;
std::map<SpellKey, MWWorld::TimeStamp> mUsedPowers;
std::map<SpellKey, CorprusStats> mCorprusSpells;
mutable bool mSpellsChanged;
mutable MagicEffects mEffects;
mutable std::map<SpellKey, MagicEffects> mSourcedEffects;
@ -73,9 +63,7 @@ namespace MWMechanics
public:
Spells();
void worsenCorprus(const ESM::Spell* spell);
static bool hasCorprusEffect(const ESM::Spell *spell);
const std::map<SpellKey, CorprusStats> & getCorprusSpells() const;
void purgeEffect(int effectId);
void purgeEffect(int effectId, const std::string & sourceId);
@ -128,7 +116,7 @@ namespace MWMechanics
void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const;
void readState (const ESM::SpellState& state);
void readState (const ESM::SpellState& state, CreatureStats* creatureStats);
void writeState (ESM::SpellState& state) const;
};
}

@ -94,8 +94,8 @@ namespace MWMechanics
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
float actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float castChance = (lowestSkill - spell->mData.mCost + 0.2f * actorWillpower + 0.1f * actorLuck);

@ -227,39 +227,46 @@ namespace MWMechanics
}
AttributeValue::AttributeValue() :
mBase(0), mModifier(0), mDamage(0)
mBase(0.f), mModifier(0.f), mDamage(0.f)
{
}
int AttributeValue::getModified() const
float AttributeValue::getModified() const
{
return std::max(0, mBase - (int) mDamage + mModifier);
return std::max(0.f, mBase - mDamage + mModifier);
}
int AttributeValue::getBase() const
float AttributeValue::getBase() const
{
return mBase;
}
int AttributeValue::getModifier() const
float AttributeValue::getModifier() const
{
return mModifier;
}
void AttributeValue::setBase(int base)
void AttributeValue::setBase(float base)
{
mBase = base;
}
void AttributeValue::setModifier(int mod)
void AttributeValue::setModifier(float mod)
{
mModifier = mod;
}
void AttributeValue::damage(float damage)
{
mDamage += std::min(damage, (float)getModified());
float threshold = mBase + mModifier;
if (mDamage + damage > threshold)
mDamage = threshold;
else
mDamage += damage;
}
void AttributeValue::restore(float amount)
{
if (mDamage <= 0) return;
mDamage -= std::min(mDamage, amount);
}
@ -268,14 +275,14 @@ namespace MWMechanics
return mDamage;
}
void AttributeValue::writeState (ESM::StatState<int>& state) const
void AttributeValue::writeState (ESM::StatState<float>& state) const
{
state.mBase = mBase;
state.mMod = mModifier;
state.mDamage = mDamage;
}
void AttributeValue::readState (const ESM::StatState<int>& state)
void AttributeValue::readState (const ESM::StatState<float>& state)
{
mBase = state.mBase;
mModifier = state.mMod;
@ -296,13 +303,13 @@ namespace MWMechanics
mProgress = progress;
}
void SkillValue::writeState (ESM::StatState<int>& state) const
void SkillValue::writeState (ESM::StatState<float>& state) const
{
AttributeValue::writeState (state);
state.mProgress = mProgress;
}
void SkillValue::readState (const ESM::StatState<int>& state)
void SkillValue::readState (const ESM::StatState<float>& state)
{
AttributeValue::readState (state);
mProgress = state.mProgress;

@ -122,20 +122,20 @@ namespace MWMechanics
class AttributeValue
{
int mBase;
int mModifier;
float mBase;
float mModifier;
float mDamage; // needs to be float to allow continuous damage
public:
AttributeValue();
int getModified() const;
int getBase() const;
int getModifier() const;
float getModified() const;
float getBase() const;
float getModifier() const;
void setBase(int base);
void setBase(float base);
void setModifier(int mod);
void setModifier(float mod);
// Maximum attribute damage is limited to the modified value.
// Note: I think MW applies damage directly to mModified, since you can also
@ -145,8 +145,8 @@ namespace MWMechanics
float getDamage() const;
void writeState (ESM::StatState<int>& state) const;
void readState (const ESM::StatState<int>& state);
void writeState (ESM::StatState<float>& state) const;
void readState (const ESM::StatState<float>& state);
};
class SkillValue : public AttributeValue
@ -157,8 +157,8 @@ namespace MWMechanics
float getProgress() const;
void setProgress(float progress);
void writeState (ESM::StatState<int>& state) const;
void readState (const ESM::StatState<int>& state);
void writeState (ESM::StatState<float>& state) const;
void readState (const ESM::StatState<float>& state);
};
inline bool operator== (const AttributeValue& left, const AttributeValue& right)

@ -0,0 +1,28 @@
#ifndef GAME_MWMECHANICS_TYPEDAIPACKAGE_H
#define GAME_MWMECHANICS_TYPEDAIPACKAGE_H
#include "aipackage.hpp"
namespace MWMechanics
{
template <class T>
struct TypedAiPackage : public AiPackage
{
TypedAiPackage() :
AiPackage(T::getTypeId(), T::makeDefaultOptions()) {}
TypedAiPackage(const Options& options) :
AiPackage(T::getTypeId(), options) {}
template <class Derived>
TypedAiPackage(Derived*) :
AiPackage(Derived::getTypeId(), Derived::makeDefaultOptions()) {}
virtual std::unique_ptr<AiPackage> clone() const override
{
return std::make_unique<T>(*static_cast<const T*>(this));
}
};
}
#endif

@ -20,6 +20,7 @@ namespace mwmp
{
MWBase::World *world = MWBase::Environment::get().getWorld();
/*
if (worldstate.time.hour != -1)
world->setHour(worldstate.time.hour);
@ -37,6 +38,7 @@ namespace mwmp
if (worldstate.time.timeScale != -1)
world->setTimeScale(worldstate.time.timeScale);
*/
}
};
}

@ -0,0 +1,104 @@
#include "fogmanager.hpp"
#include <algorithm>
#include <components/esm/loadcell.hpp>
#include <components/fallback/fallback.hpp>
#include <components/sceneutil/util.hpp>
#include <components/settings/settings.hpp>
namespace
{
float DLLandFogStart;
float DLLandFogEnd;
float DLUnderwaterFogStart;
float DLUnderwaterFogEnd;
float DLInteriorFogStart;
float DLInteriorFogEnd;
}
namespace MWRender
{
FogManager::FogManager()
: mLandFogStart(0.f)
, mLandFogEnd(std::numeric_limits<float>::max())
, mUnderwaterFogStart(0.f)
, mUnderwaterFogEnd(std::numeric_limits<float>::max())
, mFogColor(osg::Vec4f())
, mDistantFog(Settings::Manager::getBool("use distant fog", "Fog"))
, mUnderwaterColor(Fallback::Map::getColour("Water_UnderwaterColor"))
, mUnderwaterWeight(Fallback::Map::getFloat("Water_UnderwaterColorWeight"))
, mUnderwaterIndoorFog(Fallback::Map::getFloat("Water_UnderwaterIndoorFog"))
{
DLLandFogStart = Settings::Manager::getFloat("distant land fog start", "Fog");
DLLandFogEnd = Settings::Manager::getFloat("distant land fog end", "Fog");
DLUnderwaterFogStart = Settings::Manager::getFloat("distant underwater fog start", "Fog");
DLUnderwaterFogEnd = Settings::Manager::getFloat("distant underwater fog end", "Fog");
DLInteriorFogStart = Settings::Manager::getFloat("distant interior fog start", "Fog");
DLInteriorFogEnd = Settings::Manager::getFloat("distant interior fog end", "Fog");
}
void FogManager::configure(float viewDistance, const ESM::Cell *cell)
{
osg::Vec4f color = SceneUtil::colourFromRGB(cell->mAmbi.mFog);
if (mDistantFog)
{
float density = std::max(0.2f, cell->mAmbi.mFogDensity);
mLandFogStart = DLInteriorFogEnd * (1.0f - density) + DLInteriorFogStart*density;
mLandFogEnd = DLInteriorFogEnd;
mUnderwaterFogStart = DLUnderwaterFogStart;
mUnderwaterFogEnd = DLUnderwaterFogEnd;
mFogColor = color;
}
else
configure(viewDistance, cell->mAmbi.mFogDensity, mUnderwaterIndoorFog, 1.0f, 0.0f, color);
}
void FogManager::configure(float viewDistance, float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f &color)
{
if (mDistantFog)
{
mLandFogStart = dlFactor * (DLLandFogStart - dlOffset * DLLandFogEnd);
mLandFogEnd = dlFactor * (1.0f - dlOffset) * DLLandFogEnd;
mUnderwaterFogStart = DLUnderwaterFogStart;
mUnderwaterFogEnd = DLUnderwaterFogEnd;
}
else
{
if (fogDepth == 0.0)
{
mLandFogStart = 0.0f;
mLandFogEnd = std::numeric_limits<float>::max();
}
else
{
mLandFogStart = viewDistance * (1 - fogDepth);
mLandFogEnd = viewDistance;
}
mUnderwaterFogStart = std::min(viewDistance, 6666.f) * (1 - underwaterFog);
mUnderwaterFogEnd = std::min(viewDistance, 6666.f);
}
mFogColor = color;
}
float FogManager::getFogStart(bool isUnderwater) const
{
return isUnderwater ? mUnderwaterFogStart : mLandFogStart;
}
float FogManager::getFogEnd(bool isUnderwater) const
{
return isUnderwater ? mUnderwaterFogEnd : mLandFogEnd;
}
osg::Vec4f FogManager::getFogColor(bool isUnderwater) const
{
if (isUnderwater)
{
return mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight);
}
return mFogColor;
}
}

@ -0,0 +1,39 @@
#ifndef OPENMW_MWRENDER_FOGMANAGER_H
#define OPENMW_MWRENDER_FOGMANAGER_H
#include <osg/Vec4f>
namespace ESM
{
struct Cell;
}
namespace MWRender
{
class FogManager
{
public:
FogManager();
void configure(float viewDistance, const ESM::Cell *cell);
void configure(float viewDistance, float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f &color);
osg::Vec4f getFogColor(bool isUnderwater) const;
float getFogStart(bool isUnderwater) const;
float getFogEnd(bool isUnderwater) const;
private:
float mLandFogStart;
float mLandFogEnd;
float mUnderwaterFogStart;
float mUnderwaterFogEnd;
osg::Vec4f mFogColor;
bool mDistantFog;
osg::Vec4f mUnderwaterColor;
float mUnderwaterWeight;
float mUnderwaterIndoorFog;
};
}
#endif

@ -69,16 +69,7 @@
#include "navmesh.hpp"
#include "actorspaths.hpp"
#include "recastmesh.hpp"
namespace
{
float DLLandFogStart;
float DLLandFogEnd;
float DLUnderwaterFogStart;
float DLUnderwaterFogEnd;
float DLInteriorFogStart;
float DLInteriorFogEnd;
}
#include "fogmanager.hpp"
namespace MWRender
{
@ -204,19 +195,9 @@ namespace MWRender
, mWorkQueue(workQueue)
, mUnrefQueue(new SceneUtil::UnrefQueue)
, mNavigator(navigator)
, mLandFogStart(0.f)
, mLandFogEnd(std::numeric_limits<float>::max())
, mUnderwaterFogStart(0.f)
, mUnderwaterFogEnd(std::numeric_limits<float>::max())
, mUnderwaterColor(Fallback::Map::getColour("Water_UnderwaterColor"))
, mUnderwaterWeight(Fallback::Map::getFloat("Water_UnderwaterColorWeight"))
, mUnderwaterIndoorFog(Fallback::Map::getFloat("Water_UnderwaterIndoorFog"))
, mNightEyeFactor(0.f)
, mDistantFog(false)
, mDistantTerrain(false)
, mFieldOfViewOverridden(false)
, mFieldOfViewOverride(0.f)
, mBorders(false)
{
resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem);
resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders");
@ -284,16 +265,6 @@ namespace MWRender
mEffectManager.reset(new EffectManager(sceneRoot, mResourceSystem));
DLLandFogStart = Settings::Manager::getFloat("distant land fog start", "Fog");
DLLandFogEnd = Settings::Manager::getFloat("distant land fog end", "Fog");
DLUnderwaterFogStart = Settings::Manager::getFloat("distant underwater fog start", "Fog");
DLUnderwaterFogEnd = Settings::Manager::getFloat("distant underwater fog end", "Fog");
DLInteriorFogStart = Settings::Manager::getFloat("distant interior fog start", "Fog");
DLInteriorFogEnd = Settings::Manager::getFloat("distant interior fog end", "Fog");
mDistantFog = Settings::Manager::getBool("use distant fog", "Fog");
mDistantTerrain = Settings::Manager::getBool("distant terrain", "Terrain");
const std::string normalMapPattern = Settings::Manager::getString("normal map pattern", "Shaders");
const std::string heightMapPattern = Settings::Manager::getString("normal height map pattern", "Shaders");
const std::string specularMapPattern = Settings::Manager::getString("terrain specular map pattern", "Shaders");
@ -302,7 +273,7 @@ namespace MWRender
mTerrainStorage = new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps);
if (mDistantTerrain)
if (Settings::Manager::getBool("distant terrain", "Terrain"))
{
const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain");
int compMapPower = Settings::Manager::getInt("composite map level", "Terrain");
@ -349,8 +320,9 @@ namespace MWRender
defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f));
sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat);
mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager()));
mFog.reset(new FogManager());
mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager()));
mSky->setCamera(mViewer->getCamera());
mSky->setRainIntensityUniform(mWater->getRainIntensityUniform());
@ -558,9 +530,9 @@ namespace MWRender
bool RenderingManager::toggleBorders()
{
mBorders = !mBorders;
mTerrain->setBordersVisible(mBorders);
return mBorders;
bool borders = !mTerrain->getBordersVisible();
mTerrain->setBordersVisible(borders);
return borders;
}
bool RenderingManager::toggleRenderMode(RenderMode mode)
@ -606,46 +578,12 @@ namespace MWRender
void RenderingManager::configureFog(const ESM::Cell *cell)
{
osg::Vec4f color = SceneUtil::colourFromRGB(cell->mAmbi.mFog);
if(mDistantFog)
{
float density = std::max(0.2f, cell->mAmbi.mFogDensity);
mLandFogStart = (DLInteriorFogEnd*(1.0f-density) + DLInteriorFogStart*density);
mLandFogEnd = DLInteriorFogEnd;
mUnderwaterFogStart = DLUnderwaterFogStart;
mUnderwaterFogEnd = DLUnderwaterFogEnd;
mFogColor = color;
}
else
configureFog(cell->mAmbi.mFogDensity, mUnderwaterIndoorFog, 1.0f, 0.0f, color);
mFog->configure(mViewDistance, cell);
}
void RenderingManager::configureFog(float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f &color)
{
if(mDistantFog)
{
mLandFogStart = dlFactor * (DLLandFogStart - dlOffset*DLLandFogEnd);
mLandFogEnd = dlFactor * (1.0f-dlOffset) * DLLandFogEnd;
mUnderwaterFogStart = DLUnderwaterFogStart;
mUnderwaterFogEnd = DLUnderwaterFogEnd;
}
else
{
if(fogDepth == 0.0)
{
mLandFogStart = 0.0f;
mLandFogEnd = std::numeric_limits<float>::max();
}
else
{
mLandFogStart = mViewDistance * (1 - fogDepth);
mLandFogEnd = mViewDistance;
}
mUnderwaterFogStart = std::min(mViewDistance, 6666.f) * (1 - underwaterFog);
mUnderwaterFogEnd = std::min(mViewDistance, 6666.f);
}
mFogColor = color;
mFog->configure(mViewDistance, fogDepth, underwaterFog, dlFactor, dlOffset, color);
}
SkyManager* RenderingManager::getSkyManager()
@ -674,19 +612,11 @@ namespace MWRender
osg::Vec3f focal, cameraPos;
mCamera->getPosition(focal, cameraPos);
mCurrentCameraPos = cameraPos;
if (mWater->isUnderwater(cameraPos))
{
setFogColor(mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight));
mStateUpdater->setFogStart(mUnderwaterFogStart);
mStateUpdater->setFogEnd(mUnderwaterFogEnd);
}
else
{
setFogColor(mFogColor);
mStateUpdater->setFogStart(mLandFogStart);
mStateUpdater->setFogEnd(mLandFogEnd);
}
bool isUnderwater = mWater->isUnderwater(cameraPos);
mStateUpdater->setFogStart(mFog->getFogStart(isUnderwater));
mStateUpdater->setFogEnd(mFog->getFogEnd(isUnderwater));
setFogColor(mFog->getFogColor(isUnderwater));
}
void RenderingManager::updatePlayerPtr(const MWWorld::Ptr &ptr)
@ -1335,7 +1265,7 @@ namespace MWRender
else if (it->first == "Camera" && it->second == "viewing distance")
{
mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera");
if(!mDistantFog)
if(!Settings::Manager::getBool("use distant fog", "Fog"))
mStateUpdater->setFogEnd(mViewDistance);
updateProjectionMatrix();
}

@ -73,6 +73,7 @@ namespace MWRender
class StateUpdater;
class EffectManager;
class FogManager;
class SkyManager;
class NpcAnimation;
class Pathgrid;
@ -275,6 +276,7 @@ namespace MWRender
std::unique_ptr<Terrain::World> mTerrain;
TerrainStorage* mTerrainStorage;
std::unique_ptr<SkyManager> mSky;
std::unique_ptr<FogManager> mFog;
std::unique_ptr<EffectManager> mEffectManager;
std::unique_ptr<SceneUtil::ShadowManager> mShadowManager;
osg::ref_ptr<NpcAnimation> mPlayerAnimation;
@ -284,27 +286,15 @@ namespace MWRender
osg::ref_ptr<StateUpdater> mStateUpdater;
float mLandFogStart;
float mLandFogEnd;
float mUnderwaterFogStart;
float mUnderwaterFogEnd;
osg::Vec4f mUnderwaterColor;
float mUnderwaterWeight;
float mUnderwaterIndoorFog;
osg::Vec4f mFogColor;
osg::Vec4f mAmbientColor;
float mNightEyeFactor;
float mNearClip;
float mViewDistance;
bool mDistantFog : 1;
bool mDistantTerrain : 1;
bool mFieldOfViewOverridden : 1;
bool mFieldOfViewOverridden;
float mFieldOfViewOverride;
float mFieldOfView;
float mFirstPersonFieldOfView;
bool mBorders;
void operator = (const RenderingManager&);
RenderingManager(const RenderingManager&);

@ -271,8 +271,10 @@ namespace MWScript
Interpreter::Type_Integer value = runtime[0].mInteger;
runtime.pop();
ptr.getClass().getCreatureStats (ptr).setAiSetting (mIndex,
ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getBase() + value);
int modified = ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getBase() + value;
ptr.getClass().getCreatureStats (ptr).setAiSetting (mIndex, modified);
ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, modified);
}
};
template<class R>
@ -302,7 +304,7 @@ namespace MWScript
*/
stat.setModified(value, 0);
ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, stat);
ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, value);
/*
Start of tes3mp addition

@ -106,7 +106,7 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
Interpreter::Type_Integer value =
Interpreter::Type_Float value =
ptr.getClass()
.getCreatureStats (ptr)
.getAttribute(mIndex)
@ -129,7 +129,7 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
Interpreter::Type_Integer value = runtime[0].mInteger;
Interpreter::Type_Float value = runtime[0].mFloat;
runtime.pop();
MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex);
@ -151,7 +151,7 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
Interpreter::Type_Integer value = runtime[0].mInteger;
Interpreter::Type_Float value = runtime[0].mFloat;
runtime.pop();
MWMechanics::AttributeValue attribute = ptr.getClass()
@ -166,9 +166,9 @@ namespace MWScript
return;
if (value < 0)
attribute.setBase(std::max(0, attribute.getBase() + value));
attribute.setBase(std::max(0.f, attribute.getBase() + value));
else
attribute.setBase(std::min(100, attribute.getBase() + value));
attribute.setBase(std::min(100.f, attribute.getBase() + value));
ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute);
}
@ -356,7 +356,7 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
Interpreter::Type_Integer value = ptr.getClass().getSkill(ptr, mIndex);
Interpreter::Type_Float value = ptr.getClass().getSkill(ptr, mIndex);
runtime.push (value);
}
@ -375,7 +375,7 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
Interpreter::Type_Integer value = runtime[0].mInteger;
Interpreter::Type_Float value = runtime[0].mFloat;
runtime.pop();
MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats (ptr);
@ -397,7 +397,7 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
Interpreter::Type_Integer value = runtime[0].mInteger;
Interpreter::Type_Float value = runtime[0].mFloat;
runtime.pop();
MWMechanics::SkillValue &skill = ptr.getClass()
@ -407,14 +407,14 @@ namespace MWScript
if (value == 0)
return;
if (((skill.getBase() <= 0) && (value < 0))
|| ((skill.getBase() >= 100) && (value > 0)))
if (((skill.getBase() <= 0.f) && (value < 0.f))
|| ((skill.getBase() >= 100.f) && (value > 0.f)))
return;
if (value < 0)
skill.setBase(std::max(0, skill.getBase() + value));
skill.setBase(std::max(0.f, skill.getBase() + value));
else
skill.setBase(std::min(100, skill.getBase() + value));
skill.setBase(std::min(100.f, skill.getBase() + value));
}
};
@ -519,41 +519,48 @@ namespace MWScript
std::string id = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
/*
Start of tes3mp change (major)
Only remove the spell if the target has it
Send an ID_PLAYER_SPELLBOOK packet every time a player loses a spell here
*/
MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
MWMechanics::Spells &spells = creatureStats.getSpells();
MWMechanics::Spells& spells = creatureStats.getSpells();
if (spells.hasSpell(id))
if (!spells.hasSpell(id)) return;
/*
End of tes3mp change (major)
*/
// The spell may have an instant effect which must be handled before the spell's removal.
for (const auto& effect : creatureStats.getSpells().getMagicEffects())
{
// The spell may have an instant effect which must be handled before the spell's removal.
for (const auto& effect : creatureStats.getSpells().getMagicEffects())
{
if (effect.second.getMagnitude() <= 0)
continue;
MWMechanics::CastSpell cast(ptr, ptr);
if (cast.applyInstantEffect(ptr, ptr, effect.first, effect.second.getMagnitude()))
creatureStats.getSpells().purgeEffect(effect.first.mId);
}
creatureStats.getSpells().remove (id);
if (effect.second.getMagnitude() <= 0)
continue;
MWMechanics::CastSpell cast(ptr, ptr);
if (cast.applyInstantEffect(ptr, ptr, effect.first, effect.second.getMagnitude()))
creatureStats.getSpells().purgeEffect(effect.first.mId);
}
if (ptr == MWMechanics::getPlayer())
{
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
MWBase::Environment::get().getMechanicsManager()->restoreStatsAfterCorprus(ptr, id);
creatureStats.getSpells().remove (id);
if (id == wm->getSelectedSpell())
wm->unsetSelectedSpell();
MWBase::WindowManager* wm = MWBase::Environment::get().getWindowManager();
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
mwmp::Main::get().getLocalPlayer()->sendSpellChange(id, mwmp::SpellbookChanges::REMOVE);
}
if (ptr == MWMechanics::getPlayer() &&
id == wm->getSelectedSpell())
{
wm->unsetSelectedSpell();
}
/*
Start of tes3mp change (major)
Send an ID_PLAYER_SPELLBOOK packet every time a player loses a spell here
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
mwmp::Main::get().getLocalPlayer()->sendSpellChange(id, mwmp::SpellbookChanges::REMOVE);
/*
End of tes3mp change (major)
*/

@ -147,10 +147,11 @@ namespace MWSound
volume = static_cast<float>(pow(10.0, (sound->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0));
min = sound->mData.mMinRange;
max = sound->mData.mMaxRange;
if (min == 0)
if (min == 0 && max == 0)
{
min = fAudioDefaultMinDistance;
if (max == 0)
max = fAudioDefaultMaxDistance;
}
min *= fAudioMinDistanceMult;
max *= fAudioMaxDistanceMult;

@ -213,11 +213,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
profile.mPlayerClassId = classId;
profile.mPlayerCell = world.getCellName();
profile.mInGameTime.mGameHour = world.getTimeStamp().getHour();
profile.mInGameTime.mDay = world.getDay();
profile.mInGameTime.mMonth = world.getMonth();
profile.mInGameTime.mYear = world.getYear();
profile.mInGameTime = world.getEpochTimeStamp();
profile.mTimePlayed = mTimePlayed;
profile.mDescription = description;
@ -464,6 +460,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
case ESM::REC_ENAB:
case ESM::REC_LEVC:
case ESM::REC_LEVI:
case ESM::REC_CREA:
MWBase::Environment::get().getWorld()->readRecord(reader, n.intval, contentFileMap);
break;

@ -434,7 +434,7 @@ namespace MWWorld
return canSwim(ptr) || canWalk(ptr) || canFly(ptr);
}
int Class::getSkill(const MWWorld::Ptr& ptr, int skill) const
float Class::getSkill(const MWWorld::Ptr& ptr, int skill) const
{
throw std::runtime_error("class does not support skills");
}
@ -529,4 +529,9 @@ namespace MWWorld
result.z() = magicEffect->mData.mBlue / 255.f;
return result;
}
void Class::setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const
{
throw std::runtime_error ("class does not have creature stats");
}
}

@ -10,6 +10,7 @@
#include "ptr.hpp"
#include "doorstate.hpp"
#include "../mwmechanics/creaturestats.hpp"
namespace ESM
{
@ -28,7 +29,6 @@ namespace MWPhysics
namespace MWMechanics
{
class CreatureStats;
class NpcStats;
struct Movement;
}
@ -332,7 +332,7 @@ namespace MWWorld
bool isPureLandCreature(const MWWorld::Ptr& ptr) const;
bool isMobile(const MWWorld::Ptr& ptr) const;
virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const;
virtual float getSkill(const MWWorld::Ptr& ptr, int skill) const;
virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)
const;
@ -371,6 +371,8 @@ namespace MWWorld
virtual float getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const;
virtual osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const;
virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const;
};
}

@ -0,0 +1,266 @@
#include "datetimemanager.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "esmstore.hpp"
#include "globals.hpp"
#include "timestamp.hpp"
namespace
{
static int getDaysPerMonth(int month)
{
switch (month)
{
case 0: return 31;
case 1: return 28;
case 2: return 31;
case 3: return 30;
case 4: return 31;
case 5: return 30;
case 6: return 31;
case 7: return 31;
case 8: return 30;
case 9: return 31;
case 10: return 30;
case 11: return 31;
}
throw std::runtime_error ("month out of range");
}
}
namespace MWWorld
{
void DateTimeManager::setup(Globals& globalVariables)
{
mGameHour = globalVariables["gamehour"].getFloat();
mDaysPassed = globalVariables["dayspassed"].getInteger();
mDay = globalVariables["day"].getInteger();
mMonth = globalVariables["month"].getInteger();
mYear = globalVariables["year"].getInteger();
mTimeScale = globalVariables["timescale"].getFloat();
}
void DateTimeManager::setHour(double hour)
{
if (hour < 0)
hour = 0;
int days = static_cast<int>(hour / 24);
hour = std::fmod(hour, 24);
mGameHour = static_cast<float>(hour);
if (days > 0)
setDay(days + mDay);
}
void DateTimeManager::setDay(int day)
{
if (day < 1)
day = 1;
int month = mMonth;
while (true)
{
int days = getDaysPerMonth(month);
if (day <= days)
break;
if (month < 11)
{
++month;
}
else
{
month = 0;
mYear++;
}
day -= days;
}
mDay = day;
mMonth = month;
}
/*
Start of tes3mp addition
Make it possible to set the year from elsewhere
*/
void DateTimeManager::setYear(int year)
{
mYear = year;
}
/*
End of tes3mp addition
*/
/*
Start of tes3mp addition
Make it possible to set the number of days passed from elsewhere
*/
void DateTimeManager::setDaysPassed(int days)
{
mDaysPassed = days;
}
/*
End of tes3mp addition
*/
/*
Start of tes3mp addition
Make it possible to set a custom timeScale from elsewhere
*/
void DateTimeManager::setTimeScale(float timeScale)
{
mTimeScale = timeScale;
}
/*
End of tes3mp addition
*/
TimeStamp DateTimeManager::getTimeStamp() const
{
return TimeStamp(mGameHour, mDaysPassed);
}
float DateTimeManager::getTimeScaleFactor() const
{
return mTimeScale;
}
ESM::EpochTimeStamp DateTimeManager::getEpochTimeStamp() const
{
ESM::EpochTimeStamp timeStamp;
timeStamp.mGameHour = mGameHour;
timeStamp.mDay = mDay;
timeStamp.mMonth = mMonth;
timeStamp.mYear = mYear;
return timeStamp;
}
void DateTimeManager::setMonth(int month)
{
if (month < 0)
month = 0;
int years = month / 12;
month = month % 12;
int days = getDaysPerMonth(month);
if (mDay > days)
mDay = days;
mMonth = month;
if (years > 0)
mYear += years;
}
void DateTimeManager::advanceTime(double hours, Globals& globalVariables)
{
hours += mGameHour;
setHour(hours);
int days = static_cast<int>(hours / 24);
if (days > 0)
mDaysPassed += days;
globalVariables["gamehour"].setFloat(mGameHour);
globalVariables["dayspassed"].setInteger(mDaysPassed);
globalVariables["day"].setInteger(mDay);
globalVariables["month"].setInteger(mMonth);
globalVariables["year"].setInteger(mYear);
}
std::string DateTimeManager::getMonthName(int month) const
{
if (month == -1)
month = mMonth;
const int months = 12;
if (month < 0 || month >= months)
return std::string();
static const char *monthNames[months] =
{
"sMonthMorningstar", "sMonthSunsdawn", "sMonthFirstseed", "sMonthRainshand",
"sMonthSecondseed", "sMonthMidyear", "sMonthSunsheight", "sMonthLastseed",
"sMonthHeartfire", "sMonthFrostfall", "sMonthSunsdusk", "sMonthEveningstar"
};
const ESM::GameSetting *setting = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(monthNames[month]);
return setting->mValue.getString();
}
bool DateTimeManager::updateGlobalFloat(const std::string& name, float value)
{
if (name=="gamehour")
{
setHour(value);
return true;
}
else if (name=="day")
{
setDay(static_cast<int>(value));
return true;
}
else if (name=="month")
{
setMonth(static_cast<int>(value));
return true;
}
else if (name=="year")
{
mYear = static_cast<int>(value);
}
else if (name=="timescale")
{
mTimeScale = value;
}
else if (name=="dayspassed")
{
mDaysPassed = static_cast<int>(value);
}
return false;
}
bool DateTimeManager::updateGlobalInt(const std::string& name, int value)
{
if (name=="gamehour")
{
setHour(static_cast<float>(value));
return true;
}
else if (name=="day")
{
setDay(value);
return true;
}
else if (name=="month")
{
setMonth(value);
return true;
}
else if (name=="year")
{
mYear = value;
}
else if (name=="timescale")
{
mTimeScale = static_cast<float>(value);
}
else if (name=="dayspassed")
{
mDaysPassed = value;
}
return false;
}
}

@ -0,0 +1,73 @@
#ifndef GAME_MWWORLD_DATETIMEMANAGER_H
#define GAME_MWWORLD_DATETIMEMANAGER_H
#include <string>
namespace ESM
{
struct EpochTimeStamp;
}
namespace MWWorld
{
class Globals;
class TimeStamp;
class DateTimeManager
{
int mDaysPassed = 0;
int mDay = 0;
int mMonth = 0;
int mYear = 0;
float mGameHour = 0.f;
float mTimeScale = 0.f;
void setHour(double hour);
void setDay(int day);
void setMonth(int month);
/*
Start of tes3mp addition
Make it possible to set the year from elsewhere
*/
void setYear(int year);
/*
End of tes3mp addition
*/
/*
Start of tes3mp addition
Make it possible to set the number of days passed from elsewhere
*/
void setDaysPassed(int daysPassed);
/*
End of tes3mp addition
*/
/*
Start of tes3mp addition
Make it possible to set a custom timeScale from elsewhere
*/
void setTimeScale(float timeScale);
/*
End of tes3mp addition
*/
public:
std::string getMonthName(int month) const;
TimeStamp getTimeStamp() const;
ESM::EpochTimeStamp getEpochTimeStamp() const;
float getTimeScaleFactor() const;
void advanceTime(double hours, Globals& globalVariables);
void setup(Globals& globalVariables);
bool updateGlobalInt(const std::string& name, int value);
bool updateGlobalFloat(const std::string& name, float value);
};
}
#endif

@ -273,7 +273,8 @@ void ESMStore::validate()
+mSpells.getDynamicSize()
+mWeapons.getDynamicSize()
+mCreatureLists.getDynamicSize()
+mItemLists.getDynamicSize();
+mItemLists.getDynamicSize()
+mCreatures.getDynamicSize();
}
void ESMStore::write (ESM::ESMWriter& writer, Loading::Listener& progress) const
@ -295,6 +296,7 @@ void ESMStore::validate()
mNpcs.write (writer, progress);
mItemLists.write (writer, progress);
mCreatureLists.write (writer, progress);
mCreatures.write (writer, progress);
}
bool ESMStore::readRecord (ESM::ESMReader& reader, uint32_t type)
@ -312,24 +314,8 @@ void ESMStore::validate()
case ESM::REC_NPC_:
case ESM::REC_LEVI:
case ESM::REC_LEVC:
{
mStores[type]->read (reader);
}
if (type==ESM::REC_NPC_)
{
// NPC record will always be last and we know that there can be only one
// dynamic NPC record (player) -> We are done here with dynamic record loading
setUp();
const ESM::NPC *player = mNpcs.find ("player");
if (!mRaces.find (player->mRace) ||
!mClasses.find (player->mClass))
throw std::runtime_error ("Invalid player record (race or class unavailable");
}
case ESM::REC_CREA:
mStores[type]->read (reader);
return true;
case ESM::REC_DYNA:
@ -343,4 +329,15 @@ void ESMStore::validate()
}
}
void ESMStore::checkPlayer()
{
setUp();
const ESM::NPC *player = mNpcs.find ("player");
if (!mRaces.find (player->mRace) ||
!mClasses.find (player->mClass))
throw std::runtime_error ("Invalid player record (race or class unavailable");
}
} // end namespace

@ -239,6 +239,9 @@ namespace MWWorld
bool readRecord (ESM::ESMReader& reader, uint32_t type);
///< \return Known type?
// To be called when we are done with dynamic record loading
void checkPlayer();
};
/*

@ -2,10 +2,9 @@
#include <stdexcept>
#include <components/misc/stringops.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/esmreader.hpp>
#include <components/misc/stringops.hpp>
#include "esmstore.hpp"

@ -317,12 +317,12 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots
// rate weapon
for (int i = 0; i < static_cast<int>(weaponSkillsLength); ++i)
{
int max = 0;
float max = 0;
int maxWeaponSkill = -1;
for (int j = 0; j < static_cast<int>(weaponSkillsLength); ++j)
{
int skillValue = actor.getClass().getSkill(actor, static_cast<int>(weaponSkills[j]));
float skillValue = actor.getClass().getSkill(actor, static_cast<int>(weaponSkills[j]));
if (skillValue > max && !weaponSkillVisited[j])
{
max = skillValue;
@ -432,7 +432,7 @@ void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots&
static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat();
static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat();
int unarmoredSkill = actor.getClass().getSkill(actor, ESM::Skill::Unarmored);
float unarmoredSkill = actor.getClass().getSkill(actor, ESM::Skill::Unarmored);
float unarmoredRating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill);
for (ContainerStoreIterator iter (begin(ContainerStore::Type_Clothing | ContainerStore::Type_Armor)); iter!=end(); ++iter)
@ -617,7 +617,8 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
mMagicEffects = MWMechanics::MagicEffects();
if (actor.getClass().getCreatureStats(actor).isDead())
const auto& stats = actor.getClass().getCreatureStats(actor);
if (stats.isDead() && stats.isDeathAnimationFinished())
return;
for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
@ -982,16 +983,16 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito
}
}
void MWWorld::InventoryStore::purgeEffect(short effectId)
void MWWorld::InventoryStore::purgeEffect(short effectId, bool wholeSpell)
{
for (TSlots::const_iterator it = mSlots.begin(); it != mSlots.end(); ++it)
{
if (*it != end())
purgeEffect(effectId, (*it)->getCellRef().getRefId());
purgeEffect(effectId, (*it)->getCellRef().getRefId(), wholeSpell);
}
}
void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId)
void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId, bool wholeSpell)
{
TEffectMagnitudes::iterator effectMagnitudeIt = mPermanentMagicEffectMagnitudes.find(sourceId);
if (effectMagnitudeIt == mPermanentMagicEffectMagnitudes.end())
@ -1024,6 +1025,12 @@ void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sou
if (effectIt->mEffectID != effectId)
continue;
if (wholeSpell)
{
mPermanentMagicEffectMagnitudes.erase(sourceId);
return;
}
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom;
magnitude *= params[i].mMultiplier;

@ -203,10 +203,10 @@ namespace MWWorld
void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor);
void purgeEffect (short effectId);
void purgeEffect (short effectId, bool wholeSpell = false);
///< Remove a magic effect
void purgeEffect (short effectId, const std::string& sourceId);
void purgeEffect (short effectId, const std::string& sourceId, bool wholeSpell = false);
///< Remove a magic effect
virtual void clear();

@ -391,8 +391,6 @@ namespace MWWorld
else
player.mHasMark = false;
player.mAutoMove = mAutoMove ? 1 : 0;
for (int i=0; i<ESM::Attribute::Length; ++i)
mSaveAttributes[i].writeState(player.mSaveAttributes[i]);
for (int i=0; i<ESM::Skill::Length; ++i)
@ -487,8 +485,6 @@ namespace MWWorld
mMarkedCell = 0;
}
mAutoMove = player.mAutoMove!=0;
mForwardBackward = 0;
mTeleported = false;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save