mirror of
https://github.com/OpenMW/openmw.git
synced 2026-01-31 20:18:26 +00:00
Merge branch 'refnumhitman' into 'master'
Replace ActorIds with RefNums Closes #7602 See merge request OpenMW/openmw!4323
This commit is contained in:
commit
f0e31e69dd
68 changed files with 615 additions and 725 deletions
|
|
@ -524,7 +524,7 @@ namespace ESM
|
|||
record.mData.mY = 2;
|
||||
record.mData.mZ = 3;
|
||||
record.mData.mDuration = 4;
|
||||
record.mTargetActorId = 5;
|
||||
record.mTargetActor = RefNum{ .mIndex = 5, .mContentFile = -1 };
|
||||
record.mTargetId = generateRandomRefId(32);
|
||||
record.mCellId = generateRandomString(257);
|
||||
record.mRemainingDuration = 6;
|
||||
|
|
@ -540,7 +540,10 @@ namespace ESM
|
|||
EXPECT_EQ(result.mData.mDuration, record.mRemainingDuration);
|
||||
else
|
||||
EXPECT_EQ(result.mData.mDuration, record.mData.mDuration);
|
||||
EXPECT_EQ(result.mTargetActorId, record.mTargetActorId);
|
||||
if (GetParam() > MaxActorIdSaveGameFormatVersion)
|
||||
{
|
||||
EXPECT_EQ(result.mTargetActor, record.mTargetActor);
|
||||
}
|
||||
EXPECT_EQ(result.mTargetId, record.mTargetId);
|
||||
EXPECT_EQ(result.mCellId, record.mCellId);
|
||||
EXPECT_EQ(result.mRemainingDuration, record.mRemainingDuration);
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ namespace
|
|||
refId = indexedRefId.substr(0, indexedRefId.size() - 8);
|
||||
}
|
||||
|
||||
int convertActorId(const std::string& indexedRefId, ESSImport::Context& context)
|
||||
ESM::RefNum convertActorId(const std::string& indexedRefId, ESSImport::Context& context)
|
||||
{
|
||||
if (isIndexedRefId(indexedRefId))
|
||||
{
|
||||
|
|
@ -73,16 +73,15 @@ namespace
|
|||
splitIndexedRefId(indexedRefId, refIndex, refId);
|
||||
|
||||
auto it = context.mActorIdMap.find(std::make_pair(refIndex, ESM::RefId::stringRefId(refId)));
|
||||
if (it == context.mActorIdMap.end())
|
||||
return -1;
|
||||
return it->second;
|
||||
if (it != context.mActorIdMap.end())
|
||||
return it->second;
|
||||
}
|
||||
else if (indexedRefId == "PlayerSaveGame")
|
||||
{
|
||||
return context.mPlayer.mObject.mCreatureStats.mActorId;
|
||||
return context.mPlayer.mObject.mRef.mRefNum;
|
||||
}
|
||||
|
||||
return -1;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +113,7 @@ namespace ESSImport
|
|||
mGlobalMapImage->scaleImage(maph.size * 2, maph.size * 2, 1, GL_UNSIGNED_BYTE);
|
||||
}
|
||||
|
||||
void ConvertFMAP::write(ESM::ESMWriter& esm)
|
||||
void ConvertFMAP::write(ESM::ESMWriter& esm) const
|
||||
{
|
||||
int numcells = mGlobalMapImage->s() / 18; // NB truncating, doesn't divide perfectly
|
||||
// with the 512x512 map the game has by default
|
||||
|
|
@ -314,7 +313,7 @@ namespace ESSImport
|
|||
mIntCells[cell.mName] = std::move(newcell);
|
||||
}
|
||||
|
||||
void ConvertCell::writeCell(const Cell& cell, ESM::ESMWriter& esm)
|
||||
void ConvertCell::writeCell(const Cell& cell, ESM::ESMWriter& esm) const
|
||||
{
|
||||
ESM::Cell esmcell = cell.mCell;
|
||||
esm.startRecord(ESM::REC_CSTA);
|
||||
|
|
@ -378,9 +377,8 @@ namespace ESSImport
|
|||
convertNPCC(npccIt->second, objstate);
|
||||
convertCellRef(cellref, objstate);
|
||||
|
||||
objstate.mCreatureStats.mActorId = mContext->generateActorId();
|
||||
mContext->mActorIdMap.insert(
|
||||
std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId));
|
||||
mContext->generateRefNum(objstate.mRef.mRefNum);
|
||||
mContext->mActorIdMap.emplace(std::make_pair(refIndex, out.mRefID), objstate.mRef.mRefNum);
|
||||
|
||||
esm.writeHNT("OBJE", ESM::REC_NPC_);
|
||||
objstate.save(esm);
|
||||
|
|
@ -419,9 +417,8 @@ namespace ESSImport
|
|||
convertCREC(crecIt->second, objstate);
|
||||
convertCellRef(cellref, objstate);
|
||||
|
||||
objstate.mCreatureStats.mActorId = mContext->generateActorId();
|
||||
mContext->mActorIdMap.insert(
|
||||
std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId));
|
||||
mContext->generateRefNum(objstate.mRef.mRefNum);
|
||||
mContext->mActorIdMap.emplace(std::make_pair(refIndex, out.mRefID), objstate.mRef.mRefNum);
|
||||
|
||||
esm.writeHNT("OBJE", ESM::REC_CREA);
|
||||
objstate.save(esm);
|
||||
|
|
@ -437,7 +434,7 @@ namespace ESSImport
|
|||
esm.endRecord(ESM::REC_CSTA);
|
||||
}
|
||||
|
||||
void ConvertCell::write(ESM::ESMWriter& esm)
|
||||
void ConvertCell::write(ESM::ESMWriter& esm) const
|
||||
{
|
||||
for (const auto& cell : mIntCells)
|
||||
writeCell(cell.second, esm);
|
||||
|
|
@ -458,7 +455,7 @@ namespace ESSImport
|
|||
mProj.load(esm);
|
||||
}
|
||||
|
||||
void ConvertPROJ::write(ESM::ESMWriter& esm)
|
||||
void ConvertPROJ::write(ESM::ESMWriter& esm) const
|
||||
{
|
||||
for (const PROJ::PNAM& pnam : mProj.mProjectiles)
|
||||
{
|
||||
|
|
@ -501,7 +498,7 @@ namespace ESSImport
|
|||
}
|
||||
}
|
||||
|
||||
void ConvertPROJ::convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam)
|
||||
void ConvertPROJ::convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam) const
|
||||
{
|
||||
base.mId = ESM::RefId::stringRefId(pnam.mArrowId.toString());
|
||||
base.mPosition = pnam.mPosition;
|
||||
|
|
@ -510,7 +507,7 @@ namespace ESSImport
|
|||
orient.makeRotate(osg::Vec3f(0, 1, 0), pnam.mVelocity);
|
||||
base.mOrientation = orient;
|
||||
|
||||
base.mActorId = convertActorId(pnam.mActorId.toString(), *mContext);
|
||||
base.mCaster = convertActorId(pnam.mActorId.toString(), *mContext);
|
||||
}
|
||||
|
||||
void ConvertSPLM::read(ESM::ESMReader& esm)
|
||||
|
|
@ -519,7 +516,7 @@ namespace ESSImport
|
|||
mContext->mActiveSpells = mSPLM.mActiveSpells;
|
||||
}
|
||||
|
||||
void ConvertSPLM::write(ESM::ESMWriter& esm)
|
||||
void ConvertSPLM::write(ESM::ESMWriter& esm) const
|
||||
{
|
||||
std::cerr << "Warning: Skipped active spell conversion (not implemented)" << std::endl;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ namespace ESSImport
|
|||
{
|
||||
public:
|
||||
/// @return the order for writing this converter's records to the output file, in relation to other converters
|
||||
virtual int getStage() { return 1; }
|
||||
virtual int getStage() const { return 1; }
|
||||
|
||||
virtual ~Converter() = default;
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ namespace ESSImport
|
|||
|
||||
/// Called after the input file has been read in completely, which may be necessary
|
||||
/// if the conversion process relies on information in other records
|
||||
virtual void write(ESM::ESMWriter& esm) {}
|
||||
virtual void write(ESM::ESMWriter& esm) const {}
|
||||
|
||||
protected:
|
||||
Context* mContext;
|
||||
|
|
@ -77,7 +77,7 @@ namespace ESSImport
|
|||
class DefaultConverter : public Converter
|
||||
{
|
||||
public:
|
||||
int getStage() override { return 0; }
|
||||
int getStage() const override { return 0; }
|
||||
|
||||
void read(ESM::ESMReader& esm) override
|
||||
{
|
||||
|
|
@ -88,7 +88,7 @@ namespace ESSImport
|
|||
mRecords[record.mId] = record;
|
||||
}
|
||||
|
||||
void write(ESM::ESMWriter& esm) override
|
||||
void write(ESM::ESMWriter& esm) const override
|
||||
{
|
||||
for (auto it = mRecords.begin(); it != mRecords.end(); ++it)
|
||||
{
|
||||
|
|
@ -257,7 +257,7 @@ namespace ESSImport
|
|||
}
|
||||
}
|
||||
}
|
||||
void write(ESM::ESMWriter& esm) override
|
||||
void write(ESM::ESMWriter& esm) const override
|
||||
{
|
||||
esm.startRecord(ESM::REC_ASPL);
|
||||
esm.writeHNRefId("ID__", mSelectedSpell);
|
||||
|
|
@ -286,7 +286,7 @@ namespace ESSImport
|
|||
convertPCDT(pcdt, mContext->mPlayer, mContext->mDialogueState.mKnownTopics, mFirstPersonCam,
|
||||
mTeleportingEnabled, mLevitationEnabled, mContext->mControlsState);
|
||||
}
|
||||
void write(ESM::ESMWriter& esm) override
|
||||
void write(ESM::ESMWriter& esm) const override
|
||||
{
|
||||
esm.startRecord(ESM::REC_ENAB);
|
||||
esm.writeHNT("TELE", mTeleportingEnabled);
|
||||
|
|
@ -331,7 +331,7 @@ namespace ESSImport
|
|||
{
|
||||
public:
|
||||
void read(ESM::ESMReader& esm) override;
|
||||
void write(ESM::ESMWriter& esm) override;
|
||||
void write(ESM::ESMWriter& esm) const override;
|
||||
|
||||
private:
|
||||
osg::ref_ptr<osg::Image> mGlobalMapImage;
|
||||
|
|
@ -341,7 +341,7 @@ namespace ESSImport
|
|||
{
|
||||
public:
|
||||
void read(ESM::ESMReader& esm) override;
|
||||
void write(ESM::ESMWriter& esm) override;
|
||||
void write(ESM::ESMWriter& esm) const override;
|
||||
|
||||
private:
|
||||
struct Cell
|
||||
|
|
@ -356,7 +356,7 @@ namespace ESSImport
|
|||
|
||||
std::vector<ESM::CustomMarker> mMarkers;
|
||||
|
||||
void writeCell(const Cell& cell, ESM::ESMWriter& esm);
|
||||
void writeCell(const Cell& cell, ESM::ESMWriter& esm) const;
|
||||
};
|
||||
|
||||
class ConvertKLST : public Converter
|
||||
|
|
@ -371,7 +371,7 @@ namespace ESSImport
|
|||
mContext->mPlayer.mObject.mNpcStats.mWerewolfKills = klst.mWerewolfKills;
|
||||
}
|
||||
|
||||
void write(ESM::ESMWriter& esm) override
|
||||
void write(ESM::ESMWriter& esm) const override
|
||||
{
|
||||
esm.startRecord(ESM::REC_DCOU);
|
||||
for (const auto& [id, count] : mKillCounter)
|
||||
|
|
@ -428,7 +428,7 @@ namespace ESSImport
|
|||
}
|
||||
}
|
||||
}
|
||||
void write(ESM::ESMWriter& esm) override
|
||||
void write(ESM::ESMWriter& esm) const override
|
||||
{
|
||||
ESM::StolenItems items;
|
||||
for (auto it = mStolenItems.begin(); it != mStolenItems.end(); ++it)
|
||||
|
|
@ -486,7 +486,7 @@ namespace ESSImport
|
|||
if (dial.mIndex > 0)
|
||||
mDials[id] = dial;
|
||||
}
|
||||
void write(ESM::ESMWriter& esm) override
|
||||
void write(ESM::ESMWriter& esm) const override
|
||||
{
|
||||
for (auto it = mDials.begin(); it != mDials.end(); ++it)
|
||||
{
|
||||
|
|
@ -539,7 +539,7 @@ namespace ESSImport
|
|||
mHasGame = true;
|
||||
}
|
||||
|
||||
int validateWeatherID(int weatherID)
|
||||
int validateWeatherID(int weatherID) const
|
||||
{
|
||||
if (weatherID >= -1 && weatherID < 10)
|
||||
{
|
||||
|
|
@ -551,7 +551,7 @@ namespace ESSImport
|
|||
}
|
||||
}
|
||||
|
||||
void write(ESM::ESMWriter& esm) override
|
||||
void write(ESM::ESMWriter& esm) const override
|
||||
{
|
||||
if (!mHasGame)
|
||||
return;
|
||||
|
|
@ -586,7 +586,7 @@ namespace ESSImport
|
|||
convertSCPT(script, out);
|
||||
mScripts.push_back(std::move(out));
|
||||
}
|
||||
void write(ESM::ESMWriter& esm) override
|
||||
void write(ESM::ESMWriter& esm) const override
|
||||
{
|
||||
for (const auto& script : mScripts)
|
||||
{
|
||||
|
|
@ -604,12 +604,12 @@ namespace ESSImport
|
|||
class ConvertPROJ : public Converter
|
||||
{
|
||||
public:
|
||||
int getStage() override { return 2; }
|
||||
int getStage() const override { return 2; }
|
||||
void read(ESM::ESMReader& esm) override;
|
||||
void write(ESM::ESMWriter& esm) override;
|
||||
void write(ESM::ESMWriter& esm) const override;
|
||||
|
||||
private:
|
||||
void convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam);
|
||||
void convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam) const;
|
||||
PROJ mProj;
|
||||
};
|
||||
|
||||
|
|
@ -617,7 +617,7 @@ namespace ESSImport
|
|||
{
|
||||
public:
|
||||
void read(ESM::ESMReader& esm) override;
|
||||
void write(ESM::ESMWriter& esm) override;
|
||||
void write(ESM::ESMWriter& esm) const override;
|
||||
|
||||
private:
|
||||
SPLM mSPLM;
|
||||
|
|
|
|||
|
|
@ -388,13 +388,22 @@ namespace ESSImport
|
|||
profile.save(writer);
|
||||
writer.endRecord(ESM::REC_SAVE);
|
||||
|
||||
writer.startRecord(ESM::REC_DIAS);
|
||||
context.mDialogueState.save(writer);
|
||||
writer.endRecord(ESM::REC_DIAS);
|
||||
|
||||
writer.startRecord(ESM::REC_LUAM);
|
||||
writer.writeHNT<double>("LUAW", 0);
|
||||
writer.writeFormId(context.mNextRefNum, true);
|
||||
writer.endRecord(ESM::REC_LUAM);
|
||||
|
||||
// Writing order should be Dynamic Store -> Cells -> Player,
|
||||
// so that references to dynamic records can be recognized when loading
|
||||
for (auto it = converters.begin(); it != converters.end(); ++it)
|
||||
for (const auto& [_, converter] : converters)
|
||||
{
|
||||
if (it->second->getStage() != 0)
|
||||
if (converter->getStage() != 0)
|
||||
continue;
|
||||
it->second->write(writer);
|
||||
converter->write(writer);
|
||||
}
|
||||
|
||||
writer.startRecord(ESM::REC_NPC_);
|
||||
|
|
@ -402,11 +411,11 @@ namespace ESSImport
|
|||
context.mPlayerBase.save(writer);
|
||||
writer.endRecord(ESM::REC_NPC_);
|
||||
|
||||
for (auto it = converters.begin(); it != converters.end(); ++it)
|
||||
for (const auto& [_, converter] : converters)
|
||||
{
|
||||
if (it->second->getStage() != 1)
|
||||
if (converter->getStage() != 1)
|
||||
continue;
|
||||
it->second->write(writer);
|
||||
converter->write(writer);
|
||||
}
|
||||
|
||||
writer.startRecord(ESM::REC_PLAY);
|
||||
|
|
@ -418,22 +427,14 @@ namespace ESSImport
|
|||
context.mPlayer.save(writer);
|
||||
writer.endRecord(ESM::REC_PLAY);
|
||||
|
||||
writer.startRecord(ESM::REC_ACTC);
|
||||
writer.writeHNT("COUN", context.mNextActorId);
|
||||
writer.endRecord(ESM::REC_ACTC);
|
||||
|
||||
// Stage 2 requires cell references to be written / actors IDs assigned
|
||||
for (auto it = converters.begin(); it != converters.end(); ++it)
|
||||
for (const auto& [_, converter] : converters)
|
||||
{
|
||||
if (it->second->getStage() != 2)
|
||||
if (converter->getStage() != 2)
|
||||
continue;
|
||||
it->second->write(writer);
|
||||
converter->write(writer);
|
||||
}
|
||||
|
||||
writer.startRecord(ESM::REC_DIAS);
|
||||
context.mDialogueState.save(writer);
|
||||
writer.endRecord(ESM::REC_DIAS);
|
||||
|
||||
writer.startRecord(ESM::REC_INPU);
|
||||
context.mControlsState.save(writer);
|
||||
writer.endRecord(ESM::REC_INPU);
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ namespace ESSImport
|
|||
std::map<std::pair<int, ESM::RefId>, NPCC> mNpcChanges;
|
||||
std::map<std::pair<int, ESM::RefId>, CNTC> mContainerChanges;
|
||||
|
||||
std::map<std::pair<int, ESM::RefId>, int> mActorIdMap;
|
||||
int mNextActorId;
|
||||
std::map<std::pair<int, ESM::RefId>, ESM::RefNum> mActorIdMap;
|
||||
ESM::RefNum mNextRefNum;
|
||||
|
||||
std::map<ESM::RefId, ESM::Creature> mCreatures;
|
||||
std::map<ESM::RefId, ESM::NPC> mNpcs;
|
||||
|
|
@ -58,7 +58,6 @@ namespace ESSImport
|
|||
, mMonth(0)
|
||||
, mYear(0)
|
||||
, mHour(0.f)
|
||||
, mNextActorId(0)
|
||||
{
|
||||
mPlayer.mCellId = ESM::RefId::esm3ExteriorCell(0, 0);
|
||||
mPlayer.mLastKnownExteriorPosition[0] = mPlayer.mLastKnownExteriorPosition[1]
|
||||
|
|
@ -69,7 +68,7 @@ namespace ESSImport
|
|||
mPlayer.mObject.blank();
|
||||
mPlayer.mObject.mEnabled = true;
|
||||
mPlayer.mObject.mRef.mRefID = ESM::RefId::stringRefId("player"); // REFR.mRefID would be PlayerSaveGame
|
||||
mPlayer.mObject.mCreatureStats.mActorId = generateActorId();
|
||||
generateRefNum(mPlayer.mObject.mRef.mRefNum);
|
||||
|
||||
mGlobalMapState.mBounds.mMinX = 0;
|
||||
mGlobalMapState.mBounds.mMaxX = 0;
|
||||
|
|
@ -79,7 +78,16 @@ namespace ESSImport
|
|||
mPlayerBase.blank();
|
||||
}
|
||||
|
||||
int generateActorId() { return mNextActorId++; }
|
||||
void generateRefNum(ESM::RefNum& refNum)
|
||||
{
|
||||
if (!refNum.isSet())
|
||||
{
|
||||
mNextRefNum.mIndex++;
|
||||
if (mNextRefNum.mIndex == 0)
|
||||
mNextRefNum.mContentFile--;
|
||||
refNum = mNextRefNum;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ namespace MWBase
|
|||
/// It only applies to the current form the NPC is in.
|
||||
virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) = 0;
|
||||
|
||||
virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0;
|
||||
virtual void cleanupSummonedCreature(ESM::RefNum creature) = 0;
|
||||
|
||||
virtual void confiscateStolenItemToOwner(
|
||||
const MWWorld::Ptr& player, const MWWorld::Ptr& item, const MWWorld::Ptr& victim, int count)
|
||||
|
|
|
|||
|
|
@ -196,9 +196,6 @@ namespace MWBase
|
|||
///< Return a pointer to a liveCellRef with the given name.
|
||||
/// \param activeOnly do non search inactive cells.
|
||||
|
||||
virtual MWWorld::Ptr searchPtrViaActorId(int actorId) = 0;
|
||||
///< Search is limited to the active cells.
|
||||
|
||||
virtual MWWorld::Ptr findContainer(const MWWorld::ConstPtr& ptr) = 0;
|
||||
///< Return a pointer to a liveCellRef which contains \a ptr.
|
||||
/// \note Search is limited to the active cells.
|
||||
|
|
@ -375,7 +372,7 @@ namespace MWBase
|
|||
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
|
||||
virtual void disableDeferredPreviewRotation() = 0;
|
||||
|
||||
virtual void saveLoaded() = 0;
|
||||
virtual void saveLoaded(const ESM::ESMReader& reader) = 0;
|
||||
|
||||
virtual void setupPlayer() = 0;
|
||||
virtual void renderPlayer() = 0;
|
||||
|
|
|
|||
|
|
@ -376,14 +376,14 @@ namespace MWClass
|
|||
{
|
||||
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
|
||||
// First handle the attacked actor
|
||||
if ((stats.getHitAttemptActorId() == -1)
|
||||
if (!stats.getHitAttemptActor().isSet()
|
||||
&& (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer()))
|
||||
stats.setHitAttemptActorId(statsAttacker.getActorId());
|
||||
stats.setHitAttemptActor(attacker.getCellRef().getRefNum());
|
||||
|
||||
// Next handle the attacking actor
|
||||
if ((statsAttacker.getHitAttemptActorId() == -1)
|
||||
if (!statsAttacker.getHitAttemptActor().isSet()
|
||||
&& (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer()))
|
||||
statsAttacker.setHitAttemptActorId(stats.getActorId());
|
||||
statsAttacker.setHitAttemptActor(ptr.getCellRef().getRefNum());
|
||||
}
|
||||
|
||||
if (!object.empty())
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "creaturelevlist.hpp"
|
||||
|
||||
#include <components/esm3/actoridconverter.hpp>
|
||||
#include <components/esm3/creaturelevliststate.hpp>
|
||||
#include <components/esm3/loadlevlist.hpp>
|
||||
|
||||
|
|
@ -9,6 +10,7 @@
|
|||
#include "../mwworld/customdata.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/manualref.hpp"
|
||||
#include "../mwworld/worldmodel.hpp"
|
||||
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
|
||||
|
|
@ -20,9 +22,15 @@ namespace MWClass
|
|||
class CreatureLevListCustomData : public MWWorld::TypedCustomData<CreatureLevListCustomData>
|
||||
{
|
||||
public:
|
||||
// actorId of the creature we spawned
|
||||
int mSpawnActorId;
|
||||
bool mSpawn; // Should a new creature be spawned?
|
||||
ESM::RefNum mSpawnedActor;
|
||||
bool mSpawn = true; // Should a new creature be spawned?
|
||||
|
||||
MWWorld::Ptr getSpawnedPtr() const
|
||||
{
|
||||
if (mSpawnedActor.isSet())
|
||||
return MWBase::Environment::get().getWorldModel()->getPtr(mSpawnedActor);
|
||||
return {};
|
||||
}
|
||||
|
||||
CreatureLevListCustomData& asCreatureLevListCustomData() override { return *this; }
|
||||
const CreatureLevListCustomData& asCreatureLevListCustomData() const override { return *this; }
|
||||
|
|
@ -46,9 +54,7 @@ namespace MWClass
|
|||
return;
|
||||
|
||||
CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData();
|
||||
MWWorld::Ptr creature = (customData.mSpawnActorId == -1)
|
||||
? MWWorld::Ptr()
|
||||
: MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId);
|
||||
MWWorld::Ptr creature = customData.getSpawnedPtr();
|
||||
if (!creature.isEmpty())
|
||||
MWBase::Environment::get().getWorld()->adjustPosition(creature, force);
|
||||
}
|
||||
|
|
@ -71,13 +77,7 @@ namespace MWClass
|
|||
if (customData.mSpawn)
|
||||
return;
|
||||
|
||||
MWWorld::Ptr creature;
|
||||
if (customData.mSpawnActorId != -1)
|
||||
{
|
||||
creature = MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId);
|
||||
if (creature.isEmpty())
|
||||
creature = ptr.getCell()->getMovedActor(customData.mSpawnActorId);
|
||||
}
|
||||
MWWorld::Ptr creature = customData.getSpawnedPtr();
|
||||
if (!creature.isEmpty())
|
||||
{
|
||||
const MWMechanics::CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature);
|
||||
|
|
@ -116,21 +116,17 @@ namespace MWClass
|
|||
if (!id.empty())
|
||||
{
|
||||
// Delete the previous creature
|
||||
if (customData.mSpawnActorId != -1)
|
||||
{
|
||||
MWWorld::Ptr creature
|
||||
= MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId);
|
||||
if (!creature.isEmpty())
|
||||
MWBase::Environment::get().getWorld()->deleteObject(creature);
|
||||
customData.mSpawnActorId = -1;
|
||||
}
|
||||
MWWorld::Ptr previous = customData.getSpawnedPtr();
|
||||
if (!previous.isEmpty())
|
||||
MWBase::Environment::get().getWorld()->deleteObject(previous);
|
||||
|
||||
MWWorld::ManualRef manualRef(store, id);
|
||||
manualRef.getPtr().getCellRef().setPosition(ptr.getCellRef().getPosition());
|
||||
manualRef.getPtr().getCellRef().setScale(ptr.getCellRef().getScale());
|
||||
MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(
|
||||
manualRef.getPtr(), ptr.getCell(), ptr.getRefData().getPosition());
|
||||
customData.mSpawnActorId = placed.getClass().getCreatureStats(placed).getActorId();
|
||||
MWBase::Environment::get().getWorldModel()->registerPtr(placed);
|
||||
customData.mSpawnedActor = placed.getCellRef().getRefNum();
|
||||
customData.mSpawn = false;
|
||||
}
|
||||
else
|
||||
|
|
@ -141,11 +137,7 @@ namespace MWClass
|
|||
{
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
std::unique_ptr<CreatureLevListCustomData> data = std::make_unique<CreatureLevListCustomData>();
|
||||
data->mSpawnActorId = -1;
|
||||
data->mSpawn = true;
|
||||
|
||||
ptr.getRefData().setCustomData(std::move(data));
|
||||
ptr.getRefData().setCustomData(std::make_unique<CreatureLevListCustomData>());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -157,8 +149,10 @@ namespace MWClass
|
|||
ensureCustomData(ptr);
|
||||
CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData();
|
||||
const ESM::CreatureLevListState& levListState = state.asCreatureLevListState();
|
||||
customData.mSpawnActorId = levListState.mSpawnActorId;
|
||||
customData.mSpawnedActor = levListState.mSpawnedActor;
|
||||
customData.mSpawn = levListState.mSpawn;
|
||||
if (state.mActorIdConverter)
|
||||
state.mActorIdConverter->convert(customData.mSpawnedActor, customData.mSpawnedActor.mIndex);
|
||||
}
|
||||
|
||||
void CreatureLevList::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
|
||||
|
|
@ -171,7 +165,7 @@ namespace MWClass
|
|||
|
||||
const CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData();
|
||||
ESM::CreatureLevListState& levListState = state.asCreatureLevListState();
|
||||
levListState.mSpawnActorId = customData.mSpawnActorId;
|
||||
levListState.mSpawnedActor = customData.mSpawnedActor;
|
||||
levListState.mSpawn = customData.mSpawn;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -728,14 +728,14 @@ namespace MWClass
|
|||
{
|
||||
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
|
||||
// First handle the attacked actor
|
||||
if ((stats.getHitAttemptActorId() == -1)
|
||||
if (!stats.getHitAttemptActor().isSet()
|
||||
&& (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer()))
|
||||
stats.setHitAttemptActorId(statsAttacker.getActorId());
|
||||
stats.setHitAttemptActor(attacker.getCellRef().getRefNum());
|
||||
|
||||
// Next handle the attacking actor
|
||||
if ((statsAttacker.getHitAttemptActorId() == -1)
|
||||
if (!statsAttacker.getHitAttemptActor().isSet()
|
||||
&& (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer()))
|
||||
statsAttacker.setHitAttemptActorId(stats.getActorId());
|
||||
statsAttacker.setHitAttemptActor(ptr.getCellRef().getRefNum());
|
||||
}
|
||||
|
||||
if (!object.empty())
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ namespace MWGui
|
|||
// Clean up summoned creatures as well
|
||||
auto& creatureMap = creatureStats.getSummonedCreatureMap();
|
||||
for (const auto& creature : creatureMap)
|
||||
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(ptr, creature.second);
|
||||
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(creature.second);
|
||||
creatureMap.clear();
|
||||
|
||||
// Check if we are a summon and inform our master we've bit the dust
|
||||
|
|
@ -323,7 +323,7 @@ namespace MWGui
|
|||
const auto& summoner = package->getTarget();
|
||||
auto& summons = summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap();
|
||||
auto it = std::find_if(summons.begin(), summons.end(),
|
||||
[&](const auto& entry) { return entry.second == creatureStats.getActorId(); });
|
||||
[&](const auto& entry) { return entry.second == ptr.getCellRef().getRefNum(); });
|
||||
if (it != summons.end())
|
||||
{
|
||||
auto summon = *it;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/worldmodel.hpp"
|
||||
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
|
|
@ -34,36 +35,7 @@ namespace MWGui
|
|||
HUD::HUD(CustomMarkerCollection& customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender)
|
||||
: WindowBase("openmw_hud.layout")
|
||||
, LocalMapBase(customMarkers, localMapRender, Settings::map().mLocalMapHudFogOfWar)
|
||||
, mHealth(nullptr)
|
||||
, mMagicka(nullptr)
|
||||
, mStamina(nullptr)
|
||||
, mDrowning(nullptr)
|
||||
, mWeapImage(nullptr)
|
||||
, mSpellImage(nullptr)
|
||||
, mWeapStatus(nullptr)
|
||||
, mSpellStatus(nullptr)
|
||||
, mEffectBox(nullptr)
|
||||
, mMinimap(nullptr)
|
||||
, mCrosshair(nullptr)
|
||||
, mCellNameBox(nullptr)
|
||||
, mDrowningBar(nullptr)
|
||||
, mDrowningFlash(nullptr)
|
||||
, mHealthManaStaminaBaseLeft(0)
|
||||
, mWeapBoxBaseLeft(0)
|
||||
, mSpellBoxBaseLeft(0)
|
||||
, mMinimapBoxBaseRight(0)
|
||||
, mEffectBoxBaseRight(0)
|
||||
, mDragAndDrop(dragAndDrop)
|
||||
, mCellNameTimer(0.0f)
|
||||
, mWeaponSpellTimer(0.f)
|
||||
, mMapVisible(true)
|
||||
, mWeaponVisible(true)
|
||||
, mSpellVisible(true)
|
||||
, mWorldMouseOver(false)
|
||||
, mEnemyActorId(-1)
|
||||
, mEnemyHealthTimer(-1)
|
||||
, mIsDrowning(false)
|
||||
, mDrowningFlashTheta(0.f)
|
||||
{
|
||||
// Energy bars
|
||||
getWidget(mHealthFrame, "HealthFrame");
|
||||
|
|
@ -338,7 +310,7 @@ namespace MWGui
|
|||
|
||||
mSpellIcons->updateWidgets(mEffectBox, true);
|
||||
|
||||
if (mEnemyActorId != -1 && mEnemyHealth->getVisible())
|
||||
if (mEnemyActor.isSet() && mEnemyHealth->getVisible())
|
||||
{
|
||||
updateEnemyHealthBar();
|
||||
}
|
||||
|
|
@ -579,7 +551,7 @@ namespace MWGui
|
|||
|
||||
void HUD::updateEnemyHealthBar()
|
||||
{
|
||||
MWWorld::Ptr enemy = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mEnemyActorId);
|
||||
MWWorld::Ptr enemy = MWBase::Environment::get().getWorldModel()->getPtr(mEnemyActor);
|
||||
if (enemy.isEmpty())
|
||||
return;
|
||||
MWMechanics::CreatureStats& stats = enemy.getClass().getCreatureStats(enemy);
|
||||
|
|
@ -599,7 +571,7 @@ namespace MWGui
|
|||
|
||||
void HUD::setEnemy(const MWWorld::Ptr& enemy)
|
||||
{
|
||||
mEnemyActorId = enemy.getClass().getCreatureStats(enemy).getActorId();
|
||||
mEnemyActor = enemy.getCellRef().getRefNum();
|
||||
mEnemyHealthTimer = MWBase::Environment::get()
|
||||
.getESMStore()
|
||||
->get<ESM::GameSetting>()
|
||||
|
|
@ -613,12 +585,12 @@ namespace MWGui
|
|||
|
||||
void HUD::clear()
|
||||
{
|
||||
mEnemyActorId = -1;
|
||||
mEnemyActor = {};
|
||||
mEnemyHealthTimer = -1;
|
||||
|
||||
mWeaponSpellTimer = 0.f;
|
||||
mWeaponName = std::string();
|
||||
mSpellName = std::string();
|
||||
mWeaponName.clear();
|
||||
mSpellName.clear();
|
||||
mWeaponSpellBox->setVisible(false);
|
||||
|
||||
mWeapStatus->setProgressRange(100);
|
||||
|
|
|
|||
|
|
@ -64,47 +64,42 @@ namespace MWGui
|
|||
void dropDraggedItem(float mouseX, float mouseY);
|
||||
|
||||
private:
|
||||
MyGUI::ProgressBar *mHealth, *mMagicka, *mStamina, *mEnemyHealth, *mDrowning;
|
||||
MyGUI::Widget* mHealthFrame;
|
||||
MyGUI::Widget *mWeapBox, *mSpellBox, *mSneakBox;
|
||||
ItemWidget* mWeapImage;
|
||||
SpellWidget* mSpellImage;
|
||||
MyGUI::ProgressBar *mWeapStatus, *mSpellStatus;
|
||||
MyGUI::Widget *mEffectBox, *mMinimapBox;
|
||||
MyGUI::Button* mMinimapButton;
|
||||
MyGUI::ScrollView* mMinimap;
|
||||
MyGUI::ImageBox* mCrosshair;
|
||||
MyGUI::TextBox* mCellNameBox;
|
||||
MyGUI::TextBox* mWeaponSpellBox;
|
||||
MyGUI::Widget *mDrowningBar, *mDrowningFrame, *mDrowningFlash;
|
||||
MyGUI::ProgressBar *mHealth = nullptr, *mMagicka = nullptr, *mStamina = nullptr, *mEnemyHealth = nullptr,
|
||||
*mDrowning = nullptr;
|
||||
MyGUI::Widget* mHealthFrame = nullptr;
|
||||
MyGUI::Widget *mWeapBox = nullptr, *mSpellBox = nullptr, *mSneakBox = nullptr;
|
||||
ItemWidget* mWeapImage = nullptr;
|
||||
SpellWidget* mSpellImage = nullptr;
|
||||
MyGUI::ProgressBar *mWeapStatus = nullptr, *mSpellStatus = nullptr;
|
||||
MyGUI::Widget *mEffectBox = nullptr, *mMinimapBox = nullptr;
|
||||
MyGUI::Button* mMinimapButton = nullptr;
|
||||
MyGUI::ScrollView* mMinimap = nullptr;
|
||||
MyGUI::ImageBox* mCrosshair = nullptr;
|
||||
MyGUI::TextBox* mCellNameBox = nullptr;
|
||||
MyGUI::TextBox* mWeaponSpellBox = nullptr;
|
||||
MyGUI::Widget *mDrowningBar = nullptr, *mDrowningFrame = nullptr, *mDrowningFlash = nullptr;
|
||||
DragAndDrop* mDragAndDrop;
|
||||
std::string mCellName;
|
||||
std::string mWeaponName;
|
||||
std::string mSpellName;
|
||||
std::unique_ptr<SpellIcons> mSpellIcons;
|
||||
ESM::RefNum mEnemyActor;
|
||||
|
||||
// bottom left elements
|
||||
int mHealthManaStaminaBaseLeft, mWeapBoxBaseLeft, mSpellBoxBaseLeft, mSneakBoxBaseLeft;
|
||||
// bottom right elements
|
||||
int mMinimapBoxBaseRight, mEffectBoxBaseRight;
|
||||
|
||||
DragAndDrop* mDragAndDrop;
|
||||
float mCellNameTimer = 0.f;
|
||||
float mWeaponSpellTimer = 0.f;
|
||||
float mEnemyHealthTimer = -1;
|
||||
float mDrowningFlashTheta = 0.f;
|
||||
|
||||
std::string mCellName;
|
||||
float mCellNameTimer;
|
||||
|
||||
std::string mWeaponName;
|
||||
std::string mSpellName;
|
||||
float mWeaponSpellTimer;
|
||||
|
||||
bool mMapVisible;
|
||||
bool mWeaponVisible;
|
||||
bool mSpellVisible;
|
||||
|
||||
bool mWorldMouseOver;
|
||||
|
||||
std::unique_ptr<SpellIcons> mSpellIcons;
|
||||
|
||||
int mEnemyActorId;
|
||||
float mEnemyHealthTimer;
|
||||
|
||||
bool mIsDrowning;
|
||||
float mDrowningFlashTheta;
|
||||
bool mMapVisible = true;
|
||||
bool mWeaponVisible = true;
|
||||
bool mSpellVisible = true;
|
||||
bool mWorldMouseOver = false;
|
||||
bool mIsDrowning = false;
|
||||
|
||||
void onWorldClicked(MyGUI::Widget* sender);
|
||||
void onWorldMouseOver(MyGUI::Widget* sender, int x, int y);
|
||||
|
|
|
|||
|
|
@ -170,34 +170,25 @@ namespace MWLua
|
|||
float duration, const osg::Vec3f& dest, bool repeat, bool cancelOther) {
|
||||
const MWWorld::Ptr& ptr = self.ptr();
|
||||
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
|
||||
std::string_view cellNameId;
|
||||
if (cell)
|
||||
{
|
||||
ai.stack(MWMechanics::AiFollow(target.ptr().getCellRef().getRefId(),
|
||||
cell->mStore->getCell()->getNameId(), duration, dest.x(), dest.y(), dest.z(), repeat),
|
||||
ptr, cancelOther);
|
||||
}
|
||||
else
|
||||
{
|
||||
ai.stack(MWMechanics::AiFollow(
|
||||
target.ptr().getCellRef().getRefId(), duration, dest.x(), dest.y(), dest.z(), repeat),
|
||||
ptr, cancelOther);
|
||||
}
|
||||
cellNameId = cell->mStore->getCell()->getNameId();
|
||||
ai.stack(
|
||||
MWMechanics::AiFollow(getId(target.ptr()), cellNameId, duration, dest.x(), dest.y(), dest.z(), repeat),
|
||||
ptr, cancelOther);
|
||||
};
|
||||
selfAPI["_startAiEscort"] = [](SelfObject& self, const LObject& target, LCell cell, float duration,
|
||||
const osg::Vec3f& dest, bool repeat, bool cancelOther) {
|
||||
const MWWorld::Ptr& ptr = self.ptr();
|
||||
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
|
||||
// TODO: change AiEscort implementation to accept ptr instead of a non-unique refId.
|
||||
const ESM::RefId& refId = target.ptr().getCellRef().getRefId();
|
||||
int gameHoursDuration = static_cast<int>(std::ceil(duration / 3600.0));
|
||||
auto* esmCell = cell.mStore->getCell();
|
||||
if (esmCell->isExterior())
|
||||
ai.stack(MWMechanics::AiEscort(refId, gameHoursDuration, dest.x(), dest.y(), dest.z(), repeat), ptr,
|
||||
cancelOther);
|
||||
else
|
||||
ai.stack(MWMechanics::AiEscort(
|
||||
refId, esmCell->getNameId(), gameHoursDuration, dest.x(), dest.y(), dest.z(), repeat),
|
||||
ptr, cancelOther);
|
||||
std::string_view cellNameId;
|
||||
if (!esmCell->isExterior())
|
||||
cellNameId = esmCell->getNameId();
|
||||
ai.stack(MWMechanics::AiEscort(
|
||||
getId(target.ptr()), cellNameId, gameHoursDuration, dest.x(), dest.y(), dest.z(), repeat),
|
||||
ptr, cancelOther);
|
||||
};
|
||||
selfAPI["_startAiWander"]
|
||||
= [](SelfObject& self, int distance, int duration, sol::table luaIdle, bool repeat, bool cancelOther) {
|
||||
|
|
|
|||
|
|
@ -546,8 +546,7 @@ namespace MWLua
|
|||
});
|
||||
activeSpellT["caster"]
|
||||
= sol::readonly_property([lua = state.lua_state()](const ActiveSpell& activeSpell) -> sol::object {
|
||||
auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(
|
||||
activeSpell.mParams.getCasterActorId());
|
||||
auto caster = MWBase::Environment::get().getWorldModel()->getPtr(activeSpell.mParams.getCaster());
|
||||
if (caster.isEmpty())
|
||||
return sol::nil;
|
||||
else
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include <components/misc/strings/algorithm.hpp>
|
||||
|
||||
#include <components/esm/generatedrefid.hpp>
|
||||
#include <components/esm3/actoridconverter.hpp>
|
||||
#include <components/esm3/loadench.hpp>
|
||||
#include <components/esm3/loadmgef.hpp>
|
||||
#include <components/esm3/loadstat.hpp>
|
||||
|
|
@ -30,6 +31,7 @@
|
|||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/manualref.hpp"
|
||||
#include "../mwworld/worldmodel.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
|
@ -103,20 +105,19 @@ namespace MWMechanics
|
|||
const MWWorld::Ptr& caster, const ESM::RefId& id, std::string_view sourceName, ESM::RefNum item)
|
||||
: mSourceSpellId(id)
|
||||
, mDisplayName(sourceName)
|
||||
, mCasterActorId(-1)
|
||||
, mItem(item)
|
||||
, mFlags()
|
||||
, mWorsenings(-1)
|
||||
{
|
||||
if (!caster.isEmpty() && caster.getClass().isActor())
|
||||
mCasterActorId = caster.getClass().getCreatureStats(caster).getActorId();
|
||||
mCaster = caster.getCellRef().getRefNum();
|
||||
}
|
||||
|
||||
ActiveSpells::ActiveSpellParams::ActiveSpellParams(
|
||||
const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances)
|
||||
: mSourceSpellId(spell->mId)
|
||||
, mDisplayName(spell->mName)
|
||||
, mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
|
||||
, mCaster(actor.getCellRef().getRefNum())
|
||||
, mFlags()
|
||||
, mWorsenings(-1)
|
||||
{
|
||||
|
|
@ -131,7 +132,7 @@ namespace MWMechanics
|
|||
const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, const MWWorld::Ptr& actor)
|
||||
: mSourceSpellId(item.getCellRef().getRefId())
|
||||
, mDisplayName(item.getClass().getName(item))
|
||||
, mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
|
||||
, mCaster(actor.getCellRef().getRefNum())
|
||||
, mItem(item.getCellRef().getRefNum())
|
||||
, mFlags()
|
||||
, mWorsenings(-1)
|
||||
|
|
@ -146,7 +147,7 @@ namespace MWMechanics
|
|||
, mSourceSpellId(params.mSourceSpellId)
|
||||
, mEffects(params.mEffects)
|
||||
, mDisplayName(params.mDisplayName)
|
||||
, mCasterActorId(params.mCasterActorId)
|
||||
, mCaster(params.mCaster)
|
||||
, mItem(params.mItem)
|
||||
, mFlags(params.mFlags)
|
||||
, mWorsenings(params.mWorsenings)
|
||||
|
|
@ -157,7 +158,7 @@ namespace MWMechanics
|
|||
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor)
|
||||
: mSourceSpellId(params.mSourceSpellId)
|
||||
, mDisplayName(params.mDisplayName)
|
||||
, mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
|
||||
, mCaster(actor.getCellRef().getRefNum())
|
||||
, mItem(params.mItem)
|
||||
, mFlags(params.mFlags)
|
||||
, mWorsenings(-1)
|
||||
|
|
@ -171,7 +172,7 @@ namespace MWMechanics
|
|||
params.mSourceSpellId = mSourceSpellId;
|
||||
params.mEffects = mEffects;
|
||||
params.mDisplayName = mDisplayName;
|
||||
params.mCasterActorId = mCasterActorId;
|
||||
params.mCaster = mCaster;
|
||||
params.mItem = mItem;
|
||||
params.mFlags = mFlags;
|
||||
params.mWorsenings = mWorsenings;
|
||||
|
|
@ -375,8 +376,7 @@ namespace MWMechanics
|
|||
bool ActiveSpells::updateActiveSpell(
|
||||
const MWWorld::Ptr& ptr, float duration, Collection::iterator& spellIt, UpdateContext& context)
|
||||
{
|
||||
const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(
|
||||
spellIt->mCasterActorId); // Maybe make this search outside active grid?
|
||||
const auto caster = MWBase::Environment::get().getWorldModel()->getPtr(spellIt->mCaster);
|
||||
bool removedSpell = false;
|
||||
std::optional<ActiveSpellParams> reflected;
|
||||
for (auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
|
||||
|
|
@ -484,8 +484,8 @@ namespace MWMechanics
|
|||
if (!spell.hasFlag(ESM::ActiveSpells::Flag_Stackable))
|
||||
{
|
||||
auto found = std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& existing) {
|
||||
return spell.mSourceSpellId == existing.mSourceSpellId
|
||||
&& spell.mCasterActorId == existing.mCasterActorId && spell.mItem == existing.mItem;
|
||||
return spell.mSourceSpellId == existing.mSourceSpellId && spell.mCaster == existing.mCaster
|
||||
&& spell.mItem == existing.mItem;
|
||||
});
|
||||
if (found != mSpells.end())
|
||||
{
|
||||
|
|
@ -659,9 +659,9 @@ namespace MWMechanics
|
|||
ptr);
|
||||
}
|
||||
|
||||
void ActiveSpells::purge(const MWWorld::Ptr& ptr, int casterActorId)
|
||||
void ActiveSpells::purge(const MWWorld::Ptr& ptr, ESM::RefNum actor)
|
||||
{
|
||||
purge([=](const ActiveSpellParams& params) { return params.mCasterActorId == casterActorId; }, ptr);
|
||||
purge([=](const ActiveSpellParams& params) { return params.mCaster == actor; }, ptr);
|
||||
}
|
||||
|
||||
void ActiveSpells::clear(const MWWorld::Ptr& ptr)
|
||||
|
|
@ -698,6 +698,22 @@ namespace MWMechanics
|
|||
}
|
||||
for (const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue)
|
||||
mQueue.emplace_back(ActiveSpellParams{ spell });
|
||||
if (state.mActorIdConverter)
|
||||
{
|
||||
const auto convertSummons = [converter = state.mActorIdConverter](auto& collection) {
|
||||
for (ActiveSpellParams& params : collection)
|
||||
{
|
||||
converter->convert(params.mCaster, params.mCaster.mIndex);
|
||||
for (ESM::ActiveEffect& effect : params.mEffects)
|
||||
{
|
||||
if (ESM::RefNum* refNum = std::get_if<ESM::RefNum>(&effect.mArg))
|
||||
converter->convert(*refNum, refNum->mIndex);
|
||||
}
|
||||
}
|
||||
};
|
||||
convertSummons(mSpells);
|
||||
convertSummons(mQueue);
|
||||
}
|
||||
}
|
||||
|
||||
void ActiveSpells::unloadActor(const MWWorld::Ptr& ptr)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ namespace MWMechanics
|
|||
ESM::RefId mSourceSpellId;
|
||||
std::vector<ActiveEffect> mEffects;
|
||||
std::string mDisplayName;
|
||||
int mCasterActorId;
|
||||
ESM::RefNum mCaster;
|
||||
ESM::RefNum mItem;
|
||||
ESM::ActiveSpells::Flags mFlags;
|
||||
int mWorsenings;
|
||||
|
|
@ -69,7 +69,7 @@ namespace MWMechanics
|
|||
const std::vector<ActiveEffect>& getEffects() const { return mEffects; }
|
||||
std::vector<ActiveEffect>& getEffects() { return mEffects; }
|
||||
|
||||
int getCasterActorId() const { return mCasterActorId; }
|
||||
ESM::RefNum getCaster() const { return mCaster; }
|
||||
|
||||
int getWorsenings() const { return mWorsenings; }
|
||||
|
||||
|
|
@ -157,8 +157,8 @@ namespace MWMechanics
|
|||
void purge(EffectPredicate predicate, const MWWorld::Ptr& ptr);
|
||||
void purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr);
|
||||
|
||||
/// Remove all effects that were cast by \a casterActorId
|
||||
void purge(const MWWorld::Ptr& ptr, int casterActorId);
|
||||
/// Remove all effects that were cast by \a actor
|
||||
void purge(const MWWorld::Ptr& ptr, ESM::RefNum actor);
|
||||
|
||||
/// Remove all spells
|
||||
void clear(const MWWorld::Ptr& ptr);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/scene.hpp"
|
||||
#include "../mwworld/worldmodel.hpp"
|
||||
|
||||
#include "../mwbase/dialoguemanager.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
|
@ -178,7 +179,7 @@ namespace
|
|||
{
|
||||
if (effect.mEffectId != ESM::MagicEffect::Soultrap || effect.mMagnitude <= 0.f)
|
||||
continue;
|
||||
MWWorld::Ptr caster = world->searchPtrViaActorId(params.getCasterActorId());
|
||||
MWWorld::Ptr caster = MWBase::Environment::get().getWorldModel()->getPtr(params.getCaster());
|
||||
if (caster.isEmpty() || !caster.getClass().isActor())
|
||||
continue;
|
||||
|
||||
|
|
@ -641,12 +642,13 @@ namespace MWMechanics
|
|||
if (creatureStats1.getAiSequence().isInCombat(ally))
|
||||
continue;
|
||||
|
||||
if (creatureStats2.matchesActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId()))
|
||||
ESM::RefNum allyHitNum = ally.getClass().getCreatureStats(ally).getHitAttemptActor();
|
||||
if (allyHitNum.isSet() && actor2.getCellRef().getRefNum() == allyHitNum)
|
||||
{
|
||||
mechanicsManager->startCombat(actor1, actor2, &cachedAllies.getActorsSidingWith(actor2));
|
||||
// Also set the same hit attempt actor. Otherwise, if fighting the player, they may stop combat
|
||||
// if the player gets out of reach, while the ally would continue combat with the player
|
||||
creatureStats1.setHitAttemptActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId());
|
||||
creatureStats1.setHitAttemptActor(allyHitNum);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -789,8 +791,7 @@ namespace MWMechanics
|
|||
{
|
||||
bool actorKilled = false;
|
||||
|
||||
MWWorld::Ptr caster
|
||||
= MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.getCasterActorId());
|
||||
MWWorld::Ptr caster = MWBase::Environment::get().getWorldModel()->getPtr(spell.getCaster());
|
||||
if (caster.isEmpty())
|
||||
continue;
|
||||
for (const auto& effect : spell.getEffects())
|
||||
|
|
@ -831,7 +832,7 @@ namespace MWMechanics
|
|||
if (!creature.isInCell())
|
||||
return;
|
||||
|
||||
if (!creatureStats.getSummonedCreatureMap().empty() || !creatureStats.getSummonedCreatureGraveyard().empty())
|
||||
if (!creatureStats.getSummonedCreatureMap().empty())
|
||||
updateSummons(creature, mTimerDisposeSummonsCorpses == 0.f);
|
||||
}
|
||||
|
||||
|
|
@ -1155,9 +1156,10 @@ namespace MWMechanics
|
|||
= esmStore.get<ESM::GameSetting>().find("iCrimeThresholdMultiplier")->mValue.getInteger();
|
||||
if (playerStats.getBounty() >= cutoff * iCrimeThresholdMultiplier)
|
||||
{
|
||||
ESM::RefNum playerNum = player.getCellRef().getRefNum();
|
||||
mechanicsManager->startCombat(ptr, player, &cachedAllies.getActorsSidingWith(player));
|
||||
// Stops the guard from quitting combat if player is unreachable
|
||||
creatureStats.setHitAttemptActorId(playerClass.getCreatureStats(player).getActorId());
|
||||
creatureStats.setHitAttemptActor(playerNum);
|
||||
}
|
||||
else
|
||||
creatureStats.getAiSequence().stack(AiPursue(player), ptr);
|
||||
|
|
@ -1517,13 +1519,14 @@ namespace MWMechanics
|
|||
SidingCache cachedAllies{ *this, true }; // will be filled as engageCombat iterates
|
||||
|
||||
const bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive();
|
||||
const int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId();
|
||||
if (attackedByPlayerId != -1)
|
||||
const ESM::RefNum attackedByPlayerNum = player.getClass().getCreatureStats(player).getHitAttemptActor();
|
||||
if (attackedByPlayerNum.isSet())
|
||||
{
|
||||
const MWWorld::Ptr playerHitAttemptActor = world->searchPtrViaActorId(attackedByPlayerId);
|
||||
const MWWorld::Ptr playerHitAttemptActor
|
||||
= MWBase::Environment::get().getWorldModel()->getPtr(attackedByPlayerNum);
|
||||
|
||||
if (!playerHitAttemptActor.isInCell())
|
||||
player.getClass().getCreatureStats(player).setHitAttemptActorId(-1);
|
||||
player.getClass().getCreatureStats(player).setHitAttemptActor({});
|
||||
}
|
||||
const int actorsProcessingRange = Settings::game().mActorsProcessingRange;
|
||||
|
||||
|
|
@ -1548,10 +1551,10 @@ namespace MWMechanics
|
|||
|| !actor.getPtr().getClass().getCreatureStats(actor.getPtr()).getAiSequence().isInCombat()
|
||||
|| !inProcessingRange))
|
||||
{
|
||||
actor.getPtr().getClass().getCreatureStats(actor.getPtr()).setHitAttemptActorId(-1);
|
||||
if (player.getClass().getCreatureStats(player).getHitAttemptActorId()
|
||||
== actor.getPtr().getClass().getCreatureStats(actor.getPtr()).getActorId())
|
||||
player.getClass().getCreatureStats(player).setHitAttemptActorId(-1);
|
||||
actor.getPtr().getClass().getCreatureStats(actor.getPtr()).setHitAttemptActor({});
|
||||
ESM::RefNum playerHitNum = player.getClass().getCreatureStats(player).getHitAttemptActor();
|
||||
if (playerHitNum.isSet() && playerHitNum == actor.getPtr().getCellRef().getRefNum())
|
||||
player.getClass().getCreatureStats(player).setHitAttemptActor({});
|
||||
}
|
||||
|
||||
const Misc::TimerStatus engageCombatTimerStatus = actor.updateEngageCombatTimer(duration);
|
||||
|
|
@ -1812,7 +1815,7 @@ namespace MWMechanics
|
|||
= stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Vampirism).getMagnitude();
|
||||
stats.getActiveSpells().clear(actor.getPtr());
|
||||
// Make sure spell effects are removed
|
||||
purgeSpellEffects(stats.getActorId());
|
||||
purgeSpellEffects(actor.getPtr().getCellRef().getRefNum());
|
||||
|
||||
stats.getMagicEffects().add(ESM::MagicEffect::Vampirism, vampirism);
|
||||
|
||||
|
|
@ -1830,9 +1833,9 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
void Actors::cleanupSummonedCreature(MWMechanics::CreatureStats& casterStats, int creatureActorId) const
|
||||
void Actors::cleanupSummonedCreature(ESM::RefNum creature) const
|
||||
{
|
||||
const MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId);
|
||||
const MWWorld::Ptr ptr = MWBase::Environment::get().getWorldModel()->getPtr(creature);
|
||||
if (!ptr.isEmpty())
|
||||
{
|
||||
MWBase::Environment::get().getWorld()->deleteObject(ptr);
|
||||
|
|
@ -1845,24 +1848,16 @@ namespace MWMechanics
|
|||
ptr.getRefData().getPosition().asVec3());
|
||||
|
||||
// Remove the summoned creature's summoned creatures as well
|
||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
auto& creatureMap = stats.getSummonedCreatureMap();
|
||||
for (const auto& creature : creatureMap)
|
||||
cleanupSummonedCreature(stats, creature.second);
|
||||
auto& creatureMap = ptr.getClass().getCreatureStats(ptr).getSummonedCreatureMap();
|
||||
for (const auto& [_, refNum] : creatureMap)
|
||||
cleanupSummonedCreature(refNum);
|
||||
creatureMap.clear();
|
||||
}
|
||||
else if (creatureActorId != -1)
|
||||
{
|
||||
// We didn't find the creature. It's probably in an inactive cell.
|
||||
// Add to graveyard so we can delete it when the cell becomes active.
|
||||
std::vector<int>& graveyard = casterStats.getSummonedCreatureGraveyard();
|
||||
graveyard.push_back(creatureActorId);
|
||||
}
|
||||
|
||||
purgeSpellEffects(creatureActorId);
|
||||
purgeSpellEffects(creature);
|
||||
}
|
||||
|
||||
void Actors::purgeSpellEffects(int casterActorId) const
|
||||
void Actors::purgeSpellEffects(ESM::RefNum creature) const
|
||||
{
|
||||
for (const Actor& actor : mActors)
|
||||
{
|
||||
|
|
@ -1870,7 +1865,7 @@ namespace MWMechanics
|
|||
continue;
|
||||
MWMechanics::ActiveSpells& spells
|
||||
= actor.getPtr().getClass().getCreatureStats(actor.getPtr()).getActiveSpells();
|
||||
spells.purge(actor.getPtr(), casterActorId);
|
||||
spells.purge(actor.getPtr(), creature);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ namespace MWMechanics
|
|||
{
|
||||
class Actor;
|
||||
class CharacterController;
|
||||
class CreatureStats;
|
||||
class SidingCache;
|
||||
|
||||
class Actors
|
||||
|
|
@ -128,7 +127,7 @@ namespace MWMechanics
|
|||
|
||||
bool isAnyObjectInRange(const osg::Vec3f& position, float radius) const;
|
||||
|
||||
void cleanupSummonedCreature(CreatureStats& casterStats, int creatureActorId) const;
|
||||
void cleanupSummonedCreature(ESM::RefNum creature) const;
|
||||
|
||||
/// Returns the list of actors which are siding with the given actor in fights
|
||||
/**ie AiFollow or AiEscort is active and the target is the actor **/
|
||||
|
|
@ -189,7 +188,7 @@ namespace MWMechanics
|
|||
|
||||
void killDeadActors();
|
||||
|
||||
void purgeSpellEffects(int casterActorId) const;
|
||||
void purgeSpellEffects(ESM::RefNum creature) const;
|
||||
|
||||
void predictAndAvoidCollisions(float duration) const;
|
||||
|
||||
|
|
|
|||
|
|
@ -36,18 +36,24 @@ namespace
|
|||
|
||||
osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target,
|
||||
const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength);
|
||||
|
||||
bool hitAttemptMatchesTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target)
|
||||
{
|
||||
ESM::RefNum hitNum = actor.getClass().getCreatureStats(actor).getHitAttemptActor();
|
||||
return hitNum.isSet() && target.getCellRef().getRefNum() == hitNum;
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
AiCombat::AiCombat(const MWWorld::Ptr& actor)
|
||||
{
|
||||
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
|
||||
mTargetActor = actor.getCellRef().getRefNum();
|
||||
}
|
||||
|
||||
AiCombat::AiCombat(const ESM::AiSequence::AiCombat* combat)
|
||||
{
|
||||
mTargetActorId = combat->mTargetActorId;
|
||||
mTargetActor = combat->mTargetActor;
|
||||
}
|
||||
|
||||
void AiCombat::init() {}
|
||||
|
|
@ -109,14 +115,11 @@ namespace MWMechanics
|
|||
if (actor.getClass().getCreatureStats(actor).isDead())
|
||||
return true;
|
||||
|
||||
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
|
||||
if (target.isEmpty())
|
||||
return true;
|
||||
const MWWorld::Ptr target = getTarget(); // The target to follow
|
||||
|
||||
if (!target.getCellRef().getCount()
|
||||
|| !target.getRefData().isEnabled() // Really we should be checking whether the target is currently
|
||||
// registered with the MechanicsManager
|
||||
|| target.getClass().getCreatureStats(target).isDead())
|
||||
// Stop if the target doesn't exist
|
||||
if (target.isEmpty() || !target.getCellRef().getCount() || !target.getRefData().isEnabled()
|
||||
|| target.getClass().getCreatureStats(target).isDead() || !target.getRefData().getBaseNode())
|
||||
return true;
|
||||
|
||||
if (actor == target) // This should never happen.
|
||||
|
|
@ -194,8 +197,7 @@ namespace MWMechanics
|
|||
= (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), target)
|
||||
!= playerFollowersAndEscorters.end());
|
||||
if ((target == MWMechanics::getPlayer() || targetSidesWithPlayer)
|
||||
&& ((stats.getHitAttemptActorId() == target.getClass().getCreatureStats(target).getActorId())
|
||||
|| (target.getClass().getCreatureStats(target).getHitAttemptActorId() == stats.getActorId())))
|
||||
&& (hitAttemptMatchesTarget(actor, target) || hitAttemptMatchesTarget(target, actor)))
|
||||
forceFlee = true;
|
||||
else // Otherwise end combat
|
||||
return true;
|
||||
|
|
@ -491,19 +493,10 @@ namespace MWMechanics
|
|||
storage.mRotateMove = !smoothTurn(actor, targetAngleRadians, axis, eps);
|
||||
}
|
||||
|
||||
MWWorld::Ptr AiCombat::getTarget() const
|
||||
{
|
||||
if (mCachedTarget.isEmpty() || mCachedTarget.mRef->isDeleted() || !mCachedTarget.getRefData().isEnabled())
|
||||
{
|
||||
mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
|
||||
}
|
||||
return mCachedTarget;
|
||||
}
|
||||
|
||||
void AiCombat::writeState(ESM::AiSequence::AiSequence& sequence) const
|
||||
{
|
||||
auto combat = std::make_unique<ESM::AiSequence::AiCombat>();
|
||||
combat->mTargetActorId = mTargetActorId;
|
||||
combat->mTargetActor = mTargetActor;
|
||||
|
||||
ESM::AiSequence::AiPackageContainer package;
|
||||
package.mType = ESM::AiSequence::Ai_Combat;
|
||||
|
|
|
|||
|
|
@ -100,9 +100,6 @@ namespace MWMechanics
|
|||
return options;
|
||||
}
|
||||
|
||||
/// Returns target ID
|
||||
MWWorld::Ptr getTarget() const override;
|
||||
|
||||
void writeState(ESM::AiSequence::AiSequence& sequence) const override;
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -24,15 +24,16 @@
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
AiEscort::AiEscort(const ESM::RefId& actorId, int duration, float x, float y, float z, bool repeat)
|
||||
AiEscort::AiEscort(ESM::RefNum actor, std::string_view cellId, int duration, float x, float y, float z, bool repeat)
|
||||
: TypedAiPackage<AiEscort>(repeat)
|
||||
, mCellId(cellId)
|
||||
, mX(x)
|
||||
, mY(y)
|
||||
, mZ(z)
|
||||
, mDuration(static_cast<float>(duration))
|
||||
, mRemainingDuration(static_cast<float>(duration))
|
||||
{
|
||||
mTargetActorRefId = actorId;
|
||||
mTargetActor = actor;
|
||||
}
|
||||
|
||||
AiEscort::AiEscort(
|
||||
|
|
@ -58,7 +59,7 @@ namespace MWMechanics
|
|||
, mRemainingDuration(escort->mRemainingDuration)
|
||||
{
|
||||
mTargetActorRefId = escort->mTargetId;
|
||||
mTargetActorId = escort->mTargetActorId;
|
||||
mTargetActor = escort->mTargetActor;
|
||||
}
|
||||
|
||||
bool AiEscort::execute(
|
||||
|
|
@ -131,7 +132,7 @@ namespace MWMechanics
|
|||
escort->mData.mZ = mZ;
|
||||
escort->mData.mDuration = static_cast<int16_t>(mDuration);
|
||||
escort->mTargetId = mTargetActorRefId;
|
||||
escort->mTargetActorId = mTargetActorId;
|
||||
escort->mTargetActor = mTargetActor;
|
||||
escort->mRemainingDuration = mRemainingDuration;
|
||||
escort->mCellId = mCellId;
|
||||
escort->mRepeat = getRepeat();
|
||||
|
|
|
|||
|
|
@ -20,13 +20,10 @@ namespace MWMechanics
|
|||
class AiEscort final : public TypedAiPackage<AiEscort>
|
||||
{
|
||||
public:
|
||||
/// Implementation of AiEscort
|
||||
/** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or
|
||||
/** The Actor will escort the specified actor to the position x, y, z until they reach their position, or
|
||||
they run out of time \implement AiEscort **/
|
||||
AiEscort(const ESM::RefId& actorId, int duration, float x, float y, float z, bool repeat);
|
||||
/// Implementation of AiEscortCell
|
||||
/** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or
|
||||
they run out of time \implement AiEscortCell **/
|
||||
AiEscort(ESM::RefNum actor, std::string_view cellId, int duration, float x, float y, float z, bool repeat);
|
||||
/// Implementation of AiEscort/AiEscortCell
|
||||
AiEscort(
|
||||
const ESM::RefId& actorId, std::string_view cellId, int duration, float x, float y, float z, bool repeat);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ namespace MWMechanics
|
|||
{
|
||||
int AiFollow::mFollowIndexCounter = 0;
|
||||
|
||||
AiFollow::AiFollow(const ESM::RefId& actorId, float duration, float x, float y, float z, bool repeat)
|
||||
AiFollow::AiFollow(
|
||||
ESM::RefNum actor, std::string_view cellId, float duration, float x, float y, float z, bool repeat)
|
||||
: TypedAiPackage<AiFollow>(repeat)
|
||||
, mAlwaysFollow(false)
|
||||
, mDuration(duration)
|
||||
|
|
@ -38,10 +39,11 @@ namespace MWMechanics
|
|||
, mX(x)
|
||||
, mY(y)
|
||||
, mZ(z)
|
||||
, mCellId(cellId)
|
||||
, mActive(false)
|
||||
, mFollowIndex(mFollowIndexCounter++)
|
||||
{
|
||||
mTargetActorRefId = actorId;
|
||||
mTargetActor = actor;
|
||||
}
|
||||
|
||||
AiFollow::AiFollow(
|
||||
|
|
@ -72,7 +74,7 @@ namespace MWMechanics
|
|||
, mFollowIndex(mFollowIndexCounter++)
|
||||
{
|
||||
mTargetActorRefId = actor.getCellRef().getRefId();
|
||||
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
|
||||
mTargetActor = actor.getCellRef().getRefNum();
|
||||
}
|
||||
|
||||
AiFollow::AiFollow(const ESM::AiSequence::AiFollow* follow)
|
||||
|
|
@ -89,7 +91,7 @@ namespace MWMechanics
|
|||
, mFollowIndex(mFollowIndexCounter++)
|
||||
{
|
||||
mTargetActorRefId = follow->mTargetId;
|
||||
mTargetActorId = follow->mTargetActorId;
|
||||
mTargetActor = follow->mTargetActor;
|
||||
}
|
||||
|
||||
bool AiFollow::execute(
|
||||
|
|
@ -246,7 +248,7 @@ namespace MWMechanics
|
|||
follow->mData.mZ = mZ;
|
||||
follow->mData.mDuration = static_cast<int16_t>(mDuration);
|
||||
follow->mTargetId = mTargetActorRefId;
|
||||
follow->mTargetActorId = mTargetActorId;
|
||||
follow->mTargetActor = mTargetActor;
|
||||
follow->mRemainingDuration = mRemainingDuration;
|
||||
follow->mCellId = mCellId;
|
||||
follow->mAlwaysFollow = mAlwaysFollow;
|
||||
|
|
|
|||
|
|
@ -41,9 +41,7 @@ namespace MWMechanics
|
|||
class AiFollow final : public TypedAiPackage<AiFollow>
|
||||
{
|
||||
public:
|
||||
/// Follow Actor for duration or until you arrive at a world position
|
||||
AiFollow(const ESM::RefId& actorId, float duration, float x, float y, float z, bool repeat);
|
||||
/// Follow Actor for duration or until you arrive at a position in a cell
|
||||
AiFollow(ESM::RefNum actor, std::string_view cellId, float duration, float x, float y, float z, bool repeat);
|
||||
AiFollow(
|
||||
const ESM::RefId& actorId, std::string_view cellId, float duration, float x, float y, float z, bool repeat);
|
||||
/// Follow Actor indefinitely
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/worldmodel.hpp"
|
||||
|
||||
#include "../mwphysics/raycasting.hpp"
|
||||
|
||||
|
|
@ -51,71 +52,35 @@ MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options
|
|||
: mTypeId(typeId)
|
||||
, mOptions(options)
|
||||
, mReaction(MWBase::Environment::get().getWorld()->getPrng())
|
||||
, mTargetActorId(-1)
|
||||
, mCachedTarget()
|
||||
, mRotateOnTheRunChecks(0)
|
||||
, mIsShortcutting(false)
|
||||
, mShortcutProhibited(false)
|
||||
, mShortcutFailPos()
|
||||
{
|
||||
}
|
||||
|
||||
MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
|
||||
{
|
||||
if (!mCachedTarget.isEmpty())
|
||||
{
|
||||
if (mCachedTarget.mRef->isDeleted() || !mCachedTarget.getRefData().isEnabled())
|
||||
mCachedTarget = MWWorld::Ptr();
|
||||
else
|
||||
return mCachedTarget;
|
||||
}
|
||||
|
||||
if (mTargetActorId == -2)
|
||||
return MWWorld::Ptr();
|
||||
|
||||
if (mTargetActorId == -1)
|
||||
{
|
||||
if (mTargetActorRefId.empty())
|
||||
{
|
||||
mTargetActorId = -2;
|
||||
return MWWorld::Ptr();
|
||||
}
|
||||
mCachedTarget = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false);
|
||||
if (mCachedTarget.isEmpty())
|
||||
{
|
||||
mTargetActorId = -2;
|
||||
return mCachedTarget;
|
||||
}
|
||||
else
|
||||
mTargetActorId = mCachedTarget.getClass().getCreatureStats(mCachedTarget).getActorId();
|
||||
}
|
||||
|
||||
if (mTargetActorId != -1)
|
||||
mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
|
||||
if (mTargetActor.isSet())
|
||||
return MWBase::Environment::get().getWorldModel()->getPtr(mTargetActor);
|
||||
if (mTargetActorRefId.empty() || mTargetNotFound)
|
||||
return {};
|
||||
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false);
|
||||
if (ptr.isEmpty())
|
||||
mTargetNotFound = true;
|
||||
else
|
||||
return MWWorld::Ptr();
|
||||
|
||||
return mCachedTarget;
|
||||
{
|
||||
MWBase::Environment::get().getWorldModel()->registerPtr(ptr);
|
||||
mTargetActor = ptr.getCellRef().getRefNum();
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool MWMechanics::AiPackage::targetIs(const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
if (mTargetActorId == -2)
|
||||
return ptr.isEmpty();
|
||||
else if (mTargetActorId == -1)
|
||||
{
|
||||
if (mTargetActorRefId.empty())
|
||||
{
|
||||
mTargetActorId = -2;
|
||||
return ptr.isEmpty();
|
||||
}
|
||||
if (!ptr.isEmpty() && ptr.getCellRef().getRefId() == mTargetActorRefId)
|
||||
return getTarget() == ptr;
|
||||
if (ptr.isEmpty())
|
||||
return getTarget() == ptr;
|
||||
if (mTargetActor.isSet())
|
||||
return ptr.getCellRef().getRefNum() == mTargetActor;
|
||||
if (ptr.getCellRef().getRefId() != mTargetActorRefId)
|
||||
return false;
|
||||
}
|
||||
if (ptr.isEmpty() || !ptr.getClass().isActor())
|
||||
return false;
|
||||
return ptr.getClass().getCreatureStats(ptr).getActorId() == mTargetActorId;
|
||||
return getTarget() == ptr;
|
||||
}
|
||||
|
||||
void MWMechanics::AiPackage::reset()
|
||||
|
|
@ -125,7 +90,7 @@ void MWMechanics::AiPackage::reset()
|
|||
mIsShortcutting = false;
|
||||
mShortcutProhibited = false;
|
||||
mShortcutFailPos = osg::Vec3f();
|
||||
mCachedTarget = MWWorld::Ptr();
|
||||
mTargetNotFound = false;
|
||||
|
||||
mPathFinder.clearPath();
|
||||
mObstacleCheck.clear();
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace ESM
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
class AiSequence;
|
||||
class CharacterController;
|
||||
class PathgridGraph;
|
||||
|
||||
|
|
@ -171,15 +172,15 @@ namespace MWMechanics
|
|||
AiReactionTimer mReaction;
|
||||
|
||||
ESM::RefId mTargetActorRefId;
|
||||
mutable int mTargetActorId;
|
||||
mutable MWWorld::Ptr mCachedTarget;
|
||||
|
||||
short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility
|
||||
|
||||
bool mIsShortcutting; // if shortcutting at the moment
|
||||
bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt
|
||||
mutable ESM::RefNum mTargetActor;
|
||||
osg::Vec3f mShortcutFailPos; // position of last shortcut fail
|
||||
float mLastDestinationTolerance = 0;
|
||||
short mRotateOnTheRunChecks = 0; // attempts to check rotation to the pathpoint on the run possibility
|
||||
mutable bool mTargetNotFound = false;
|
||||
bool mIsShortcutting = false; // if shortcutting at the moment
|
||||
bool mShortcutProhibited = false; // shortcutting may be prohibited after unsuccessful attempt
|
||||
|
||||
friend class AiSequence;
|
||||
|
||||
private:
|
||||
bool isNearInactiveCell(osg::Vec3f position);
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ namespace MWMechanics
|
|||
|
||||
AiPursue::AiPursue(const MWWorld::Ptr& actor)
|
||||
{
|
||||
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
|
||||
mTargetActor = actor.getCellRef().getRefNum();
|
||||
}
|
||||
|
||||
AiPursue::AiPursue(const ESM::AiSequence::AiPursue* pursue)
|
||||
{
|
||||
mTargetActorId = pursue->mTargetActorId;
|
||||
mTargetActor = pursue->mTargetActor;
|
||||
}
|
||||
|
||||
bool AiPursue::execute(
|
||||
|
|
@ -33,12 +33,11 @@ namespace MWMechanics
|
|||
if (actor.getClass().getCreatureStats(actor).isDead())
|
||||
return true;
|
||||
|
||||
const MWWorld::Ptr target
|
||||
= MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); // The target to follow
|
||||
const MWWorld::Ptr target = getTarget(); // The target to follow
|
||||
|
||||
// Stop if the target doesn't exist
|
||||
// Really we should be checking whether the target is currently registered with the MechanicsManager
|
||||
if (target == MWWorld::Ptr() || !target.getCellRef().getCount() || !target.getRefData().isEnabled())
|
||||
if (target.isEmpty() || !target.getCellRef().getCount() || !target.getRefData().isEnabled()
|
||||
|| !target.getRefData().getBaseNode())
|
||||
return true;
|
||||
|
||||
if (isTargetMagicallyHidden(target)
|
||||
|
|
@ -79,23 +78,10 @@ namespace MWMechanics
|
|||
return false;
|
||||
}
|
||||
|
||||
MWWorld::Ptr AiPursue::getTarget() const
|
||||
{
|
||||
if (!mCachedTarget.isEmpty())
|
||||
{
|
||||
if (mCachedTarget.mRef->isDeleted() || !mCachedTarget.getRefData().isEnabled())
|
||||
mCachedTarget = MWWorld::Ptr();
|
||||
else
|
||||
return mCachedTarget;
|
||||
}
|
||||
mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
|
||||
return mCachedTarget;
|
||||
}
|
||||
|
||||
void AiPursue::writeState(ESM::AiSequence::AiSequence& sequence) const
|
||||
{
|
||||
auto pursue = std::make_unique<ESM::AiSequence::AiPursue>();
|
||||
pursue->mTargetActorId = mTargetActorId;
|
||||
pursue->mTargetActor = mTargetActor;
|
||||
|
||||
ESM::AiSequence::AiPackageContainer package;
|
||||
package.mType = ESM::AiSequence::Ai_Pursue;
|
||||
|
|
|
|||
|
|
@ -39,8 +39,6 @@ namespace MWMechanics
|
|||
return options;
|
||||
}
|
||||
|
||||
MWWorld::Ptr getTarget() const override;
|
||||
|
||||
void writeState(ESM::AiSequence::AiSequence& sequence) const override;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <limits>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/esm3/actoridconverter.hpp>
|
||||
#include <components/esm3/aisequence.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
|
@ -538,6 +539,7 @@ namespace MWMechanics
|
|||
for (auto& container : sequence.mPackages)
|
||||
{
|
||||
std::unique_ptr<MWMechanics::AiPackage> package;
|
||||
bool hasTarget = false;
|
||||
switch (container.mType)
|
||||
{
|
||||
case ESM::AiSequence::Ai_Wander:
|
||||
|
|
@ -560,12 +562,14 @@ namespace MWMechanics
|
|||
{
|
||||
package = std::make_unique<AiEscort>(
|
||||
&static_cast<const ESM::AiSequence::AiEscort&>(*container.mPackage));
|
||||
hasTarget = true;
|
||||
break;
|
||||
}
|
||||
case ESM::AiSequence::Ai_Follow:
|
||||
{
|
||||
package = std::make_unique<AiFollow>(
|
||||
&static_cast<const ESM::AiSequence::AiFollow&>(*container.mPackage));
|
||||
hasTarget = true;
|
||||
break;
|
||||
}
|
||||
case ESM::AiSequence::Ai_Activate:
|
||||
|
|
@ -578,12 +582,14 @@ namespace MWMechanics
|
|||
{
|
||||
package = std::make_unique<AiCombat>(
|
||||
&static_cast<const ESM::AiSequence::AiCombat&>(*container.mPackage));
|
||||
hasTarget = true;
|
||||
break;
|
||||
}
|
||||
case ESM::AiSequence::Ai_Pursue:
|
||||
{
|
||||
package = std::make_unique<AiPursue>(
|
||||
&static_cast<const ESM::AiSequence::AiPursue&>(*container.mPackage));
|
||||
hasTarget = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
@ -592,6 +598,8 @@ namespace MWMechanics
|
|||
|
||||
if (!package.get())
|
||||
continue;
|
||||
if (hasTarget && sequence.mActorIdConverter)
|
||||
sequence.mActorIdConverter->convert(package->mTargetActor, package->mTargetActor.mIndex);
|
||||
|
||||
onPackageAdded(*package);
|
||||
mPackages.push_back(std::move(package));
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
|
||||
#include <components/esm3/actoridconverter.hpp>
|
||||
#include <components/esm3/creaturestats.hpp>
|
||||
#include <components/esm3/esmreader.hpp>
|
||||
#include <components/esm3/esmwriter.hpp>
|
||||
|
|
@ -17,8 +18,6 @@
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
int CreatureStats::sActorId = 0;
|
||||
|
||||
CreatureStats::CreatureStats()
|
||||
{
|
||||
for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get<ESM::Attribute>())
|
||||
|
|
@ -365,14 +364,14 @@ namespace MWMechanics
|
|||
return mLastHitAttemptObject;
|
||||
}
|
||||
|
||||
void CreatureStats::setHitAttemptActorId(int actorId)
|
||||
void CreatureStats::setHitAttemptActor(ESM::RefNum actor)
|
||||
{
|
||||
mHitAttemptActorId = actorId;
|
||||
mHitAttemptActor = actor;
|
||||
}
|
||||
|
||||
int CreatureStats::getHitAttemptActorId() const
|
||||
ESM::RefNum CreatureStats::getHitAttemptActor() const
|
||||
{
|
||||
return mHitAttemptActorId;
|
||||
return mHitAttemptActor;
|
||||
}
|
||||
|
||||
void CreatureStats::addToFallHeight(float height)
|
||||
|
|
@ -539,7 +538,6 @@ namespace MWMechanics
|
|||
state.mRecalcDynamicStats = false;
|
||||
state.mDrawState = static_cast<int>(mDrawState);
|
||||
state.mLevel = mLevel;
|
||||
state.mActorId = mActorId;
|
||||
state.mDeathAnimation = mDeathAnimation;
|
||||
state.mTimeOfDeath = mTimeOfDeath.toEsm();
|
||||
// state.mHitAttemptActorId = mHitAttemptActorId;
|
||||
|
|
@ -550,7 +548,6 @@ namespace MWMechanics
|
|||
mMagicEffects.writeState(state.mMagicEffects);
|
||||
|
||||
state.mSummonedCreatures = mSummonedCreatures;
|
||||
state.mSummonGraveyard = mSummonGraveyard;
|
||||
|
||||
state.mHasAiSettings = true;
|
||||
for (size_t i = 0; i < state.mAiSettings.size(); ++i)
|
||||
|
|
@ -593,10 +590,9 @@ namespace MWMechanics
|
|||
mLastHitAttemptObject = state.mLastHitAttemptObject;
|
||||
mDrawState = DrawState(state.mDrawState);
|
||||
mLevel = state.mLevel;
|
||||
mActorId = state.mActorId;
|
||||
mDeathAnimation = state.mDeathAnimation;
|
||||
mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath);
|
||||
// mHitAttemptActorId = state.mHitAttemptActorId;
|
||||
// mHitAttemptActor = state.mHitAttemptActor;
|
||||
|
||||
mSpells.readState(state.mSpells, this);
|
||||
mActiveSpells.readState(state.mActiveSpells);
|
||||
|
|
@ -604,13 +600,19 @@ namespace MWMechanics
|
|||
mMagicEffects.readState(state.mMagicEffects);
|
||||
|
||||
mSummonedCreatures = state.mSummonedCreatures;
|
||||
mSummonGraveyard = state.mSummonGraveyard;
|
||||
|
||||
if (state.mHasAiSettings)
|
||||
for (size_t i = 0; i < state.mAiSettings.size(); ++i)
|
||||
mAiSettings[i].readState(state.mAiSettings[i]);
|
||||
if (state.mRecalcDynamicStats)
|
||||
recalculateMagicka();
|
||||
if (state.mAiSequence.mActorIdConverter)
|
||||
{
|
||||
for (auto& [_, refNum] : mSummonedCreatures)
|
||||
state.mAiSequence.mActorIdConverter->convert(refNum, refNum.mIndex);
|
||||
auto& graveyard = state.mAiSequence.mActorIdConverter->mGraveyard;
|
||||
graveyard.insert(graveyard.end(), state.mSummonGraveyard.begin(), state.mSummonGraveyard.end());
|
||||
}
|
||||
}
|
||||
|
||||
void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime)
|
||||
|
|
@ -632,36 +634,6 @@ namespace MWMechanics
|
|||
return mGoldPool;
|
||||
}
|
||||
|
||||
int CreatureStats::getActorId()
|
||||
{
|
||||
if (mActorId == -1)
|
||||
mActorId = sActorId++;
|
||||
|
||||
return mActorId;
|
||||
}
|
||||
|
||||
bool CreatureStats::matchesActorId(int id) const
|
||||
{
|
||||
return mActorId != -1 && id == mActorId;
|
||||
}
|
||||
|
||||
void CreatureStats::cleanup()
|
||||
{
|
||||
sActorId = 0;
|
||||
}
|
||||
|
||||
void CreatureStats::writeActorIdCounter(ESM::ESMWriter& esm)
|
||||
{
|
||||
esm.startRecord(ESM::REC_ACTC);
|
||||
esm.writeHNT("COUN", sActorId);
|
||||
esm.endRecord(ESM::REC_ACTC);
|
||||
}
|
||||
|
||||
void CreatureStats::readActorIdCounter(ESM::ESMReader& esm)
|
||||
{
|
||||
esm.getHNT(sActorId, "COUN");
|
||||
}
|
||||
|
||||
signed char CreatureStats::getDeathAnimation() const
|
||||
{
|
||||
return mDeathAnimation;
|
||||
|
|
@ -677,16 +649,11 @@ namespace MWMechanics
|
|||
return mTimeOfDeath;
|
||||
}
|
||||
|
||||
std::multimap<int, int>& CreatureStats::getSummonedCreatureMap()
|
||||
std::multimap<int, ESM::RefNum>& CreatureStats::getSummonedCreatureMap()
|
||||
{
|
||||
return mSummonedCreatures;
|
||||
}
|
||||
|
||||
std::vector<int>& CreatureStats::getSummonedCreatureGraveyard()
|
||||
{
|
||||
return mSummonGraveyard;
|
||||
}
|
||||
|
||||
void CreatureStats::updateAwareness(float duration)
|
||||
{
|
||||
mAwarenessTimer += duration;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ namespace MWMechanics
|
|||
///
|
||||
class CreatureStats
|
||||
{
|
||||
static int sActorId;
|
||||
std::map<ESM::RefId, AttributeValue> mAttributes;
|
||||
DynamicStat<float> mDynamic[3]; // health, magicka, fatigue
|
||||
DrawState mDrawState = DrawState::Nothing;
|
||||
|
|
@ -73,10 +72,9 @@ namespace MWMechanics
|
|||
// The pool of merchant gold (not in inventory)
|
||||
int mGoldPool = 0;
|
||||
|
||||
int mActorId = -1;
|
||||
// Stores an actor that attacked this actor. Only one is stored at a time, and it is not changed if a different
|
||||
// actor attacks. It is cleared when combat ends.
|
||||
int mHitAttemptActorId = -1;
|
||||
ESM::RefNum mHitAttemptActor;
|
||||
|
||||
// The difference between view direction and lower body direction.
|
||||
float mSideMovementAngle = 0;
|
||||
|
|
@ -84,11 +82,7 @@ namespace MWMechanics
|
|||
MWWorld::TimeStamp mTimeOfDeath;
|
||||
|
||||
private:
|
||||
std::multimap<int, int> mSummonedCreatures; // <Effect, ActorId>
|
||||
|
||||
// Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet.
|
||||
// This may be necessary when the creature is in an inactive cell.
|
||||
std::vector<int> mSummonGraveyard;
|
||||
std::multimap<int, ESM::RefNum> mSummonedCreatures; // <Effect, Actor>
|
||||
|
||||
float mAwarenessTimer = 0.f;
|
||||
int mAwarenessRoll = -1;
|
||||
|
|
@ -237,8 +231,7 @@ namespace MWMechanics
|
|||
void setBlock(bool value);
|
||||
bool getBlock() const;
|
||||
|
||||
std::multimap<int, int>& getSummonedCreatureMap(); // <Effect, ActorId of summoned creature>
|
||||
std::vector<int>& getSummonedCreatureGraveyard(); // ActorIds
|
||||
std::multimap<int, ESM::RefNum>& getSummonedCreatureMap(); // <Effect, summoned creature>
|
||||
|
||||
enum Flag
|
||||
{
|
||||
|
|
@ -266,16 +259,13 @@ namespace MWMechanics
|
|||
void setLastHitAttemptObject(const ESM::RefId& objectid);
|
||||
void clearLastHitAttemptObject();
|
||||
const ESM::RefId& getLastHitAttemptObject() const;
|
||||
void setHitAttemptActorId(const int actorId);
|
||||
int getHitAttemptActorId() const;
|
||||
void setHitAttemptActor(ESM::RefNum actorId);
|
||||
ESM::RefNum getHitAttemptActor() const;
|
||||
|
||||
void writeState(ESM::CreatureStats& state) const;
|
||||
|
||||
void readState(const ESM::CreatureStats& state);
|
||||
|
||||
static void writeActorIdCounter(ESM::ESMWriter& esm);
|
||||
static void readActorIdCounter(ESM::ESMReader& esm);
|
||||
|
||||
void setLastRestockTime(MWWorld::TimeStamp tradeTime);
|
||||
MWWorld::TimeStamp getLastRestockTime() const;
|
||||
|
||||
|
|
@ -287,15 +277,6 @@ namespace MWMechanics
|
|||
|
||||
MWWorld::TimeStamp getTimeOfDeath() const;
|
||||
|
||||
int getActorId();
|
||||
///< Will generate an actor ID, if the actor does not have one yet.
|
||||
|
||||
bool matchesActorId(int id) const;
|
||||
///< Check if \a id matches the actor ID of *this (if the actor does not have an ID
|
||||
/// assigned this function will return false).
|
||||
|
||||
static void cleanup();
|
||||
|
||||
float getSideMovementAngle() const { return mSideMovementAngle; }
|
||||
void setSideMovementAngle(float angle) { mSideMovementAngle = angle; }
|
||||
|
||||
|
|
|
|||
|
|
@ -1454,7 +1454,7 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
startCombat(actor, player, &playerFollowers);
|
||||
observerStats.setHitAttemptActorId(player.getClass().getCreatureStats(player).getActorId());
|
||||
observerStats.setHitAttemptActor(player.getCellRef().getRefNum());
|
||||
|
||||
// Apply aggression value to the base Fight rating, so that the actor can continue fighting
|
||||
// after a Calm spell wears off
|
||||
|
|
@ -1734,8 +1734,9 @@ namespace MWMechanics
|
|||
// if guard starts combat with player, guards pursuing player should do the same
|
||||
if (ptr.getClass().isClass(ptr, "Guard"))
|
||||
{
|
||||
const ESM::RefNum playerNum = target.getCellRef().getRefNum();
|
||||
// Stops guard from ending combat if player is unreachable
|
||||
stats.setHitAttemptActorId(target.getClass().getCreatureStats(target).getActorId());
|
||||
stats.setHitAttemptActor(playerNum);
|
||||
for (const Actor& actor : mActors)
|
||||
{
|
||||
if (actor.isInvalid())
|
||||
|
|
@ -1749,10 +1750,7 @@ namespace MWMechanics
|
|||
aiSeq.stopPursuit();
|
||||
aiSeq.stack(MWMechanics::AiCombat(target), ptr);
|
||||
// Stops guard from ending combat if player is unreachable
|
||||
actor.getPtr()
|
||||
.getClass()
|
||||
.getCreatureStats(actor.getPtr())
|
||||
.setHitAttemptActorId(target.getClass().getCreatureStats(target).getActorId());
|
||||
actor.getPtr().getClass().getCreatureStats(actor.getPtr()).setHitAttemptActor(playerNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2041,9 +2039,9 @@ namespace MWMechanics
|
|||
skill.setModifier(acrobatics->mWerewolfValue - skill.getModified());
|
||||
}
|
||||
|
||||
void MechanicsManager::cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId)
|
||||
void MechanicsManager::cleanupSummonedCreature(ESM::RefNum creature)
|
||||
{
|
||||
mActors.cleanupSummonedCreature(caster.getClass().getCreatureStats(caster), creatureActorId);
|
||||
mActors.cleanupSummonedCreature(creature);
|
||||
}
|
||||
|
||||
void MechanicsManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ namespace MWMechanics
|
|||
void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) override;
|
||||
void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) override;
|
||||
|
||||
void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) override;
|
||||
void cleanupSummonedCreature(ESM::RefNum creature) override;
|
||||
|
||||
void confiscateStolenItemToOwner(
|
||||
const MWWorld::Ptr& player, const MWWorld::Ptr& item, const MWWorld::Ptr& victim, int count) override;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/worldmodel.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
|
@ -1106,6 +1107,7 @@ namespace MWMechanics
|
|||
const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect)
|
||||
{
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
const auto worldModel = MWBase::Environment::get().getWorldModel();
|
||||
auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects();
|
||||
switch (effect.mEffectId)
|
||||
{
|
||||
|
|
@ -1192,14 +1194,14 @@ namespace MWMechanics
|
|||
case ESM::MagicEffect::SummonCreature04:
|
||||
case ESM::MagicEffect::SummonCreature05:
|
||||
{
|
||||
int actorId = effect.getActorId();
|
||||
if (actorId != -1)
|
||||
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, actorId);
|
||||
ESM::RefNum actor = effect.getActor();
|
||||
if (actor.isSet())
|
||||
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(actor);
|
||||
auto& summons = target.getClass().getCreatureStats(target).getSummonedCreatureMap();
|
||||
auto [begin, end] = summons.equal_range(effect.mEffectId);
|
||||
for (auto it = begin; it != end; ++it)
|
||||
{
|
||||
if (it->second == actorId)
|
||||
if (it->second == actor)
|
||||
{
|
||||
summons.erase(it);
|
||||
break;
|
||||
|
|
@ -1284,7 +1286,7 @@ namespace MWMechanics
|
|||
break;
|
||||
case ESM::MagicEffect::AbsorbAttribute:
|
||||
{
|
||||
const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId());
|
||||
const auto caster = worldModel->getPtr(spellParams.getCaster());
|
||||
restoreAttribute(target, effect, effect.mMagnitude);
|
||||
if (!caster.isEmpty())
|
||||
fortifyAttribute(caster, effect, -effect.mMagnitude);
|
||||
|
|
@ -1294,7 +1296,7 @@ namespace MWMechanics
|
|||
{
|
||||
if (target.getClass().isNpc())
|
||||
restoreSkill(target, effect, effect.mMagnitude);
|
||||
const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId());
|
||||
const auto caster = worldModel->getPtr(spellParams.getCaster());
|
||||
if (!caster.isEmpty() && caster.getClass().isNpc())
|
||||
fortifySkill(caster, effect, -effect.mMagnitude);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,10 +82,10 @@ namespace
|
|||
|
||||
bool isSpellActive(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const ESM::RefId& id)
|
||||
{
|
||||
int actorId = caster.getClass().getCreatureStats(caster).getActorId();
|
||||
ESM::RefNum actor = caster.getCellRef().getRefNum();
|
||||
const auto& active = target.getClass().getCreatureStats(target).getActiveSpells();
|
||||
return std::find_if(active.begin(), active.end(), [&](const auto& spell) {
|
||||
return spell.getCasterActorId() == actorId && spell.getSourceSpellId() == id;
|
||||
return spell.getCaster() == actor && spell.getSourceSpellId() == id;
|
||||
}) != active.end();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ namespace MWMechanics
|
|||
|
||||
// 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())
|
||||
if (creatureStats != &player.getClass().getCreatureStats(player))
|
||||
return;
|
||||
|
||||
// Note: if target actor has the Restore attribute effects, stats will be restored.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/manualref.hpp"
|
||||
#include "../mwworld/worldmodel.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
|
||||
|
|
@ -80,10 +81,10 @@ namespace MWMechanics
|
|||
return ESM::RefId();
|
||||
}
|
||||
|
||||
int summonCreature(int effectId, const MWWorld::Ptr& summoner)
|
||||
ESM::RefNum summonCreature(int effectId, const MWWorld::Ptr& summoner)
|
||||
{
|
||||
const ESM::RefId& creatureID = getSummonedCreature(effectId);
|
||||
int creatureActorId = -1;
|
||||
ESM::RefNum creature;
|
||||
if (!creatureID.empty())
|
||||
{
|
||||
try
|
||||
|
|
@ -91,13 +92,12 @@ namespace MWMechanics
|
|||
auto world = MWBase::Environment::get().getWorld();
|
||||
MWWorld::ManualRef ref(world->getStore(), creatureID, 1);
|
||||
MWWorld::Ptr placed = world->safePlaceObject(ref.getPtr(), summoner, summoner.getCell(), 0, 120.f);
|
||||
|
||||
MWMechanics::CreatureStats& summonedCreatureStats = placed.getClass().getCreatureStats(placed);
|
||||
MWBase::Environment::get().getWorldModel()->registerPtr(placed);
|
||||
creature = placed.getCellRef().getRefNum();
|
||||
|
||||
// Make the summoned creature follow its master and help in fights
|
||||
AiFollow package(summoner);
|
||||
summonedCreatureStats.getAiSequence().stack(package, placed);
|
||||
creatureActorId = summonedCreatureStats.getActorId();
|
||||
placed.getClass().getCreatureStats(placed).getAiSequence().stack(package, placed);
|
||||
|
||||
MWRender::Animation* anim = world->getAnimation(placed);
|
||||
if (anim)
|
||||
|
|
@ -117,9 +117,9 @@ namespace MWMechanics
|
|||
// log
|
||||
}
|
||||
|
||||
summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap().emplace(effectId, creatureActorId);
|
||||
summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap().emplace(effectId, creature);
|
||||
}
|
||||
return creatureActorId;
|
||||
return creature;
|
||||
}
|
||||
|
||||
void updateSummons(const MWWorld::Ptr& summoner, bool cleanup)
|
||||
|
|
@ -127,24 +127,18 @@ namespace MWMechanics
|
|||
MWMechanics::CreatureStats& creatureStats = summoner.getClass().getCreatureStats(summoner);
|
||||
auto& creatureMap = creatureStats.getSummonedCreatureMap();
|
||||
|
||||
std::vector<int> graveyard = creatureStats.getSummonedCreatureGraveyard();
|
||||
creatureStats.getSummonedCreatureGraveyard().clear();
|
||||
|
||||
for (const int creature : graveyard)
|
||||
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, creature);
|
||||
|
||||
if (!cleanup)
|
||||
return;
|
||||
|
||||
for (auto it = creatureMap.begin(); it != creatureMap.end();)
|
||||
{
|
||||
if (it->second == -1)
|
||||
if (!it->second.isSet())
|
||||
{
|
||||
// Keep the spell effect active if we failed to spawn anything
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second);
|
||||
MWWorld::Ptr ptr = MWBase::Environment::get().getWorldModel()->getPtr(it->second);
|
||||
if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead()
|
||||
&& ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished())
|
||||
{
|
||||
|
|
@ -158,15 +152,15 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair<int, int>& summon)
|
||||
void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair<int, ESM::RefNum>& summon)
|
||||
{
|
||||
auto& creatureStats = summoner.getClass().getCreatureStats(summoner);
|
||||
creatureStats.getActiveSpells().purge(
|
||||
[summon](const auto& spell, const auto& effect) {
|
||||
return effect.mEffectId == summon.first && effect.getActorId() == summon.second;
|
||||
return effect.mEffectId == summon.first && effect.getActor() == summon.second;
|
||||
},
|
||||
summoner);
|
||||
|
||||
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, summon.second);
|
||||
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summon.second);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include <components/esm3/refnum.hpp>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
class RefId;
|
||||
|
|
@ -18,9 +21,9 @@ namespace MWMechanics
|
|||
|
||||
ESM::RefId getSummonedCreature(int effectId);
|
||||
|
||||
void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair<int, int>& summon);
|
||||
void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair<int, ESM::RefNum>& summon);
|
||||
|
||||
int summonCreature(int effectId, const MWWorld::Ptr& summoner);
|
||||
ESM::RefNum summonCreature(int effectId, const MWWorld::Ptr& summoner);
|
||||
|
||||
void updateSummons(const MWWorld::Ptr& summoner, bool cleanup);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ namespace MWScript
|
|||
if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer())
|
||||
return;
|
||||
|
||||
MWMechanics::AiEscort escortPackage(actorID, static_cast<int>(duration), x, y, z, repeat);
|
||||
MWMechanics::AiEscort escortPackage(actorID, {}, static_cast<int>(duration), x, y, z, repeat);
|
||||
ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(escortPackage, ptr);
|
||||
|
||||
Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration;
|
||||
|
|
@ -353,7 +353,7 @@ namespace MWScript
|
|||
if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer())
|
||||
return;
|
||||
|
||||
MWMechanics::AiFollow followPackage(actorID, duration, x, y, z, repeat);
|
||||
MWMechanics::AiFollow followPackage(actorID, {}, duration, x, y, z, repeat);
|
||||
ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(followPackage, ptr);
|
||||
|
||||
Log(Debug::Info) << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/esm3/actoridconverter.hpp>
|
||||
#include <components/esm3/esmreader.hpp>
|
||||
#include <components/esm3/esmwriter.hpp>
|
||||
#include <components/esm3/loadcell.hpp>
|
||||
|
|
@ -65,7 +66,6 @@ void MWState::StateManager::cleanup(bool force)
|
|||
mCharacterManager.setCurrentCharacter(nullptr);
|
||||
mTimePlayed = 0;
|
||||
mLastSavegame.clear();
|
||||
MWMechanics::CreatureStats::cleanup();
|
||||
|
||||
mState = State_NoGame;
|
||||
MWBase::Environment::get().getLuaManager()->noGame();
|
||||
|
|
@ -469,6 +469,10 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
|
|||
reader.setContentFileMapping(&contentFileMap);
|
||||
MWBase::Environment::get().getLuaManager()->setContentFileMapping(contentFileMap);
|
||||
|
||||
ESM::ActorIdConverter actorIdConverter;
|
||||
if (version <= ESM::MaxActorIdSaveGameFormatVersion)
|
||||
reader.setActorIdConverter(&actorIdConverter);
|
||||
|
||||
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
||||
|
||||
listener.setProgressRange(100);
|
||||
|
|
@ -589,7 +593,6 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
|
|||
currentPercent = progressPercent;
|
||||
}
|
||||
}
|
||||
|
||||
mCharacterManager.setCurrentCharacter(character);
|
||||
|
||||
mState = State_Running;
|
||||
|
|
@ -599,7 +602,8 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
|
|||
mLastSavegame = filepath;
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->setNewGame(false);
|
||||
MWBase::Environment::get().getWorld()->saveLoaded();
|
||||
MWBase::Environment::get().getWorld()->saveLoaded(reader);
|
||||
actorIdConverter.apply();
|
||||
MWBase::Environment::get().getWorld()->setupPlayer();
|
||||
MWBase::Environment::get().getWorld()->renderPlayer();
|
||||
MWBase::Environment::get().getWindowManager()->updatePlayer();
|
||||
|
|
@ -646,6 +650,12 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
|
|||
MWBase::Environment::get().getWorldScene()->markCellAsUnchanged();
|
||||
|
||||
MWBase::Environment::get().getLuaManager()->gameLoaded();
|
||||
for (int actorId : actorIdConverter.mGraveyard)
|
||||
{
|
||||
auto mapped = actorIdConverter.mMappings.find(actorId);
|
||||
if (mapped != actorIdConverter.mMappings.end())
|
||||
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mapped->second);
|
||||
}
|
||||
}
|
||||
catch (const SaveVersionTooNewError& e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/esm/format.hpp>
|
||||
#include <components/esm3/actoridconverter.hpp>
|
||||
#include <components/esm3/cellref.hpp>
|
||||
#include <components/esm3/cellstate.hpp>
|
||||
#include <components/esm3/containerstate.hpp>
|
||||
|
|
@ -141,25 +142,6 @@ namespace
|
|||
return MWWorld::Ptr();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
MWWorld::Ptr searchViaActorId(MWWorld::CellRefList<T>& actorList, int actorId, MWWorld::CellStore* cell,
|
||||
const std::map<MWWorld::LiveCellRefBase*, MWWorld::CellStore*>& toIgnore)
|
||||
{
|
||||
for (typename MWWorld::CellRefList<T>::List::iterator iter(actorList.mList.begin());
|
||||
iter != actorList.mList.end(); ++iter)
|
||||
{
|
||||
MWWorld::Ptr actor(&*iter, cell);
|
||||
|
||||
if (toIgnore.find(&*iter) != toIgnore.end())
|
||||
continue;
|
||||
|
||||
if (actor.getClass().getCreatureStats(actor).matchesActorId(actorId) && actor.getCellRef().getCount() > 0)
|
||||
return actor;
|
||||
}
|
||||
|
||||
return MWWorld::Ptr();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void writeReferenceCollection(ESM::ESMWriter& writer, const MWWorld::CellRefList<T>& collection)
|
||||
{
|
||||
|
|
@ -275,6 +257,14 @@ namespace
|
|||
if constexpr (std::is_same_v<T, ESM::Creature> || std::is_same_v<T, ESM::NPC>)
|
||||
MWWorld::convertEnchantmentSlots(state.mCreatureStats, state.mInventory);
|
||||
}
|
||||
if constexpr (std::is_same_v<T, ESM::Creature> || std::is_same_v<T, ESM::NPC>)
|
||||
{
|
||||
if (reader.getActorIdConverter() && state.mHasCustomState)
|
||||
{
|
||||
MWBase::Environment::get().getWorldModel()->assignSaveFileRefNum(state.mRef);
|
||||
reader.getActorIdConverter()->mMappings.emplace(state.mCreatureStats.mActorId, state.mRef.mRefNum);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.mRef.mRefNum.hasContentFile())
|
||||
{
|
||||
|
|
@ -685,26 +675,6 @@ namespace MWWorld
|
|||
return searchVisitor.mFound;
|
||||
}
|
||||
|
||||
Ptr CellStore::searchViaActorId(int id)
|
||||
{
|
||||
if (Ptr ptr = ::searchViaActorId(get<ESM::NPC>(), id, this, mMovedToAnotherCell); !ptr.isEmpty())
|
||||
return ptr;
|
||||
|
||||
if (Ptr ptr = ::searchViaActorId(get<ESM::Creature>(), id, this, mMovedToAnotherCell); !ptr.isEmpty())
|
||||
return ptr;
|
||||
|
||||
for (const auto& [base, _] : mMovedHere)
|
||||
{
|
||||
MWWorld::Ptr actor(base, this);
|
||||
if (!actor.getClass().isActor())
|
||||
continue;
|
||||
if (actor.getClass().getCreatureStats(actor).matchesActorId(id) && actor.getCellRef().getCount() > 0)
|
||||
return actor;
|
||||
}
|
||||
|
||||
return Ptr();
|
||||
}
|
||||
|
||||
class RefNumSearchVisitor
|
||||
{
|
||||
ESM::RefNum mRefNum;
|
||||
|
|
@ -1362,20 +1332,6 @@ namespace MWWorld
|
|||
ptr.getBase(), static_cast<float>(MWMechanics::getEnchantmentCharge(*enchantment)));
|
||||
}
|
||||
|
||||
Ptr MWWorld::CellStore::getMovedActor(int actorId) const
|
||||
{
|
||||
for (const auto& [cellRef, cell] : mMovedToAnotherCell)
|
||||
{
|
||||
if (cellRef->mClass->isActor() && cellRef->mData.getCustomData())
|
||||
{
|
||||
Ptr actor(cellRef, cell);
|
||||
if (actor.getClass().getCreatureStats(actor).getActorId() == actorId)
|
||||
return actor;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
CellStore* MWWorld::CellStore::getOriginCell(const Ptr& object) const
|
||||
{
|
||||
MovedRefTracker::const_iterator found = mMovedHere.find(object.getBase());
|
||||
|
|
|
|||
|
|
@ -183,9 +183,6 @@ namespace MWWorld
|
|||
/// containers.
|
||||
/// @note Does not trigger CellStore hasState flag.
|
||||
|
||||
Ptr searchViaActorId(int id);
|
||||
///< Will return an empty Ptr if cell is not loaded.
|
||||
|
||||
float getWaterLevel() const;
|
||||
|
||||
bool movedHere(const MWWorld::Ptr& ptr) const;
|
||||
|
|
@ -336,8 +333,6 @@ namespace MWWorld
|
|||
void respawn();
|
||||
///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded.
|
||||
|
||||
Ptr getMovedActor(int actorId) const;
|
||||
|
||||
CellStore* getOriginCell(const Ptr& object) const;
|
||||
|
||||
Ptr getPtr(ESM::RefId id);
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ namespace MWWorld
|
|||
ESM::ActiveSpells::ActiveSpellParams params;
|
||||
params.mSourceSpellId = id;
|
||||
params.mDisplayName = spell->mName;
|
||||
params.mCasterActorId = creatureStats.mActorId;
|
||||
params.mCaster.mIndex = creatureStats.mActorId;
|
||||
if (spell->mData.mType == ESM::Spell::ST_Ability)
|
||||
params.mFlags = ESM::Compatibility::ActiveSpells::Type_Ability_Flags;
|
||||
else
|
||||
|
|
@ -137,7 +137,7 @@ namespace MWWorld
|
|||
ESM::ActiveSpells::ActiveSpellParams params;
|
||||
params.mSourceSpellId = id;
|
||||
params.mDisplayName = std::move(name);
|
||||
params.mCasterActorId = creatureStats.mActorId;
|
||||
params.mCaster.mIndex = creatureStats.mActorId;
|
||||
params.mFlags = ESM::Compatibility::ActiveSpells::Type_Enchantment_Flags;
|
||||
params.mWorsenings = -1;
|
||||
params.mNextWorsening = ESM::TimeStamp();
|
||||
|
|
@ -196,7 +196,7 @@ namespace MWWorld
|
|||
{
|
||||
if (effect.mEffectId == key.mEffectId && effect.mEffectIndex == key.mEffectIndex)
|
||||
{
|
||||
effect.mArg = actorId;
|
||||
effect.mArg = ESM::RefNum{ .mIndex = static_cast<uint32_t>(actorId), .mContentFile = -1 };
|
||||
effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
|
||||
found = true;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/esm/defs.hpp>
|
||||
#include <components/esm3/actoridconverter.hpp>
|
||||
#include <components/esm3/esmreader.hpp>
|
||||
#include <components/esm3/esmwriter.hpp>
|
||||
#include <components/esm3/loadbsgn.hpp>
|
||||
|
|
@ -343,6 +344,9 @@ namespace MWWorld
|
|||
|
||||
MWBase::Environment::get().getWorldModel()->deregisterLiveCellRef(mPlayer);
|
||||
mPlayer.load(player.mObject);
|
||||
MWBase::Environment::get().getWorldModel()->registerPtr(getPlayer());
|
||||
if (ESM::ActorIdConverter* converter = reader.getActorIdConverter())
|
||||
converter->mMappings.emplace(player.mObject.mCreatureStats.mActorId, mPlayer.mRef.getRefNum());
|
||||
|
||||
for (size_t i = 0; i < mSaveAttributes.size(); ++i)
|
||||
mSaveAttributes[i] = player.mSaveAttributes[i];
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/esm3/actoridconverter.hpp>
|
||||
#include <components/esm3/esmreader.hpp>
|
||||
#include <components/esm3/esmwriter.hpp>
|
||||
#include <components/esm3/loadench.hpp>
|
||||
#include <components/esm3/loadmgef.hpp>
|
||||
|
|
@ -36,6 +38,7 @@
|
|||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/manualref.hpp"
|
||||
#include "../mwworld/worldmodel.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
|
|
@ -289,10 +292,8 @@ namespace MWWorld
|
|||
state.mSpellId = spellId;
|
||||
state.mCasterHandle = caster;
|
||||
state.mItem = item;
|
||||
if (caster.getClass().isActor())
|
||||
state.mActorId = caster.getClass().getCreatureStats(caster).getActorId();
|
||||
else
|
||||
state.mActorId = -1;
|
||||
MWBase::Environment::get().getWorldModel()->registerPtr(caster);
|
||||
state.mCaster = caster.getCellRef().getRefNum();
|
||||
|
||||
std::string texture;
|
||||
|
||||
|
|
@ -342,7 +343,7 @@ namespace MWWorld
|
|||
const osg::Quat& orient, const Ptr& bow, float speed, float attackStrength)
|
||||
{
|
||||
ProjectileState state;
|
||||
state.mActorId = actor.getClass().getCreatureStats(actor).getActorId();
|
||||
state.mCaster = actor.getCellRef().getRefNum();
|
||||
state.mBowId = bow.getCellRef().getRefId();
|
||||
state.mVelocity = orient * osg::Vec3f(0, 1, 0) * speed;
|
||||
state.mIdArrow = projectile.getCellRef().getRefId();
|
||||
|
|
@ -367,24 +368,24 @@ namespace MWWorld
|
|||
void ProjectileManager::updateCasters()
|
||||
{
|
||||
for (auto& state : mProjectiles)
|
||||
mPhysics->setCaster(state.mProjectileId, state.getCaster());
|
||||
{
|
||||
state.mCasterHandle = state.getCaster();
|
||||
mPhysics->setCaster(state.mProjectileId, state.mCasterHandle);
|
||||
}
|
||||
|
||||
for (auto& state : mMagicBolts)
|
||||
{
|
||||
// casters are identified by actor id in the savegame. objects doesn't have one so they can't be identified
|
||||
// back.
|
||||
// TODO: should object-type caster be restored from savegame?
|
||||
if (state.mActorId == -1)
|
||||
if (!state.mCaster.isSet())
|
||||
continue;
|
||||
|
||||
auto caster = state.getCaster();
|
||||
if (caster.isEmpty())
|
||||
state.mCasterHandle = state.getCaster();
|
||||
if (state.mCasterHandle.isEmpty())
|
||||
{
|
||||
Log(Debug::Error) << "Couldn't find caster with ID " << state.mActorId;
|
||||
Log(Debug::Error) << "Couldn't find caster with ID " << state.mCaster;
|
||||
cleanupMagicBolt(state);
|
||||
continue;
|
||||
}
|
||||
mPhysics->setCaster(state.mProjectileId, caster);
|
||||
mPhysics->setCaster(state.mProjectileId, state.mCasterHandle);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -650,37 +651,37 @@ namespace MWWorld
|
|||
|
||||
void ProjectileManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) const
|
||||
{
|
||||
for (std::vector<ProjectileState>::const_iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it)
|
||||
for (const ProjectileState& projectile : mProjectiles)
|
||||
{
|
||||
writer.startRecord(ESM::REC_PROJ);
|
||||
|
||||
ESM::ProjectileState state;
|
||||
state.mId = it->mIdArrow;
|
||||
state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition()));
|
||||
state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude()));
|
||||
state.mActorId = it->mActorId;
|
||||
state.mId = projectile.mIdArrow;
|
||||
state.mPosition = ESM::Vector3(osg::Vec3f(projectile.mNode->getPosition()));
|
||||
state.mOrientation = ESM::Quaternion(osg::Quat(projectile.mNode->getAttitude()));
|
||||
state.mCaster = projectile.mCaster;
|
||||
|
||||
state.mBowId = it->mBowId;
|
||||
state.mVelocity = it->mVelocity;
|
||||
state.mAttackStrength = it->mAttackStrength;
|
||||
state.mBowId = projectile.mBowId;
|
||||
state.mVelocity = projectile.mVelocity;
|
||||
state.mAttackStrength = projectile.mAttackStrength;
|
||||
|
||||
state.save(writer);
|
||||
|
||||
writer.endRecord(ESM::REC_PROJ);
|
||||
}
|
||||
|
||||
for (std::vector<MagicBoltState>::const_iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it)
|
||||
for (const MagicBoltState& bolt : mMagicBolts)
|
||||
{
|
||||
writer.startRecord(ESM::REC_MPRJ);
|
||||
|
||||
ESM::MagicBoltState state;
|
||||
state.mId = it->mIdMagic.at(0);
|
||||
state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition()));
|
||||
state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude()));
|
||||
state.mActorId = it->mActorId;
|
||||
state.mItem = it->mItem;
|
||||
state.mSpellId = it->mSpellId;
|
||||
state.mSpeed = it->mSpeed;
|
||||
state.mId = bolt.mIdMagic.at(0);
|
||||
state.mPosition = ESM::Vector3(osg::Vec3f(bolt.mNode->getPosition()));
|
||||
state.mOrientation = ESM::Quaternion(osg::Quat(bolt.mNode->getAttitude()));
|
||||
state.mCaster = bolt.mCaster;
|
||||
state.mItem = bolt.mItem;
|
||||
state.mSpellId = bolt.mSpellId;
|
||||
state.mSpeed = bolt.mSpeed;
|
||||
|
||||
state.save(writer);
|
||||
|
||||
|
|
@ -696,7 +697,7 @@ namespace MWWorld
|
|||
esm.load(reader);
|
||||
|
||||
ProjectileState state;
|
||||
state.mActorId = esm.mActorId;
|
||||
state.mCaster = esm.mCaster;
|
||||
state.mBowId = esm.mBowId;
|
||||
state.mVelocity = esm.mVelocity;
|
||||
state.mIdArrow = esm.mId;
|
||||
|
|
@ -736,7 +737,7 @@ namespace MWWorld
|
|||
MagicBoltState state;
|
||||
state.mIdMagic.push_back(esm.mId);
|
||||
state.mSpellId = esm.mSpellId;
|
||||
state.mActorId = esm.mActorId;
|
||||
state.mCaster = esm.mCaster;
|
||||
state.mToDelete = false;
|
||||
state.mItem = esm.mItem;
|
||||
std::string texture;
|
||||
|
|
@ -798,12 +799,24 @@ namespace MWWorld
|
|||
return mMagicBolts.size() + mProjectiles.size();
|
||||
}
|
||||
|
||||
void ProjectileManager::saveLoaded(const ESM::ESMReader& reader)
|
||||
{
|
||||
// Can't do this in readRecord because the vectors might get reallocated as they grow
|
||||
if (reader.getActorIdConverter())
|
||||
{
|
||||
for (ProjectileState& projectile : mProjectiles)
|
||||
reader.getActorIdConverter()->convert(projectile.mCaster, projectile.mCaster.mIndex);
|
||||
for (MagicBoltState& bolt : mMagicBolts)
|
||||
reader.getActorIdConverter()->convert(bolt.mCaster, bolt.mCaster.mIndex);
|
||||
}
|
||||
}
|
||||
|
||||
MWWorld::Ptr ProjectileManager::State::getCaster()
|
||||
{
|
||||
if (!mCasterHandle.isEmpty())
|
||||
return mCasterHandle;
|
||||
|
||||
return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId);
|
||||
return MWBase::Environment::get().getWorldModel()->getPtr(mCaster);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ namespace MWWorld
|
|||
void write(ESM::ESMWriter& writer, Loading::Listener& progress) const;
|
||||
bool readRecord(ESM::ESMReader& reader, uint32_t type);
|
||||
size_t countSavedGameRecords() const;
|
||||
void saveLoaded(const ESM::ESMReader& reader);
|
||||
|
||||
private:
|
||||
osg::ref_ptr<osg::Group> mParent;
|
||||
|
|
@ -81,11 +82,7 @@ namespace MWWorld
|
|||
osg::ref_ptr<osg::PositionAttitudeTransform> mNode;
|
||||
std::shared_ptr<MWRender::EffectAnimationTime> mEffectAnimationTime;
|
||||
|
||||
int mActorId;
|
||||
int mProjectileId;
|
||||
|
||||
// TODO: this will break when the game is saved and reloaded, since there is currently
|
||||
// no way to write identifiers for non-actors to a savegame.
|
||||
ESM::RefNum mCaster;
|
||||
MWWorld::Ptr mCasterHandle;
|
||||
|
||||
MWWorld::Ptr getCaster();
|
||||
|
|
@ -96,6 +93,7 @@ namespace MWWorld
|
|||
// MW-id of an arrow projectile
|
||||
ESM::RefId mIdArrow;
|
||||
|
||||
int mProjectileId;
|
||||
bool mToDelete;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1083,17 +1083,6 @@ namespace MWWorld
|
|||
return mActiveCells.contains(&cell);
|
||||
}
|
||||
|
||||
Ptr Scene::searchPtrViaActorId(int actorId)
|
||||
{
|
||||
for (CellStoreCollection::const_iterator iter(mActiveCells.begin()); iter != mActiveCells.end(); ++iter)
|
||||
{
|
||||
Ptr ptr = (*iter)->searchViaActorId(actorId);
|
||||
if (!ptr.isEmpty())
|
||||
return ptr;
|
||||
}
|
||||
return Ptr();
|
||||
}
|
||||
|
||||
class PreloadMeshItem : public SceneUtil::WorkItem
|
||||
{
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -197,8 +197,6 @@ namespace MWWorld
|
|||
|
||||
bool isCellActive(const CellStore& cell);
|
||||
|
||||
Ptr searchPtrViaActorId(int actorId);
|
||||
|
||||
void preload(const std::string& mesh, bool useAnim = false);
|
||||
|
||||
void testExteriorCells();
|
||||
|
|
|
|||
|
|
@ -449,7 +449,6 @@ namespace MWWorld
|
|||
+ mGlobalVariables.countSavedGameRecords() + mProjectileManager->countSavedGameRecords()
|
||||
+ 1 // player record
|
||||
+ 1 // weather record
|
||||
+ 1 // actorId counter
|
||||
+ 1 // levitation/teleport enabled state
|
||||
+ 1 // camera
|
||||
+ 1; // random state.
|
||||
|
|
@ -472,8 +471,6 @@ namespace MWWorld
|
|||
MWBase::Environment::get().getWindowManager()->writeFog(cellstore);
|
||||
}
|
||||
|
||||
MWMechanics::CreatureStats::writeActorIdCounter(writer);
|
||||
|
||||
mStore.write(writer, progress); // dynamic Store must be written (and read) before Cells, so that
|
||||
// references to custom made records will be recognized
|
||||
mWorldModel.write(writer, progress); // the player's cell needs to be loaded before the player
|
||||
|
|
@ -497,7 +494,7 @@ namespace MWWorld
|
|||
switch (type)
|
||||
{
|
||||
case ESM::REC_ACTC:
|
||||
MWMechanics::CreatureStats::readActorIdCounter(reader);
|
||||
reader.skipRecord();
|
||||
return;
|
||||
case ESM::REC_ENAB:
|
||||
reader.getHNT(mTeleportEnabled, "TELE");
|
||||
|
|
@ -750,15 +747,6 @@ namespace MWWorld
|
|||
throw std::runtime_error(error);
|
||||
}
|
||||
|
||||
Ptr World::searchPtrViaActorId(int actorId)
|
||||
{
|
||||
// The player is not registered in any CellStore so must be checked manually
|
||||
if (actorId == getPlayerPtr().getClass().getCreatureStats(getPlayerPtr()).getActorId())
|
||||
return getPlayerPtr();
|
||||
// Now search cells
|
||||
return mWorldScene->searchPtrViaActorId(actorId);
|
||||
}
|
||||
|
||||
struct FindContainerVisitor
|
||||
{
|
||||
ConstPtr mContainedPtr;
|
||||
|
|
@ -2307,11 +2295,12 @@ namespace MWWorld
|
|||
return true;
|
||||
}
|
||||
|
||||
void World::saveLoaded()
|
||||
void World::saveLoaded(const ESM::ESMReader& reader)
|
||||
{
|
||||
mStore.rebuildIdsIndex();
|
||||
mStore.validateDynamic();
|
||||
mTimeManager->setup(mGlobalVariables);
|
||||
mProjectileManager->saveLoaded(reader);
|
||||
}
|
||||
|
||||
void World::setupPlayer()
|
||||
|
|
|
|||
|
|
@ -288,9 +288,6 @@ namespace MWWorld
|
|||
///< Return a pointer to a liveCellRef with the given name.
|
||||
/// \param activeOnly do not search inactive cells.
|
||||
|
||||
Ptr searchPtrViaActorId(int actorId) override;
|
||||
///< Search is limited to the active cells.
|
||||
|
||||
MWWorld::Ptr findContainer(const MWWorld::ConstPtr& ptr) override;
|
||||
///< Return a pointer to a liveCellRef which contains \a ptr.
|
||||
/// \note Search is limited to the active cells.
|
||||
|
|
@ -467,7 +464,7 @@ namespace MWWorld
|
|||
void applyDeferredPreviewRotationToPlayer(float dt) override;
|
||||
void disableDeferredPreviewRotation() override;
|
||||
|
||||
void saveLoaded() override;
|
||||
void saveLoaded(const ESM::ESMReader& reader) override;
|
||||
|
||||
void setupPlayer() override;
|
||||
void renderPlayer() override;
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ add_component_dir (esm3
|
|||
weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile
|
||||
aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache
|
||||
infoorder timestamp formatversion landrecorddata selectiongroup dialoguecondition
|
||||
refnum
|
||||
refnum actoridconverter
|
||||
)
|
||||
|
||||
add_component_dir (esmterrain
|
||||
|
|
|
|||
|
|
@ -70,25 +70,6 @@ namespace ESM
|
|||
return false;
|
||||
}
|
||||
|
||||
struct ToInt
|
||||
{
|
||||
int effectId;
|
||||
|
||||
int operator()(const ESM::RefId& id) const
|
||||
{
|
||||
if (!id.empty())
|
||||
{
|
||||
if (affectsAttribute(effectId))
|
||||
return ESM::Attribute::refIdToIndex(id);
|
||||
else if (affectsSkill(effectId))
|
||||
return ESM::Skill::refIdToIndex(id);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int operator()(int actor) const { return actor; }
|
||||
};
|
||||
|
||||
void saveImpl(ESMWriter& esm, const std::vector<ActiveSpells::ActiveSpellParams>& spells, NAME tag)
|
||||
{
|
||||
for (const auto& params : spells)
|
||||
|
|
@ -96,7 +77,7 @@ namespace ESM
|
|||
esm.writeHNRefId(tag, params.mSourceSpellId);
|
||||
esm.writeHNRefId("SPID", params.mActiveSpellId);
|
||||
|
||||
esm.writeHNT("CAST", params.mCasterActorId);
|
||||
esm.writeFormId(params.mCaster, true, "CAST");
|
||||
esm.writeHNString("DISP", params.mDisplayName);
|
||||
esm.writeHNT("FLAG", params.mFlags);
|
||||
if (params.mItem.isSet())
|
||||
|
|
@ -110,9 +91,16 @@ namespace ESM
|
|||
for (auto& effect : params.mEffects)
|
||||
{
|
||||
esm.writeHNT("MGEF", effect.mEffectId);
|
||||
int arg = std::visit(ToInt{ effect.mEffectId }, effect.mArg);
|
||||
if (arg != -1)
|
||||
esm.writeHNT("ARG_", arg);
|
||||
if (const ESM::RefId* id = std::get_if<ESM::RefId>(&effect.mArg))
|
||||
{
|
||||
if (!id->empty())
|
||||
esm.writeHNRefId("ARG_", *id);
|
||||
}
|
||||
else if (const ESM::RefNum* actor = std::get_if<ESM::RefNum>(&effect.mArg))
|
||||
{
|
||||
if (actor->isSet())
|
||||
esm.writeFormId(*actor, true, "SUM_");
|
||||
}
|
||||
esm.writeHNT("MAGN", effect.mMagnitude);
|
||||
esm.writeHNT("MAGN", effect.mMinMagnitude);
|
||||
esm.writeHNT("MAGN", effect.mMaxMagnitude);
|
||||
|
|
@ -134,7 +122,10 @@ namespace ESM
|
|||
params.mSourceSpellId = esm.getRefId();
|
||||
if (format > MaxActiveSpellTypeVersion)
|
||||
params.mActiveSpellId = esm.getHNRefId("SPID");
|
||||
esm.getHNT(params.mCasterActorId, "CAST");
|
||||
if (format <= MaxActorIdSaveGameFormatVersion)
|
||||
esm.getHNT(params.mCaster.mIndex, "CAST");
|
||||
else
|
||||
params.mCaster = esm.getFormId(true, "CAST");
|
||||
params.mDisplayName = esm.getHNString("DISP");
|
||||
if (format <= MaxClearModifiersFormatVersion)
|
||||
params.mFlags = Compatibility::ActiveSpells::Type_Temporary_Flags;
|
||||
|
|
@ -186,17 +177,24 @@ namespace ESM
|
|||
{
|
||||
ActiveEffect effect;
|
||||
esm.getHT(effect.mEffectId);
|
||||
int32_t arg = -1;
|
||||
esm.getHNOT(arg, "ARG_");
|
||||
if (arg >= 0)
|
||||
if (format <= MaxActorIdSaveGameFormatVersion)
|
||||
{
|
||||
if (isSummon(effect.mEffectId))
|
||||
effect.mArg = arg;
|
||||
else if (affectsAttribute(effect.mEffectId))
|
||||
effect.mArg = ESM::Attribute::indexToRefId(arg);
|
||||
else if (affectsSkill(effect.mEffectId))
|
||||
effect.mArg = ESM::Skill::indexToRefId(arg);
|
||||
int32_t arg = -1;
|
||||
esm.getHNOT(arg, "ARG_");
|
||||
if (arg >= 0)
|
||||
{
|
||||
if (isSummon(effect.mEffectId))
|
||||
effect.mArg = RefNum{ .mIndex = static_cast<uint32_t>(arg), .mContentFile = -1 };
|
||||
else if (affectsAttribute(effect.mEffectId))
|
||||
effect.mArg = ESM::Attribute::indexToRefId(arg);
|
||||
else if (affectsSkill(effect.mEffectId))
|
||||
effect.mArg = ESM::Skill::indexToRefId(arg);
|
||||
}
|
||||
}
|
||||
else if (esm.peekNextSub("ARG_"))
|
||||
effect.mArg = esm.getHNRefId("ARG_");
|
||||
else if (esm.peekNextSub("SUM_"))
|
||||
effect.mArg = esm.getFormId(true, "SUM_");
|
||||
esm.getHNT(effect.mMagnitude, "MAGN");
|
||||
if (format <= MaxClearModifiersFormatVersion)
|
||||
{
|
||||
|
|
@ -235,21 +233,22 @@ namespace ESM
|
|||
|
||||
void ActiveSpells::load(ESMReader& esm)
|
||||
{
|
||||
mActorIdConverter = esm.getActorIdConverter();
|
||||
loadImpl(esm, mSpells, "ID__");
|
||||
loadImpl(esm, mQueue, "QID_");
|
||||
}
|
||||
|
||||
RefId ActiveEffect::getSkillOrAttribute() const
|
||||
{
|
||||
if (const auto* id = std::get_if<ESM::RefId>(&mArg))
|
||||
if (const ESM::RefId* id = std::get_if<ESM::RefId>(&mArg))
|
||||
return *id;
|
||||
return {};
|
||||
}
|
||||
|
||||
int ActiveEffect::getActorId() const
|
||||
RefNum ActiveEffect::getActor() const
|
||||
{
|
||||
if (const auto* id = std::get_if<int>(&mArg))
|
||||
return *id;
|
||||
return -1;
|
||||
if (const ESM::RefNum* actor = std::get_if<ESM::RefNum>(&mArg))
|
||||
return *actor;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace ESM
|
|||
{
|
||||
class ESMReader;
|
||||
class ESMWriter;
|
||||
class ActorIdConverter;
|
||||
|
||||
// Parameters of an effect concerning lasting effects.
|
||||
// Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc.
|
||||
|
|
@ -34,14 +35,14 @@ namespace ESM
|
|||
float mMagnitude;
|
||||
float mMinMagnitude;
|
||||
float mMaxMagnitude;
|
||||
std::variant<RefId, int> mArg; // skill, attribute, or summon
|
||||
std::variant<RefId, RefNum> mArg; // skill, attribute, or summon
|
||||
float mDuration;
|
||||
float mTimeLeft;
|
||||
int32_t mEffectIndex;
|
||||
int32_t mFlags;
|
||||
|
||||
RefId getSkillOrAttribute() const;
|
||||
int getActorId() const;
|
||||
RefNum getActor() const;
|
||||
};
|
||||
|
||||
// format 0, saved games only
|
||||
|
|
@ -65,7 +66,7 @@ namespace ESM
|
|||
RefId mSourceSpellId;
|
||||
std::vector<ActiveEffect> mEffects;
|
||||
std::string mDisplayName;
|
||||
int32_t mCasterActorId;
|
||||
RefNum mCaster;
|
||||
RefNum mItem;
|
||||
Flags mFlags;
|
||||
int32_t mWorsenings;
|
||||
|
|
@ -74,6 +75,7 @@ namespace ESM
|
|||
|
||||
std::vector<ActiveSpellParams> mSpells;
|
||||
std::vector<ActiveSpellParams> mQueue;
|
||||
ActorIdConverter* mActorIdConverter = nullptr;
|
||||
|
||||
void load(ESMReader& esm);
|
||||
void save(ESMWriter& esm) const;
|
||||
|
|
|
|||
32
components/esm3/actoridconverter.cpp
Normal file
32
components/esm3/actoridconverter.cpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#include "actoridconverter.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
void ActorIdConverter::apply()
|
||||
{
|
||||
for (auto& [refNum, actorId] : mToConvert)
|
||||
{
|
||||
auto it = mMappings.find(actorId);
|
||||
if (it == mMappings.end())
|
||||
refNum = {};
|
||||
else
|
||||
refNum = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
void ActorIdConverter::convert(ESM::RefNum& refNum, int actorId)
|
||||
{
|
||||
if (actorId == -1)
|
||||
{
|
||||
refNum = {};
|
||||
return;
|
||||
}
|
||||
auto it = mMappings.find(actorId);
|
||||
if (it == mMappings.end())
|
||||
{
|
||||
mToConvert.emplace_back(refNum, actorId);
|
||||
return;
|
||||
}
|
||||
refNum = it->second;
|
||||
}
|
||||
}
|
||||
25
components/esm3/actoridconverter.hpp
Normal file
25
components/esm3/actoridconverter.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef OPENMW_COMPONENTS_ESM3_ACTORIDCONVERTER_H
|
||||
#define OPENMW_COMPONENTS_ESM3_ACTORIDCONVERTER_H
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "refnum.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
class ActorIdConverter
|
||||
{
|
||||
std::vector<std::pair<ESM::RefNum&, int>> mToConvert;
|
||||
|
||||
public:
|
||||
std::map<int, ESM::RefNum> mMappings;
|
||||
std::vector<int> mGraveyard;
|
||||
|
||||
void apply();
|
||||
|
||||
void convert(ESM::RefNum& refNum, int actorId);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -35,6 +35,22 @@ namespace ESM
|
|||
f(v.mX, v.mY, v.mZ, v.mDuration);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
void loadActorId(ESMReader& esm, ESM::NAME name, RefNum& refNum)
|
||||
{
|
||||
if (esm.getFormatVersion() <= MaxActorIdSaveGameFormatVersion)
|
||||
{
|
||||
refNum.mIndex = static_cast<uint32_t>(-1);
|
||||
esm.getHNOT(refNum.mIndex, name);
|
||||
}
|
||||
else if (esm.peekNextSub(name))
|
||||
refNum = esm.getFormId(true, name);
|
||||
else
|
||||
refNum = {};
|
||||
}
|
||||
}
|
||||
|
||||
namespace AiSequence
|
||||
{
|
||||
void AiWander::load(ESMReader& esm)
|
||||
|
|
@ -72,8 +88,7 @@ namespace ESM
|
|||
{
|
||||
esm.getNamedComposite("DATA", mData);
|
||||
mTargetId = esm.getHNRefId("TARG");
|
||||
mTargetActorId = -1;
|
||||
esm.getHNOT(mTargetActorId, "TAID");
|
||||
loadActorId(esm, "TAID", mTargetActor);
|
||||
esm.getHNT(mRemainingDuration, "DURA");
|
||||
mCellId = esm.getHNOString("CELL");
|
||||
mRepeat = false;
|
||||
|
|
@ -92,7 +107,8 @@ namespace ESM
|
|||
{
|
||||
esm.writeNamedComposite("DATA", mData);
|
||||
esm.writeHNRefId("TARG", mTargetId);
|
||||
esm.writeHNT("TAID", mTargetActorId);
|
||||
if (esm.getFormatVersion() > MaxActorIdSaveGameFormatVersion)
|
||||
esm.writeFormId(mTargetActor, true, "TAID");
|
||||
esm.writeHNT("DURA", mRemainingDuration);
|
||||
if (!mCellId.empty())
|
||||
esm.writeHNString("CELL", mCellId);
|
||||
|
|
@ -104,8 +120,7 @@ namespace ESM
|
|||
{
|
||||
esm.getNamedComposite("DATA", mData);
|
||||
mTargetId = esm.getHNRefId("TARG");
|
||||
mTargetActorId = -1;
|
||||
esm.getHNOT(mTargetActorId, "TAID");
|
||||
loadActorId(esm, "TAID", mTargetActor);
|
||||
esm.getHNT(mRemainingDuration, "DURA");
|
||||
mCellId = esm.getHNOString("CELL");
|
||||
esm.getHNT(mAlwaysFollow, "ALWY");
|
||||
|
|
@ -129,7 +144,8 @@ namespace ESM
|
|||
{
|
||||
esm.writeNamedComposite("DATA", mData);
|
||||
esm.writeHNRefId("TARG", mTargetId);
|
||||
esm.writeHNT("TAID", mTargetActorId);
|
||||
if (esm.getFormatVersion() > MaxActorIdSaveGameFormatVersion)
|
||||
esm.writeFormId(mTargetActor, true, "TAID");
|
||||
esm.writeHNT("DURA", mRemainingDuration);
|
||||
if (!mCellId.empty())
|
||||
esm.writeHNString("CELL", mCellId);
|
||||
|
|
@ -157,22 +173,22 @@ namespace ESM
|
|||
|
||||
void AiCombat::load(ESMReader& esm)
|
||||
{
|
||||
esm.getHNT(mTargetActorId, "TARG");
|
||||
loadActorId(esm, "TARG", mTargetActor);
|
||||
}
|
||||
|
||||
void AiCombat::save(ESMWriter& esm) const
|
||||
{
|
||||
esm.writeHNT("TARG", mTargetActorId);
|
||||
esm.writeFormId(mTargetActor, true, "TARG");
|
||||
}
|
||||
|
||||
void AiPursue::load(ESMReader& esm)
|
||||
{
|
||||
esm.getHNT(mTargetActorId, "TARG");
|
||||
loadActorId(esm, "TARG", mTargetActor);
|
||||
}
|
||||
|
||||
void AiPursue::save(ESMWriter& esm) const
|
||||
{
|
||||
esm.writeHNT("TARG", mTargetActorId);
|
||||
esm.writeFormId(mTargetActor, true, "TARG");
|
||||
}
|
||||
|
||||
void AiSequence::save(ESMWriter& esm) const
|
||||
|
|
@ -214,6 +230,7 @@ namespace ESM
|
|||
|
||||
void AiSequence::load(ESMReader& esm)
|
||||
{
|
||||
mActorIdConverter = esm.getActorIdConverter();
|
||||
int count = 0;
|
||||
while (esm.isNextSub("AIPK"))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,8 +9,11 @@
|
|||
#include <components/esm/refid.hpp>
|
||||
#include <components/esm/vector3.hpp>
|
||||
|
||||
#include "refnum.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
class ActorIdConverter;
|
||||
class ESMReader;
|
||||
class ESMWriter;
|
||||
|
||||
|
|
@ -33,7 +36,7 @@ namespace ESM
|
|||
|
||||
struct AiPackage
|
||||
{
|
||||
virtual ~AiPackage() {}
|
||||
virtual ~AiPackage() = default;
|
||||
};
|
||||
|
||||
struct AiWanderData
|
||||
|
|
@ -89,7 +92,7 @@ namespace ESM
|
|||
{
|
||||
AiEscortData mData;
|
||||
|
||||
int32_t mTargetActorId;
|
||||
ESM::RefNum mTargetActor;
|
||||
ESM::RefId mTargetId;
|
||||
std::string mCellId;
|
||||
float mRemainingDuration;
|
||||
|
|
@ -103,7 +106,7 @@ namespace ESM
|
|||
{
|
||||
AiEscortData mData;
|
||||
|
||||
int32_t mTargetActorId;
|
||||
ESM::RefNum mTargetActor;
|
||||
ESM::RefId mTargetId;
|
||||
std::string mCellId;
|
||||
float mRemainingDuration;
|
||||
|
|
@ -129,7 +132,7 @@ namespace ESM
|
|||
|
||||
struct AiCombat : AiPackage
|
||||
{
|
||||
int32_t mTargetActorId;
|
||||
ESM::RefNum mTargetActor;
|
||||
|
||||
void load(ESMReader& esm);
|
||||
void save(ESMWriter& esm) const;
|
||||
|
|
@ -137,7 +140,7 @@ namespace ESM
|
|||
|
||||
struct AiPursue : AiPackage
|
||||
{
|
||||
int32_t mTargetActorId;
|
||||
ESM::RefNum mTargetActor;
|
||||
|
||||
void load(ESMReader& esm);
|
||||
void save(ESMWriter& esm) const;
|
||||
|
|
@ -152,17 +155,16 @@ namespace ESM
|
|||
|
||||
struct AiSequence
|
||||
{
|
||||
AiSequence() { mLastAiPackage = -1; }
|
||||
|
||||
std::vector<AiPackageContainer> mPackages;
|
||||
int32_t mLastAiPackage;
|
||||
ActorIdConverter* mActorIdConverter = nullptr;
|
||||
int32_t mLastAiPackage = -1;
|
||||
|
||||
AiSequence() {}
|
||||
AiSequence(const AiSequence&) = delete;
|
||||
AiSequence& operator=(const AiSequence&) = delete;
|
||||
|
||||
void load(ESMReader& esm);
|
||||
void save(ESMWriter& esm) const;
|
||||
|
||||
private:
|
||||
AiSequence(const AiSequence&);
|
||||
AiSequence& operator=(const AiSequence&);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,13 @@ namespace ESM
|
|||
{
|
||||
ObjectState::load(esm);
|
||||
|
||||
mSpawnActorId = -1;
|
||||
esm.getHNOT(mSpawnActorId, "SPAW");
|
||||
if (esm.getFormatVersion() <= MaxActorIdSaveGameFormatVersion)
|
||||
{
|
||||
mSpawnedActor.mIndex = static_cast<uint32_t>(-1);
|
||||
esm.getHNOT(mSpawnedActor.mIndex, "SPAW");
|
||||
}
|
||||
else if (esm.peekNextSub("SPAW"))
|
||||
mSpawnedActor = esm.getFormId(true, "SPAW");
|
||||
|
||||
mSpawn = false;
|
||||
esm.getHNOT(mSpawn, "RESP");
|
||||
|
|
@ -21,8 +26,8 @@ namespace ESM
|
|||
{
|
||||
ObjectState::save(esm, inInventory);
|
||||
|
||||
if (mSpawnActorId != -1)
|
||||
esm.writeHNT("SPAW", mSpawnActorId);
|
||||
if (mSpawnedActor.isSet())
|
||||
esm.writeFormId(mSpawnedActor, true, "SPAW");
|
||||
|
||||
if (mSpawn)
|
||||
esm.writeHNT("RESP", mSpawn);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace ESM
|
|||
|
||||
struct CreatureLevListState final : public ObjectState
|
||||
{
|
||||
int32_t mSpawnActorId;
|
||||
ESM::RefNum mSpawnedActor; // actor id in older saves
|
||||
bool mSpawn;
|
||||
|
||||
void load(ESMReader& esm) override;
|
||||
|
|
|
|||
|
|
@ -118,7 +118,8 @@ namespace ESM
|
|||
int32_t actorId;
|
||||
esm.getHNT(actorId, "ACID");
|
||||
mSummonedCreatureMap[SummonKey(magicEffect, source, effectIndex)] = actorId;
|
||||
mSummonedCreatures.emplace(magicEffect, actorId);
|
||||
mSummonedCreatures.emplace(
|
||||
magicEffect, RefNum{ .mIndex = static_cast<uint32_t>(actorId), .mContentFile = -1 });
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -127,9 +128,12 @@ namespace ESM
|
|||
{
|
||||
int32_t magicEffect;
|
||||
esm.getHT(magicEffect);
|
||||
int32_t actorId;
|
||||
esm.getHNT(actorId, "ACID");
|
||||
mSummonedCreatures.emplace(magicEffect, actorId);
|
||||
RefNum actor;
|
||||
if (esm.getFormatVersion() <= MaxActorIdSaveGameFormatVersion)
|
||||
esm.getHNT(actor.mIndex, "ACID");
|
||||
else
|
||||
actor = esm.getFormId(true, "ACID");
|
||||
mSummonedCreatures.emplace(magicEffect, actor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -231,9 +235,6 @@ namespace ESM
|
|||
if (mLevel != 1)
|
||||
esm.writeHNT("LEVL", mLevel);
|
||||
|
||||
if (mActorId != -1)
|
||||
esm.writeHNT("ACID", mActorId);
|
||||
|
||||
if (mDeathAnimation != -1)
|
||||
esm.writeHNT("DANM", mDeathAnimation);
|
||||
|
||||
|
|
@ -245,15 +246,10 @@ namespace ESM
|
|||
mAiSequence.save(esm);
|
||||
mMagicEffects.save(esm);
|
||||
|
||||
for (const auto& [effectId, actorId] : mSummonedCreatures)
|
||||
for (const auto& [effectId, actor] : mSummonedCreatures)
|
||||
{
|
||||
esm.writeHNT("SUMM", effectId);
|
||||
esm.writeHNT("ACID", actorId);
|
||||
}
|
||||
|
||||
for (int32_t key : mSummonGraveyard)
|
||||
{
|
||||
esm.writeHNT("GRAV", key);
|
||||
esm.writeFormId(actor, true, "ACID");
|
||||
}
|
||||
|
||||
esm.writeHNT("AISE", mHasAiSettings);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ namespace ESM
|
|||
std::array<StatState<int>, 4> mAiSettings;
|
||||
|
||||
std::map<SummonKey, int> mSummonedCreatureMap;
|
||||
std::multimap<int, int> mSummonedCreatures;
|
||||
std::multimap<int, RefNum> mSummonedCreatures;
|
||||
std::vector<int> mSummonGraveyard;
|
||||
|
||||
TimeStamp mTradeTime;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ namespace ESM
|
|||
inline constexpr bool IsReadable<void> = false;
|
||||
|
||||
class ReadersCache;
|
||||
class ActorIdConverter; // For old save games
|
||||
|
||||
class ESMReader
|
||||
{
|
||||
|
|
@ -118,11 +119,13 @@ namespace ESM
|
|||
|
||||
// Used only when loading saves to adjust FormIds if load order was changes.
|
||||
void setContentFileMapping(const std::map<int, int>* mapping) { mContentFileMapping = mapping; }
|
||||
const std::map<int, int>* getContentFileMapping();
|
||||
|
||||
// Returns false if content file not found.
|
||||
bool applyContentFileMapping(FormId& id);
|
||||
|
||||
void setActorIdConverter(ActorIdConverter* converter) { mActorIdConverter = converter; }
|
||||
ActorIdConverter* getActorIdConverter() const { return mActorIdConverter; }
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* Medium-level reading shortcuts
|
||||
|
|
@ -386,6 +389,8 @@ namespace ESM
|
|||
size_t mFileSize;
|
||||
|
||||
const std::map<int, int>* mContentFileMapping = nullptr;
|
||||
|
||||
ActorIdConverter* mActorIdConverter = nullptr;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ namespace ESM
|
|||
inline constexpr FormatVersion MaxOldCountFormatVersion = 30;
|
||||
inline constexpr FormatVersion MaxActiveSpellTypeVersion = 31;
|
||||
inline constexpr FormatVersion MaxPlayerBeforeCellDataFormatVersion = 32;
|
||||
inline constexpr FormatVersion CurrentSaveGameFormatVersion = 34;
|
||||
inline constexpr FormatVersion MaxActorIdSaveGameFormatVersion = 34;
|
||||
inline constexpr FormatVersion CurrentSaveGameFormatVersion = 35;
|
||||
|
||||
inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 5;
|
||||
inline constexpr FormatVersion OpenMW0_49MinSaveGameFormatVersion = 5;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace ESM
|
|||
void ObjectState::load(ESMReader& esm)
|
||||
{
|
||||
mVersion = esm.getFormatVersion();
|
||||
mActorIdConverter = esm.getActorIdConverter();
|
||||
|
||||
bool isDeleted;
|
||||
mRef.loadData(esm, isDeleted);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
namespace ESM
|
||||
{
|
||||
class ActorIdConverter;
|
||||
class ESMReader;
|
||||
class ESMWriter;
|
||||
struct ContainerState;
|
||||
|
|
@ -29,27 +30,18 @@ namespace ESM
|
|||
{
|
||||
CellRef mRef;
|
||||
|
||||
unsigned char mHasLocals;
|
||||
Locals mLocals;
|
||||
LuaScripts mLuaScripts;
|
||||
unsigned char mEnabled;
|
||||
Position mPosition;
|
||||
uint32_t mFlags;
|
||||
AnimationState mAnimationState;
|
||||
ActorIdConverter* mActorIdConverter = nullptr;
|
||||
FormatVersion mVersion = DefaultFormatVersion;
|
||||
uint32_t mFlags = 0;
|
||||
unsigned char mHasLocals = 0;
|
||||
unsigned char mEnabled = 0;
|
||||
|
||||
// Is there any class-specific state following the ObjectState
|
||||
bool mHasCustomState;
|
||||
|
||||
FormatVersion mVersion = DefaultFormatVersion;
|
||||
|
||||
AnimationState mAnimationState;
|
||||
|
||||
ObjectState()
|
||||
: mHasLocals(0)
|
||||
, mEnabled(0)
|
||||
, mFlags(0)
|
||||
, mHasCustomState(true)
|
||||
{
|
||||
}
|
||||
bool mHasCustomState = true;
|
||||
|
||||
/// @note Does not load the CellRef ID, it should already be loaded before calling this method
|
||||
virtual void load(ESMReader& esm);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace ESM
|
|||
esm.writeHNRefId("ID__", mId);
|
||||
esm.writeHNT("VEC3", mPosition);
|
||||
esm.writeHNT("QUAT", mOrientation);
|
||||
esm.writeHNT("ACTO", mActorId);
|
||||
esm.writeFormId(mCaster, true, "ACTO");
|
||||
}
|
||||
|
||||
void BaseProjectileState::load(ESMReader& esm)
|
||||
|
|
@ -19,7 +19,13 @@ namespace ESM
|
|||
mId = esm.getHNRefId("ID__");
|
||||
esm.getHNT("VEC3", mPosition.mValues);
|
||||
esm.getHNT("QUAT", mOrientation.mValues);
|
||||
esm.getHNT(mActorId, "ACTO");
|
||||
if (esm.getFormatVersion() <= MaxActorIdSaveGameFormatVersion)
|
||||
{
|
||||
mCaster.mIndex = static_cast<uint32_t>(-1);
|
||||
esm.getHNT(mCaster.mIndex, "ACTO");
|
||||
}
|
||||
else
|
||||
mCaster = esm.getFormId(true, "ACTO");
|
||||
}
|
||||
|
||||
void MagicBoltState::save(ESMWriter& esm) const
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace ESM
|
|||
Vector3 mPosition;
|
||||
Quaternion mOrientation;
|
||||
|
||||
int32_t mActorId;
|
||||
RefNum mCaster;
|
||||
|
||||
void load(ESMReader& esm);
|
||||
void save(ESMWriter& esm) const;
|
||||
|
|
|
|||
Loading…
Reference in a new issue