forked from mirror/openmw-tes3mp
Load/read methods (for ESM records) accept a deleted flag in OpenMW
This commit is contained in:
parent
4a16eba716
commit
67c8f95c4e
5 changed files with 79 additions and 83 deletions
|
@ -22,7 +22,7 @@ namespace MWWorld
|
||||||
/// and the build will fail with an ugly three-way cyclic header dependence
|
/// and the build will fail with an ugly three-way cyclic header dependence
|
||||||
/// so we need to pass the instantiation of the method to the linker, when
|
/// so we need to pass the instantiation of the method to the linker, when
|
||||||
/// all methods are known.
|
/// all methods are known.
|
||||||
void load (ESM::CellRef &ref, const MWWorld::ESMStore &esmStore);
|
void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore);
|
||||||
|
|
||||||
LiveRef *find (const std::string& name)
|
LiveRef *find (const std::string& name)
|
||||||
{
|
{
|
||||||
|
|
|
@ -147,7 +147,7 @@ namespace MWWorld
|
||||||
{
|
{
|
||||||
|
|
||||||
template <typename X>
|
template <typename X>
|
||||||
void CellRefList<X>::load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore)
|
void CellRefList<X>::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore)
|
||||||
{
|
{
|
||||||
const MWWorld::Store<X> &store = esmStore.get<X>();
|
const MWWorld::Store<X> &store = esmStore.get<X>();
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ namespace MWWorld
|
||||||
|
|
||||||
LiveRef liveCellRef (ref, ptr);
|
LiveRef liveCellRef (ref, ptr);
|
||||||
|
|
||||||
if (ref.mIsDeleted)
|
if (deleted)
|
||||||
liveCellRef.mData.setDeleted(true);
|
liveCellRef.mData.setDeleted(true);
|
||||||
|
|
||||||
if (iter != mList.end())
|
if (iter != mList.end())
|
||||||
|
@ -374,9 +374,10 @@ namespace MWWorld
|
||||||
ESM::CellRef ref;
|
ESM::CellRef ref;
|
||||||
|
|
||||||
// Get each reference in turn
|
// Get each reference in turn
|
||||||
while (mCell->getNextRef (esm[index], ref))
|
bool deleted = false;
|
||||||
|
while (mCell->getNextRef (esm[index], ref, deleted))
|
||||||
{
|
{
|
||||||
if (ref.mIsDeleted)
|
if (deleted)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Don't list reference if it was moved to a different cell.
|
// Don't list reference if it was moved to a different cell.
|
||||||
|
@ -419,7 +420,8 @@ namespace MWWorld
|
||||||
ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile;
|
ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile;
|
||||||
|
|
||||||
// Get each reference in turn
|
// Get each reference in turn
|
||||||
while(mCell->getNextRef(esm[index], ref))
|
bool deleted = false;
|
||||||
|
while(mCell->getNextRef(esm[index], ref, deleted))
|
||||||
{
|
{
|
||||||
// Don't load reference if it was moved to a different cell.
|
// Don't load reference if it was moved to a different cell.
|
||||||
ESM::MovedCellRefTracker::const_iterator iter =
|
ESM::MovedCellRefTracker::const_iterator iter =
|
||||||
|
@ -428,7 +430,7 @@ namespace MWWorld
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadRef (ref, store);
|
loadRef (ref, deleted, store);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,7 +439,7 @@ namespace MWWorld
|
||||||
{
|
{
|
||||||
ESM::CellRef &ref = const_cast<ESM::CellRef&>(*it);
|
ESM::CellRef &ref = const_cast<ESM::CellRef&>(*it);
|
||||||
|
|
||||||
loadRef (ref, store);
|
loadRef (ref, false, store);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,32 +468,32 @@ namespace MWWorld
|
||||||
return Ptr();
|
return Ptr();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CellStore::loadRef (ESM::CellRef& ref, const ESMStore& store)
|
void CellStore::loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store)
|
||||||
{
|
{
|
||||||
Misc::StringUtils::toLower (ref.mRefID);
|
Misc::StringUtils::toLower (ref.mRefID);
|
||||||
|
|
||||||
switch (store.find (ref.mRefID))
|
switch (store.find (ref.mRefID))
|
||||||
{
|
{
|
||||||
case ESM::REC_ACTI: mActivators.load(ref, store); break;
|
case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break;
|
||||||
case ESM::REC_ALCH: mPotions.load(ref,store); break;
|
case ESM::REC_ALCH: mPotions.load(ref, deleted,store); break;
|
||||||
case ESM::REC_APPA: mAppas.load(ref, store); break;
|
case ESM::REC_APPA: mAppas.load(ref, deleted, store); break;
|
||||||
case ESM::REC_ARMO: mArmors.load(ref, store); break;
|
case ESM::REC_ARMO: mArmors.load(ref, deleted, store); break;
|
||||||
case ESM::REC_BOOK: mBooks.load(ref, store); break;
|
case ESM::REC_BOOK: mBooks.load(ref, deleted, store); break;
|
||||||
case ESM::REC_CLOT: mClothes.load(ref, store); break;
|
case ESM::REC_CLOT: mClothes.load(ref, deleted, store); break;
|
||||||
case ESM::REC_CONT: mContainers.load(ref, store); break;
|
case ESM::REC_CONT: mContainers.load(ref, deleted, store); break;
|
||||||
case ESM::REC_CREA: mCreatures.load(ref, store); break;
|
case ESM::REC_CREA: mCreatures.load(ref, deleted, store); break;
|
||||||
case ESM::REC_DOOR: mDoors.load(ref, store); break;
|
case ESM::REC_DOOR: mDoors.load(ref, deleted, store); break;
|
||||||
case ESM::REC_INGR: mIngreds.load(ref, store); break;
|
case ESM::REC_INGR: mIngreds.load(ref, deleted, store); break;
|
||||||
case ESM::REC_LEVC: mCreatureLists.load(ref, store); break;
|
case ESM::REC_LEVC: mCreatureLists.load(ref, deleted, store); break;
|
||||||
case ESM::REC_LEVI: mItemLists.load(ref, store); break;
|
case ESM::REC_LEVI: mItemLists.load(ref, deleted, store); break;
|
||||||
case ESM::REC_LIGH: mLights.load(ref, store); break;
|
case ESM::REC_LIGH: mLights.load(ref, deleted, store); break;
|
||||||
case ESM::REC_LOCK: mLockpicks.load(ref, store); break;
|
case ESM::REC_LOCK: mLockpicks.load(ref, deleted, store); break;
|
||||||
case ESM::REC_MISC: mMiscItems.load(ref, store); break;
|
case ESM::REC_MISC: mMiscItems.load(ref, deleted, store); break;
|
||||||
case ESM::REC_NPC_: mNpcs.load(ref, store); break;
|
case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break;
|
||||||
case ESM::REC_PROB: mProbes.load(ref, store); break;
|
case ESM::REC_PROB: mProbes.load(ref, deleted, store); break;
|
||||||
case ESM::REC_REPA: mRepairs.load(ref, store); break;
|
case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break;
|
||||||
case ESM::REC_STAT: mStatics.load(ref, store); break;
|
case ESM::REC_STAT: mStatics.load(ref, deleted, store); break;
|
||||||
case ESM::REC_WEAP: mWeapons.load(ref, store); break;
|
case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break;
|
||||||
|
|
||||||
case 0: std::cerr << "Cell reference " + ref.mRefID + " not found!\n"; break;
|
case 0: std::cerr << "Cell reference " + ref.mRefID + " not found!\n"; break;
|
||||||
|
|
||||||
|
|
|
@ -214,7 +214,7 @@ namespace MWWorld
|
||||||
|
|
||||||
void loadRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
|
void loadRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
|
||||||
|
|
||||||
void loadRef (ESM::CellRef& ref, const ESMStore& store);
|
void loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store);
|
||||||
///< Make case-adjustments to \a ref and insert it into the respective container.
|
///< Make case-adjustments to \a ref and insert it into the respective container.
|
||||||
///
|
///
|
||||||
/// Invalid \a ref objects are silently dropped.
|
/// Invalid \a ref objects are silently dropped.
|
||||||
|
|
|
@ -92,7 +92,11 @@ namespace MWWorld
|
||||||
if (type==ESM::REC_GLOB)
|
if (type==ESM::REC_GLOB)
|
||||||
{
|
{
|
||||||
ESM::Global global;
|
ESM::Global global;
|
||||||
global.load(reader);
|
bool isDeleted = false;
|
||||||
|
|
||||||
|
// This readRecord() method is used when reading a saved game.
|
||||||
|
// Deleted globals can't appear there, so isDeleted will be ignored here.
|
||||||
|
global.load(reader, isDeleted);
|
||||||
Misc::StringUtils::toLower(global.mId);
|
Misc::StringUtils::toLower(global.mId);
|
||||||
|
|
||||||
Collection::iterator iter = mVariables.find (global.mId);
|
Collection::iterator iter = mVariables.find (global.mId);
|
||||||
|
|
|
@ -66,7 +66,9 @@ namespace MWWorld
|
||||||
void IndexedStore<T>::load(ESM::ESMReader &esm)
|
void IndexedStore<T>::load(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
T record;
|
T record;
|
||||||
record.load(esm);
|
bool isDeleted = false;
|
||||||
|
|
||||||
|
record.load(esm, isDeleted);
|
||||||
|
|
||||||
// Try to overwrite existing record
|
// Try to overwrite existing record
|
||||||
std::pair<typename Static::iterator, bool> ret = mStatic.insert(std::make_pair(record.mIndex, record));
|
std::pair<typename Static::iterator, bool> ret = mStatic.insert(std::make_pair(record.mIndex, record));
|
||||||
|
@ -187,14 +189,16 @@ namespace MWWorld
|
||||||
RecordId Store<T>::load(ESM::ESMReader &esm)
|
RecordId Store<T>::load(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
T record;
|
T record;
|
||||||
record.load(esm);
|
bool isDeleted = false;
|
||||||
|
|
||||||
|
record.load(esm, isDeleted);
|
||||||
Misc::StringUtils::toLower(record.mId);
|
Misc::StringUtils::toLower(record.mId);
|
||||||
|
|
||||||
std::pair<typename Static::iterator, bool> inserted = mStatic.insert(std::make_pair(record.mId, record));
|
std::pair<typename Static::iterator, bool> inserted = mStatic.insert(std::make_pair(record.mId, record));
|
||||||
if (inserted.second)
|
if (inserted.second)
|
||||||
mShared.push_back(&inserted.first->second);
|
mShared.push_back(&inserted.first->second);
|
||||||
|
|
||||||
return RecordId(record.mId, record.mIsDeleted);
|
return RecordId(record.mId, isDeleted);
|
||||||
}
|
}
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void Store<T>::setUp()
|
void Store<T>::setUp()
|
||||||
|
@ -324,10 +328,12 @@ namespace MWWorld
|
||||||
RecordId Store<T>::read(ESM::ESMReader& reader)
|
RecordId Store<T>::read(ESM::ESMReader& reader)
|
||||||
{
|
{
|
||||||
T record;
|
T record;
|
||||||
record.load (reader);
|
bool isDeleted = false;
|
||||||
|
|
||||||
|
record.load (reader, isDeleted);
|
||||||
insert (record);
|
insert (record);
|
||||||
|
|
||||||
return RecordId(record.mId, record.mIsDeleted);
|
return RecordId(record.mId, isDeleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
// LandTexture
|
// LandTexture
|
||||||
|
@ -370,7 +376,9 @@ namespace MWWorld
|
||||||
RecordId Store<ESM::LandTexture>::load(ESM::ESMReader &esm, size_t plugin)
|
RecordId Store<ESM::LandTexture>::load(ESM::ESMReader &esm, size_t plugin)
|
||||||
{
|
{
|
||||||
ESM::LandTexture lt;
|
ESM::LandTexture lt;
|
||||||
lt.load(esm);
|
bool isDeleted = false;
|
||||||
|
|
||||||
|
lt.load(esm, isDeleted);
|
||||||
|
|
||||||
// Make sure we have room for the structure
|
// Make sure we have room for the structure
|
||||||
if (plugin >= mStatic.size()) {
|
if (plugin >= mStatic.size()) {
|
||||||
|
@ -383,7 +391,7 @@ namespace MWWorld
|
||||||
// Store it
|
// Store it
|
||||||
ltexl[lt.mIndex] = lt;
|
ltexl[lt.mIndex] = lt;
|
||||||
|
|
||||||
return RecordId(lt.mId, lt.mIsDeleted);
|
return RecordId(lt.mId, isDeleted);
|
||||||
}
|
}
|
||||||
RecordId Store<ESM::LandTexture>::load(ESM::ESMReader &esm)
|
RecordId Store<ESM::LandTexture>::load(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
|
@ -449,7 +457,9 @@ namespace MWWorld
|
||||||
RecordId Store<ESM::Land>::load(ESM::ESMReader &esm)
|
RecordId Store<ESM::Land>::load(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
ESM::Land *ptr = new ESM::Land();
|
ESM::Land *ptr = new ESM::Land();
|
||||||
ptr->load(esm);
|
bool isDeleted = false;
|
||||||
|
|
||||||
|
ptr->load(esm, isDeleted);
|
||||||
|
|
||||||
// Same area defined in multiple plugins? -> last plugin wins
|
// Same area defined in multiple plugins? -> last plugin wins
|
||||||
// Can't use search() because we aren't sorted yet - is there any other way to speed this up?
|
// Can't use search() because we aren't sorted yet - is there any other way to speed this up?
|
||||||
|
@ -465,7 +475,7 @@ namespace MWWorld
|
||||||
|
|
||||||
mStatic.push_back(ptr);
|
mStatic.push_back(ptr);
|
||||||
|
|
||||||
return RecordId(); // No ID and can't be deleted (for now)
|
return RecordId("", isDeleted);
|
||||||
}
|
}
|
||||||
void Store<ESM::Land>::setUp()
|
void Store<ESM::Land>::setUp()
|
||||||
{
|
{
|
||||||
|
@ -617,12 +627,12 @@ namespace MWWorld
|
||||||
|
|
||||||
// All cells have a name record, even nameless exterior cells.
|
// All cells have a name record, even nameless exterior cells.
|
||||||
ESM::Cell cell;
|
ESM::Cell cell;
|
||||||
cell.loadName(esm);
|
bool isDeleted = false;
|
||||||
std::string idLower = Misc::StringUtils::lowerCase(cell.mName);
|
|
||||||
|
|
||||||
// Load the (x,y) coordinates of the cell, if it is an exterior cell,
|
// Load the (x,y) coordinates of the cell, if it is an exterior cell,
|
||||||
// so we can find the cell we need to merge with
|
// so we can find the cell we need to merge with
|
||||||
cell.loadData(esm);
|
cell.loadNameAndData(esm, isDeleted);
|
||||||
|
std::string idLower = Misc::StringUtils::lowerCase(cell.mName);
|
||||||
|
|
||||||
if(cell.mData.mFlags & ESM::Cell::Interior)
|
if(cell.mData.mFlags & ESM::Cell::Interior)
|
||||||
{
|
{
|
||||||
|
@ -690,7 +700,8 @@ namespace MWWorld
|
||||||
mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell;
|
mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return RecordId("", cell.mIsDeleted);
|
|
||||||
|
return RecordId(cell.mName, isDeleted);
|
||||||
}
|
}
|
||||||
Store<ESM::Cell>::iterator Store<ESM::Cell>::intBegin() const
|
Store<ESM::Cell>::iterator Store<ESM::Cell>::intBegin() const
|
||||||
{
|
{
|
||||||
|
@ -849,7 +860,9 @@ namespace MWWorld
|
||||||
RecordId Store<ESM::Pathgrid>::load(ESM::ESMReader &esm)
|
RecordId Store<ESM::Pathgrid>::load(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
ESM::Pathgrid pathgrid;
|
ESM::Pathgrid pathgrid;
|
||||||
pathgrid.load(esm);
|
bool isDeleted = false;
|
||||||
|
|
||||||
|
pathgrid.load(esm, isDeleted);
|
||||||
|
|
||||||
// Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell.
|
// Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell.
|
||||||
// For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name.
|
// For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name.
|
||||||
|
@ -872,7 +885,7 @@ namespace MWWorld
|
||||||
ret.first->second = pathgrid;
|
ret.first->second = pathgrid;
|
||||||
}
|
}
|
||||||
|
|
||||||
return RecordId(); // No ID and can't be deleted (for now)
|
return RecordId("", isDeleted);
|
||||||
}
|
}
|
||||||
size_t Store<ESM::Pathgrid>::getSize() const
|
size_t Store<ESM::Pathgrid>::getSize() const
|
||||||
{
|
{
|
||||||
|
@ -1027,22 +1040,24 @@ namespace MWWorld
|
||||||
inline RecordId Store<ESM::Dialogue>::load(ESM::ESMReader &esm) {
|
inline RecordId Store<ESM::Dialogue>::load(ESM::ESMReader &esm) {
|
||||||
// The original letter case of a dialogue ID is saved, because it's printed
|
// The original letter case of a dialogue ID is saved, because it's printed
|
||||||
ESM::Dialogue dialogue;
|
ESM::Dialogue dialogue;
|
||||||
|
bool isDeleted = false;
|
||||||
|
|
||||||
dialogue.loadId(esm);
|
dialogue.loadId(esm);
|
||||||
|
|
||||||
std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId);
|
std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId);
|
||||||
std::map<std::string, ESM::Dialogue>::iterator found = mStatic.find(idLower);
|
std::map<std::string, ESM::Dialogue>::iterator found = mStatic.find(idLower);
|
||||||
if (found == mStatic.end())
|
if (found == mStatic.end())
|
||||||
{
|
{
|
||||||
dialogue.loadData(esm);
|
dialogue.loadData(esm, isDeleted);
|
||||||
mStatic.insert(std::make_pair(idLower, dialogue));
|
mStatic.insert(std::make_pair(idLower, dialogue));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
found->second.loadData(esm);
|
found->second.loadData(esm, isDeleted);
|
||||||
dialogue = found->second;
|
dialogue = found->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
return RecordId(dialogue.mId, dialogue.mIsDeleted);
|
return RecordId(dialogue.mId, isDeleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1052,7 +1067,9 @@ namespace MWWorld
|
||||||
template <>
|
template <>
|
||||||
inline RecordId Store<ESM::Script>::load(ESM::ESMReader &esm) {
|
inline RecordId Store<ESM::Script>::load(ESM::ESMReader &esm) {
|
||||||
ESM::Script script;
|
ESM::Script script;
|
||||||
script.load(esm);
|
bool isDeleted = false;
|
||||||
|
|
||||||
|
script.load(esm, isDeleted);
|
||||||
Misc::StringUtils::toLower(script.mId);
|
Misc::StringUtils::toLower(script.mId);
|
||||||
|
|
||||||
std::pair<typename Static::iterator, bool> inserted = mStatic.insert(std::make_pair(script.mId, script));
|
std::pair<typename Static::iterator, bool> inserted = mStatic.insert(std::make_pair(script.mId, script));
|
||||||
|
@ -1061,7 +1078,7 @@ namespace MWWorld
|
||||||
else
|
else
|
||||||
inserted.first->second = script;
|
inserted.first->second = script;
|
||||||
|
|
||||||
return RecordId(script.mId, script.mIsDeleted);
|
return RecordId(script.mId, isDeleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1072,7 +1089,9 @@ namespace MWWorld
|
||||||
inline RecordId Store<ESM::StartScript>::load(ESM::ESMReader &esm)
|
inline RecordId Store<ESM::StartScript>::load(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
ESM::StartScript script;
|
ESM::StartScript script;
|
||||||
script.load(esm);
|
bool isDeleted = false;
|
||||||
|
|
||||||
|
script.load(esm, isDeleted);
|
||||||
Misc::StringUtils::toLower(script.mId);
|
Misc::StringUtils::toLower(script.mId);
|
||||||
|
|
||||||
std::pair<typename Static::iterator, bool> inserted = mStatic.insert(std::make_pair(script.mId, script));
|
std::pair<typename Static::iterator, bool> inserted = mStatic.insert(std::make_pair(script.mId, script));
|
||||||
|
@ -1081,36 +1100,7 @@ namespace MWWorld
|
||||||
else
|
else
|
||||||
inserted.first->second = script;
|
inserted.first->second = script;
|
||||||
|
|
||||||
return RecordId(script.mId);
|
return RecordId(script.mId, isDeleted);
|
||||||
}
|
|
||||||
|
|
||||||
// GameSetting
|
|
||||||
// Need to specialize load() and read() methods, because GameSetting can't
|
|
||||||
// be deleted (has no mIsDeleted flag)
|
|
||||||
//=========================================================================
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline RecordId Store<ESM::GameSetting>::load(ESM::ESMReader &reader)
|
|
||||||
{
|
|
||||||
ESM::GameSetting setting;
|
|
||||||
setting.load(reader);
|
|
||||||
Misc::StringUtils::toLower(setting.mId);
|
|
||||||
|
|
||||||
std::pair<typename Static::iterator, bool> inserted = mStatic.insert(std::make_pair(setting.mId, setting));
|
|
||||||
if (inserted.second)
|
|
||||||
mShared.push_back(&inserted.first->second);
|
|
||||||
|
|
||||||
return RecordId(setting.mId);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline RecordId Store<ESM::GameSetting>::read(ESM::ESMReader &reader)
|
|
||||||
{
|
|
||||||
ESM::GameSetting setting;
|
|
||||||
setting.load(reader);
|
|
||||||
insert(setting);
|
|
||||||
|
|
||||||
return RecordId(setting.mId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue