2012-10-01 15:17:04 +00:00
# include "esmstore.hpp"
2010-05-17 18:59:15 +00:00
# include <set>
2010-05-17 15:35:42 +00:00
# include <iostream>
2013-02-14 09:22:00 +00:00
# include <boost/filesystem/operations.hpp>
2012-11-25 16:19:29 +00:00
2013-08-27 13:48:13 +00:00
# include <components/loadinglistener/loadinglistener.hpp>
2015-01-25 00:53:20 +00:00
# include <components/esm/esmreader.hpp>
2015-07-09 17:22:04 +00:00
# include <components/esm/esmwriter.hpp>
2015-01-25 00:53:20 +00:00
2012-10-01 15:17:04 +00:00
namespace MWWorld
2010-05-17 15:35:42 +00:00
{
2012-11-05 14:09:14 +00:00
static bool isCacheableRecord ( int id )
{
if ( id = = ESM : : REC_ACTI | | id = = ESM : : REC_ALCH | | id = = ESM : : REC_APPA | | id = = ESM : : REC_ARMO | |
id = = ESM : : REC_BOOK | | id = = ESM : : REC_CLOT | | id = = ESM : : REC_CONT | | id = = ESM : : REC_CREA | |
id = = ESM : : REC_DOOR | | id = = ESM : : REC_INGR | | id = = ESM : : REC_LEVC | | id = = ESM : : REC_LEVI | |
id = = ESM : : REC_LIGH | | id = = ESM : : REC_LOCK | | id = = ESM : : REC_MISC | | id = = ESM : : REC_NPC_ | |
2016-01-01 23:49:53 +00:00
id = = ESM : : REC_PROB | | id = = ESM : : REC_REPA | | id = = ESM : : REC_STAT | | id = = ESM : : REC_WEAP | |
id = = ESM : : REC_BODY )
2012-11-05 14:09:14 +00:00
{
return true ;
}
return false ;
}
2013-08-27 13:48:13 +00:00
void ESMStore : : load ( ESM : : ESMReader & esm , Loading : : Listener * listener )
2010-05-17 15:35:42 +00:00
{
2013-08-27 13:48:13 +00:00
listener - > setProgressRange ( 1000 ) ;
2010-08-06 13:19:39 +00:00
ESM : : Dialogue * dialogue = 0 ;
2015-11-27 20:40:36 +00:00
// Land texture loading needs to use a separate internal store for each plugin.
// We set the number of plugins here to avoid continual resizes during loading,
// and so we can properly verify if valid plugin indices are being passed to the
// LandTexture Store retrieval methods.
mLandTextures . resize ( esm . getGlobalReaderList ( ) - > size ( ) ) ;
2013-03-12 07:15:20 +00:00
/// \todo Move this to somewhere else. ESMReader?
2012-11-25 16:19:29 +00:00
// Cache parent esX files by tracking their indices in the global list of
2013-01-20 18:07:33 +00:00
// all files/readers used by the engine. This will greaty accelerate
// refnumber mangling, as required for handling moved references.
2013-09-22 04:06:29 +00:00
const std : : vector < ESM : : Header : : MasterData > & masters = esm . getGameFiles ( ) ;
2012-11-25 16:19:29 +00:00
std : : vector < ESM : : ESMReader > * allPlugins = esm . getGlobalReaderList ( ) ;
for ( size_t j = 0 ; j < masters . size ( ) ; j + + ) {
2013-03-12 07:15:20 +00:00
ESM : : Header : : MasterData & mast = const_cast < ESM : : Header : : MasterData & > ( masters [ j ] ) ;
2012-11-25 16:19:29 +00:00
std : : string fname = mast . name ;
2014-05-26 22:06:34 +00:00
int index = ~ 0 ;
2013-01-19 22:33:18 +00:00
for ( int i = 0 ; i < esm . getIndex ( ) ; i + + ) {
2012-11-25 16:19:29 +00:00
const std : : string & candidate = allPlugins - > at ( i ) . getContext ( ) . filename ;
std : : string fnamecandidate = boost : : filesystem : : path ( candidate ) . filename ( ) . string ( ) ;
2014-05-26 15:34:36 +00:00
if ( Misc : : StringUtils : : ciEqual ( fname , fnamecandidate ) ) {
2012-11-25 16:19:29 +00:00
index = i ;
break ;
}
}
2013-01-19 22:33:18 +00:00
if ( index = = ( int ) ~ 0 ) {
2012-11-25 16:19:29 +00:00
// Tried to load a parent file that has not been loaded yet. This is bad,
// the launcher should have taken care of this.
2013-12-16 10:39:24 +00:00
std : : string fstring = " File " + esm . getName ( ) + " asks for parent file " + masters [ j ] . name
2012-11-25 16:19:29 +00:00
+ " , but it has not been loaded yet. Please check your load order. " ;
esm . fail ( fstring ) ;
}
mast . index = index ;
}
2010-08-06 13:19:39 +00:00
// Loop through all records
while ( esm . hasMoreRecs ( ) )
2010-05-17 15:35:42 +00:00
{
2012-10-01 15:17:04 +00:00
ESM : : NAME n = esm . getRecName ( ) ;
2010-08-06 13:19:39 +00:00
esm . getRecHeader ( ) ;
2010-05-17 15:35:42 +00:00
2010-08-06 13:19:39 +00:00
// Look up the record type.
2016-05-07 17:32:51 +00:00
std : : map < int , StoreBase * > : : iterator it = mStores . find ( n . intval ) ;
2012-11-05 14:09:14 +00:00
if ( it = = mStores . end ( ) ) {
2016-05-07 17:32:51 +00:00
if ( n . intval = = ESM : : REC_INFO ) {
2014-05-30 22:36:37 +00:00
if ( dialogue )
{
dialogue - > readInfo ( esm , esm . getIndex ( ) ! = 0 ) ;
}
else
{
2010-08-06 13:19:39 +00:00
std : : cerr < < " error: info record without dialog " < < std : : endl ;
esm . skipRecord ( ) ;
}
2016-05-07 17:32:51 +00:00
} else if ( n . intval = = ESM : : REC_MGEF ) {
2012-11-05 14:09:14 +00:00
mMagicEffects . load ( esm ) ;
2016-05-07 17:32:51 +00:00
} else if ( n . intval = = ESM : : REC_SKIL ) {
2012-11-05 14:09:14 +00:00
mSkills . load ( esm ) ;
2014-09-13 18:48:24 +00:00
}
2016-05-07 17:32:51 +00:00
else if ( n . intval = = ESM : : REC_FILT | | n . intval = = ESM : : REC_DBGP )
2014-09-13 18:48:24 +00:00
{
// ignore project file only records
esm . skipRecord ( ) ;
}
else {
2014-05-18 14:29:24 +00:00
std : : stringstream error ;
error < < " Unknown record: " < < n . toString ( ) ;
throw std : : runtime_error ( error . str ( ) ) ;
2010-08-06 13:19:39 +00:00
}
2012-11-05 14:09:14 +00:00
} else {
2015-07-12 12:20:22 +00:00
RecordId id = it - > second - > load ( esm ) ;
if ( id . mIsDeleted )
2015-07-08 18:26:20 +00:00
{
2015-07-12 12:20:22 +00:00
it - > second - > eraseStatic ( id . mId ) ;
2015-07-08 18:26:20 +00:00
continue ;
2014-06-07 17:21:37 +00:00
}
2016-05-07 17:32:51 +00:00
if ( n . intval = = ESM : : REC_DIAL ) {
2015-07-12 12:20:22 +00:00
dialogue = const_cast < ESM : : Dialogue * > ( mDialogs . find ( id . mId ) ) ;
2012-11-05 14:09:14 +00:00
} else {
2010-08-06 13:19:39 +00:00
dialogue = 0 ;
2012-11-05 14:09:14 +00:00
}
2010-08-06 13:19:39 +00:00
}
2015-03-08 00:07:29 +00:00
listener - > setProgress ( static_cast < size_t > ( esm . getFileOffset ( ) / ( float ) esm . getFileSize ( ) * 1000 ) ) ;
2010-05-17 15:35:42 +00:00
}
}
2012-10-01 15:17:04 +00:00
2018-06-09 16:47:17 +00:00
void ESMStore : : setUp ( bool validateRecords )
2012-11-05 14:09:14 +00:00
{
2015-12-11 14:55:45 +00:00
mIds . clear ( ) ;
std : : map < int , StoreBase * > : : iterator storeIt = mStores . begin ( ) ;
for ( ; storeIt ! = mStores . end ( ) ; + + storeIt ) {
2015-12-11 15:59:13 +00:00
storeIt - > second - > setUp ( ) ;
2015-12-11 14:55:45 +00:00
if ( isCacheableRecord ( storeIt - > first ) )
{
std : : vector < std : : string > identifiers ;
storeIt - > second - > listIdentifier ( identifiers ) ;
for ( std : : vector < std : : string > : : const_iterator record = identifiers . begin ( ) ; record ! = identifiers . end ( ) ; + + record )
mIds [ * record ] = storeIt - > first ;
}
2012-11-05 14:09:14 +00:00
}
mSkills . setUp ( ) ;
mMagicEffects . setUp ( ) ;
mAttributes . setUp ( ) ;
2014-10-19 15:45:18 +00:00
mDialogs . setUp ( ) ;
2018-05-03 06:37:55 +00:00
mStatics . setUp ( ) ;
2018-06-09 16:47:17 +00:00
if ( validateRecords )
validate ( ) ;
}
void ESMStore : : validate ( )
{
// Cache first class from store - we will use it if current class is not found
std : : string defaultCls = " " ;
Store < ESM : : Class > : : iterator it = mClasses . begin ( ) ;
if ( it ! = mClasses . end ( ) )
defaultCls = it - > mId ;
else
throw std : : runtime_error ( " List of NPC classes is empty! " ) ;
// Validate NPCs for non-existing class and faction.
// We will replace invalid entries by fixed ones
std : : vector < ESM : : NPC > entitiesToReplace ;
for ( ESM : : NPC npc : mNpcs )
{
bool changed = false ;
const std : : string npcFaction = npc . mFaction ;
if ( ! npcFaction . empty ( ) )
{
const ESM : : Faction * fact = mFactions . search ( npcFaction ) ;
if ( ! fact )
{
std : : cerr < < " NPC ' " < < npc . mId < < " ' ( " < < npc . mName < < " ) has nonexistent faction ' " < < npc . mFaction < < " ', ignoring it. " < < std : : endl ;
npc . mFaction = " " ;
npc . mNpdt . mRank = - 1 ;
changed = true ;
}
}
std : : string npcClass = npc . mClass ;
if ( ! npcClass . empty ( ) )
{
const ESM : : Class * cls = mClasses . search ( npcClass ) ;
if ( ! cls )
{
std : : cerr < < " NPC ' " < < npc . mId < < " ' ( " < < npc . mName < < " ) has nonexistent class ' " < < npc . mClass < < " ', using ' " < < defaultCls < < " ' class as replacement. " < < std : : endl ;
npc . mClass = defaultCls ;
changed = true ;
}
}
if ( changed )
entitiesToReplace . push_back ( npc ) ;
}
for ( const ESM : : NPC & npc : entitiesToReplace )
{
mNpcs . eraseStatic ( npc . mId ) ;
mNpcs . insertStatic ( npc ) ;
}
2012-11-05 14:09:14 +00:00
}
2013-12-07 12:17:28 +00:00
int ESMStore : : countSavedGameRecords ( ) const
{
2014-05-10 22:32:22 +00:00
return 1 // DYNA (dynamic name counter)
+ mPotions . getDynamicSize ( )
2013-12-07 12:17:28 +00:00
+ mArmors . getDynamicSize ( )
+ mBooks . getDynamicSize ( )
+ mClasses . getDynamicSize ( )
+ mClothes . getDynamicSize ( )
+ mEnchants . getDynamicSize ( )
+ mNpcs . getDynamicSize ( )
+ mSpells . getDynamicSize ( )
2014-12-17 00:05:32 +00:00
+ mWeapons . getDynamicSize ( )
+ mCreatureLists . getDynamicSize ( )
+ mItemLists . getDynamicSize ( ) ;
2013-12-07 12:17:28 +00:00
}
2014-04-28 09:29:57 +00:00
void ESMStore : : write ( ESM : : ESMWriter & writer , Loading : : Listener & progress ) const
2013-12-07 12:17:28 +00:00
{
2014-05-10 22:32:22 +00:00
writer . startRecord ( ESM : : REC_DYNA ) ;
writer . startSubRecord ( " COUN " ) ;
writer . writeT ( mDynamicCount ) ;
writer . endRecord ( " COUN " ) ;
writer . endRecord ( ESM : : REC_DYNA ) ;
2014-04-28 09:29:57 +00:00
mPotions . write ( writer , progress ) ;
mArmors . write ( writer , progress ) ;
mBooks . write ( writer , progress ) ;
mClasses . write ( writer , progress ) ;
mClothes . write ( writer , progress ) ;
mEnchants . write ( writer , progress ) ;
mSpells . write ( writer , progress ) ;
mWeapons . write ( writer , progress ) ;
mNpcs . write ( writer , progress ) ;
2014-12-17 00:05:32 +00:00
mItemLists . write ( writer , progress ) ;
mCreatureLists . write ( writer , progress ) ;
2013-12-07 12:17:28 +00:00
}
2015-01-22 18:04:59 +00:00
bool ESMStore : : readRecord ( ESM : : ESMReader & reader , uint32_t type )
2013-12-07 12:17:28 +00:00
{
switch ( type )
{
case ESM : : REC_ALCH :
case ESM : : REC_ARMO :
case ESM : : REC_BOOK :
case ESM : : REC_CLAS :
case ESM : : REC_CLOT :
case ESM : : REC_ENCH :
case ESM : : REC_SPEL :
case ESM : : REC_WEAP :
2014-01-18 14:06:58 +00:00
case ESM : : REC_NPC_ :
2014-12-17 00:05:32 +00:00
case ESM : : REC_LEVI :
case ESM : : REC_LEVC :
2013-12-07 12:17:28 +00:00
2015-01-08 15:17:13 +00:00
{
2015-12-11 14:55:45 +00:00
mStores [ type ] - > read ( reader ) ;
2015-01-08 15:17:13 +00:00
}
2014-01-18 14:06:58 +00:00
if ( type = = ESM : : REC_NPC_ )
{
// NPC record will always be last and we know that there can be only one
2015-12-11 14:55:45 +00:00
// dynamic NPC record (player) -> We are done here with dynamic record loading
2014-01-18 14:06:58 +00:00
setUp ( ) ;
const ESM : : NPC * player = mNpcs . find ( " player " ) ;
if ( ! mRaces . find ( player - > mRace ) | |
! mClasses . find ( player - > mClass ) )
2014-05-12 19:04:02 +00:00
throw std : : runtime_error ( " Invalid player record (race or class unavailable " ) ;
2014-01-18 14:06:58 +00:00
}
2013-12-07 12:17:28 +00:00
return true ;
2014-05-10 22:32:22 +00:00
case ESM : : REC_DYNA :
reader . getSubNameIs ( " COUN " ) ;
reader . getHT ( mDynamicCount ) ;
return true ;
2013-12-07 12:17:28 +00:00
default :
return false ;
}
}
2012-10-01 15:17:04 +00:00
} // end namespace