1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2026-01-19 16:30:53 +00:00

Use a RefMum to track spawned levelled creatures

This commit is contained in:
Evil Eye 2024-12-20 14:55:24 +01:00
parent b3d984a9b1
commit 1bcc403544
12 changed files with 133 additions and 54 deletions

View file

@ -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,16 @@ 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)
{
if (state.mActorIdConverter->convert(customData.mSpawnedActor, customData.mSpawnedActor.mIndex))
return;
state.mActorIdConverter->addConverter([converter = state.mActorIdConverter, &customData]() {
customData.mSpawnedActor = converter->convert(customData.mSpawnedActor.mIndex);
});
}
}
void CreatureLevList::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
@ -171,7 +171,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;
}
}

View file

@ -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>
@ -469,6 +470,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 +594,7 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
currentPercent = progressPercent;
}
}
actorIdConverter.apply();
mCharacterManager.setCurrentCharacter(character);
mState = State_Running;

View file

@ -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>
@ -275,6 +276,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 (reader.getActorIdConverter())
{
if constexpr (std::is_same_v<T, ESM::Creature> || std::is_same_v<T, ESM::NPC>)
{
MWBase::Environment::get().getWorldModel()->assignSaveFileRefNum(state.mRef);
reader.getActorIdConverter()->mMappings.emplace(state.mCreatureStats.mActorId, state.mRef.mRefNum);
}
}
if (state.mRef.mRefNum.hasContentFile())
{

View file

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

View file

@ -0,0 +1,32 @@
#include "actoridconverter.hpp"
namespace ESM
{
void ActorIdConverter::apply()
{
for (auto& converter : mConverters)
converter();
}
ESM::RefNum ActorIdConverter::convert(int actorId) const
{
auto it = mMappings.find(actorId);
if (it == mMappings.end())
return {};
return it->second;
}
bool ActorIdConverter::convert(ESM::RefNum& refNum, int actorId) const
{
if (actorId == -1)
{
refNum = {};
return true;
}
auto it = mMappings.find(actorId);
if (it == mMappings.end())
return false;
refNum = it->second;
return true;
}
}

View file

@ -0,0 +1,29 @@
#ifndef OPENMW_COMPONENTS_ESM3_ACTORIDCONVERTER_H
#define OPENMW_COMPONENTS_ESM3_ACTORIDCONVERTER_H
#include <functional>
#include <map>
#include <vector>
#include "refnum.hpp"
namespace ESM
{
class ActorIdConverter
{
std::vector<std::function<void()>> mConverters;
public:
std::map<int, ESM::RefNum> mMappings;
void apply();
ESM::RefNum convert(int actorId) const;
bool convert(ESM::RefNum& refNum, int actorId) const;
void addConverter(std::function<void()>&& converter) { mConverters.emplace_back(converter); }
};
}
#endif

View file

@ -10,8 +10,13 @@ namespace ESM
{
ObjectState::load(esm);
mSpawnActorId = -1;
esm.getHNOT(mSpawnActorId, "SPAW");
if (esm.getFormatVersion() <= MaxActorIdSaveGameFormatVersion)
{
mSpawnedActor.mIndex = -1;
esm.getHNOT(mSpawnedActor.mIndex, "SPAW");
}
else if (esm.peekNextSub("SPAW"))
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);

View file

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

View file

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

View file

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

View file

@ -14,6 +14,7 @@ namespace ESM
void ObjectState::load(ESMReader& esm)
{
mVersion = esm.getFormatVersion();
mActorIdConverter = esm.getActorIdConverter();
bool isDeleted;
mRef.loadData(esm, isDeleted);

View file

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