/** * @file loadsave.cpp * * Implementation of save game functionality. */ #include "loadsave.h" #include #include #include #include "automap.h" #include "codec.h" #include "control.h" #include "cursor.h" #include "dead.h" #include "doom.h" #include "engine.h" #include "engine/point.hpp" #include "engine/random.hpp" #include "init.h" #include "inv.h" #include "lighting.h" #include "missiles.h" #include "mpqapi.h" #include "pfile.h" #include "stores.h" #include "utils/endian.hpp" #include "utils/language.h" namespace devilution { bool gbIsHellfireSaveGame; uint8_t giNumberOfLevels; namespace { uint8_t giNumberQuests; uint8_t giNumberOfSmithPremiumItems; template T SwapLE(T in) { switch (sizeof(T)) { case 2: return SDL_SwapLE16(in); case 4: return SDL_SwapLE32(in); case 8: return SDL_SwapLE64(in); default: return in; } } template T SwapBE(T in) { switch (sizeof(T)) { case 2: return SDL_SwapBE16(in); case 4: return SDL_SwapBE32(in); case 8: return SDL_SwapBE64(in); default: return in; } } class LoadHelper { std::unique_ptr m_buffer_; size_t m_cur_ = 0; size_t m_size_; template T Next() { const auto size = sizeof(T); if (!IsValid(size)) return 0; T value; memcpy(&value, &m_buffer_[m_cur_], size); m_cur_ += size; return value; } public: LoadHelper(const char *szFileName) { m_buffer_ = pfile_read(szFileName, &m_size_); } bool IsValid(size_t size = 1) { return m_buffer_ != nullptr && m_size_ >= (m_cur_ + size); } template constexpr void Skip() { Skip(sizeof(T)); } void Skip(size_t size) { m_cur_ += size; } void NextBytes(void *bytes, size_t size) { if (!IsValid(size)) return; memcpy(bytes, &m_buffer_[m_cur_], size); m_cur_ += size; } template T NextLE() { return SwapLE(Next()); } template T NextBE() { return SwapBE(Next()); } bool NextBool8() { return Next() != 0; } bool NextBool32() { return Next() != 0; } }; class SaveHelper { const char *m_szFileName_; std::unique_ptr m_buffer_; size_t m_cur_ = 0; size_t m_capacity_; public: SaveHelper(const char *szFileName, size_t bufferLen) : m_szFileName_(szFileName) , m_buffer_(new byte[codec_get_encoded_len(bufferLen)]) , m_capacity_(bufferLen) { } bool IsValid(size_t len = 1) { return m_buffer_ != nullptr && m_capacity_ >= (m_cur_ + len); } template constexpr void Skip() { Skip(sizeof(T)); } void Skip(size_t len) { std::memset(&m_buffer_[m_cur_], 0, len); m_cur_ += len; } void WriteBytes(const void *bytes, size_t len) { if (!IsValid(len)) return; memcpy(&m_buffer_[m_cur_], bytes, len); m_cur_ += len; } template void WriteLE(T value) { value = SwapLE(value); WriteBytes(&value, sizeof(value)); } template void WriteBE(T value) { value = SwapBE(value); WriteBytes(&value, sizeof(value)); } ~SaveHelper() { const auto encodedLen = codec_get_encoded_len(m_cur_); const char *const password = pfile_get_password(); codec_encode(m_buffer_.get(), m_cur_, encodedLen, password); mpqapi_write_file(m_szFileName_, m_buffer_.get(), encodedLen); } }; void LoadItemData(LoadHelper *file, ItemStruct *pItem) { pItem->_iSeed = file->NextLE(); pItem->_iCreateInfo = file->NextLE(); file->Skip(2); // Alignment pItem->_itype = static_cast(file->NextLE()); pItem->position.x = file->NextLE(); pItem->position.y = file->NextLE(); pItem->_iAnimFlag = file->NextBool32(); file->Skip(4); // Skip pointer _iAnimData pItem->AnimInfo = {}; pItem->AnimInfo.NumberOfFrames = file->NextLE(); pItem->AnimInfo.CurrentFrame = file->NextLE(); file->Skip(8); // Skip _iAnimWidth and _iAnimWidth2 file->Skip(4); // Unused since 1.02 pItem->_iSelFlag = file->NextLE(); file->Skip(3); // Alignment pItem->_iPostDraw = file->NextBool32(); pItem->_iIdentified = file->NextBool32(); pItem->_iMagical = static_cast(file->NextLE()); file->NextBytes(pItem->_iName, 64); file->NextBytes(pItem->_iIName, 64); pItem->_iLoc = static_cast(file->NextLE()); pItem->_iClass = static_cast(file->NextLE()); file->Skip(1); // Alignment pItem->_iCurs = file->NextLE(); pItem->_ivalue = file->NextLE(); pItem->_iIvalue = file->NextLE(); pItem->_iMinDam = file->NextLE(); pItem->_iMaxDam = file->NextLE(); pItem->_iAC = file->NextLE(); pItem->_iFlags = file->NextLE(); pItem->_iMiscId = static_cast(file->NextLE()); pItem->_iSpell = static_cast(file->NextLE()); pItem->_iCharges = file->NextLE(); pItem->_iMaxCharges = file->NextLE(); pItem->_iDurability = file->NextLE(); pItem->_iMaxDur = file->NextLE(); pItem->_iPLDam = file->NextLE(); pItem->_iPLToHit = file->NextLE(); pItem->_iPLAC = file->NextLE(); pItem->_iPLStr = file->NextLE(); pItem->_iPLMag = file->NextLE(); pItem->_iPLDex = file->NextLE(); pItem->_iPLVit = file->NextLE(); pItem->_iPLFR = file->NextLE(); pItem->_iPLLR = file->NextLE(); pItem->_iPLMR = file->NextLE(); pItem->_iPLMana = file->NextLE(); pItem->_iPLHP = file->NextLE(); pItem->_iPLDamMod = file->NextLE(); pItem->_iPLGetHit = file->NextLE(); pItem->_iPLLight = file->NextLE(); pItem->_iSplLvlAdd = file->NextLE(); pItem->_iRequest = file->NextBool8(); file->Skip(2); // Alignment pItem->_iUid = file->NextLE(); pItem->_iFMinDam = file->NextLE(); pItem->_iFMaxDam = file->NextLE(); pItem->_iLMinDam = file->NextLE(); pItem->_iLMaxDam = file->NextLE(); pItem->_iPLEnAc = file->NextLE(); pItem->_iPrePower = static_cast(file->NextLE()); pItem->_iSufPower = static_cast(file->NextLE()); file->Skip(2); // Alignment pItem->_iVAdd1 = file->NextLE(); pItem->_iVMult1 = file->NextLE(); pItem->_iVAdd2 = file->NextLE(); pItem->_iVMult2 = file->NextLE(); pItem->_iMinStr = file->NextLE(); pItem->_iMinMag = file->NextLE(); pItem->_iMinDex = file->NextLE(); file->Skip(1); // Alignment pItem->_iStatFlag = file->NextBool32(); pItem->IDidx = static_cast<_item_indexes>(file->NextLE()); if (gbIsSpawn) { pItem->IDidx = RemapItemIdxFromSpawn(pItem->IDidx); } if (!gbIsHellfireSaveGame) { pItem->IDidx = RemapItemIdxFromDiablo(pItem->IDidx); } pItem->dwBuff = file->NextLE(); if (gbIsHellfireSaveGame) pItem->_iDamAcFlags = file->NextLE(); else pItem->_iDamAcFlags = 0; RemoveInvalidItem(pItem); } void LoadItems(LoadHelper *file, const int n, ItemStruct *pItem) { for (int i = 0; i < n; i++) { LoadItemData(file, &pItem[i]); } } void LoadPlayer(LoadHelper *file, int p) { auto &player = Players[p]; player._pmode = static_cast(file->NextLE()); for (int8_t &step : player.walkpath) { step = file->NextLE(); } player.plractive = file->NextBool8(); file->Skip(2); // Alignment player.destAction = static_cast(file->NextLE()); player.destParam1 = file->NextLE(); player.destParam2 = file->NextLE(); player.destParam3 = static_cast(file->NextLE()); player.destParam4 = file->NextLE(); player.plrlevel = file->NextLE(); player.position.tile.x = file->NextLE(); player.position.tile.y = file->NextLE(); player.position.future.x = file->NextLE(); player.position.future.y = file->NextLE(); file->Skip(8); // Skip _ptargx and _ptargy player.position.last.x = file->NextLE(); player.position.last.y = file->NextLE(); player.position.old.x = file->NextLE(); player.position.old.y = file->NextLE(); player.position.offset.deltaX = file->NextLE(); player.position.offset.deltaY = file->NextLE(); player.position.velocity.deltaX = file->NextLE(); player.position.velocity.deltaY = file->NextLE(); player._pdir = static_cast(file->NextLE()); file->Skip(4); // Unused player._pgfxnum = file->NextLE(); file->Skip(4); // Skip pointer pData player.AnimInfo = {}; player.AnimInfo.TicksPerFrame = file->NextLE() + 1; player.AnimInfo.TickCounterOfCurrentFrame = file->NextLE(); player.AnimInfo.NumberOfFrames = file->NextLE(); player.AnimInfo.CurrentFrame = file->NextLE(); file->Skip(4); // Skip _pAnimWidth file->Skip(4); // Skip _pAnimWidth2 file->Skip(4); // Skip _peflag player._plid = file->NextLE(); player._pvid = file->NextLE(); player._pSpell = static_cast(file->NextLE()); player._pSplType = static_cast(file->NextLE()); player._pSplFrom = file->NextLE(); file->Skip(2); // Alignment player._pTSpell = static_cast(file->NextLE()); file->Skip(); // Skip _pTSplType file->Skip(3); // Alignment player._pRSpell = static_cast(file->NextLE()); player._pRSplType = static_cast(file->NextLE()); file->Skip(3); // Alignment player._pSBkSpell = static_cast(file->NextLE()); file->Skip(); // Skip _pSBkSplType for (int8_t &spellLevel : player._pSplLvl) spellLevel = file->NextLE(); file->Skip(7); // Alignment player._pMemSpells = file->NextLE(); player._pAblSpells = file->NextLE(); player._pScrlSpells = file->NextLE(); player._pSpellFlags = file->NextLE(); file->Skip(3); // Alignment for (auto &spell : player._pSplHotKey) spell = static_cast(file->NextLE()); for (auto &spellType : player._pSplTHotKey) spellType = static_cast(file->NextLE()); file->Skip(); // Skip _pwtype player._pBlockFlag = file->NextBool8(); player._pInvincible = file->NextBool8(); player._pLightRad = file->NextLE(); player._pLvlChanging = file->NextBool8(); file->NextBytes(player._pName, PLR_NAME_LEN); player._pClass = static_cast(file->NextLE()); file->Skip(3); // Alignment player._pStrength = file->NextLE(); player._pBaseStr = file->NextLE(); player._pMagic = file->NextLE(); player._pBaseMag = file->NextLE(); player._pDexterity = file->NextLE(); player._pBaseDex = file->NextLE(); player._pVitality = file->NextLE(); player._pBaseVit = file->NextLE(); player._pStatPts = file->NextLE(); player._pDamageMod = file->NextLE(); player._pBaseToBlk = file->NextLE(); if (player._pBaseToBlk == 0) player._pBaseToBlk = BlockBonuses[static_cast(player._pClass)]; player._pHPBase = file->NextLE(); player._pMaxHPBase = file->NextLE(); player._pHitPoints = file->NextLE(); player._pMaxHP = file->NextLE(); file->Skip(sizeof(int32_t)); // Skip _pHPPer - always derived from hp and maxHP. player._pManaBase = file->NextLE(); player._pMaxManaBase = file->NextLE(); player._pMana = file->NextLE(); player._pMaxMana = file->NextLE(); file->Skip(sizeof(int32_t)); // Skip _pManaPer - always derived from mana and maxMana player._pLevel = file->NextLE(); player._pMaxLvl = file->NextLE(); file->Skip(2); // Alignment player._pExperience = file->NextLE(); file->Skip(); // Skip _pMaxExp - unused player._pNextExper = file->NextLE(); // This can be calculated based on pLevel (which in turn could be calculated based on pExperience) player._pArmorClass = file->NextLE(); player._pMagResist = file->NextLE(); player._pFireResist = file->NextLE(); player._pLghtResist = file->NextLE(); player._pGold = file->NextLE(); player._pInfraFlag = file->NextBool32(); player.position.temp.x = file->NextLE(); player.position.temp.y = file->NextLE(); player.tempDirection = static_cast(file->NextLE()); player._pVar4 = file->NextLE(); player._pVar5 = file->NextLE(); player.position.offset2.deltaX = file->NextLE(); player.position.offset2.deltaY = file->NextLE(); file->Skip(4); // Skip actionFrame for (uint8_t i = 0; i < giNumberOfLevels; i++) player._pLvlVisited[i] = file->NextBool8(); for (uint8_t i = 0; i < giNumberOfLevels; i++) player._pSLvlVisited[i] = file->NextBool8(); file->Skip(2); // Alignment file->Skip(4); // skip _pGFXLoad file->Skip(4 * 8); // Skip pointers _pNAnim player._pNFrames = file->NextLE(); file->Skip(4); // skip _pNWidth file->Skip(4 * 8); // Skip pointers _pWAnim player._pWFrames = file->NextLE(); file->Skip(4); // skip _pWWidth file->Skip(4 * 8); // Skip pointers _pAAnim player._pAFrames = file->NextLE(); file->Skip(4); // skip _pAWidth player._pAFNum = file->NextLE(); file->Skip(4 * 8); // Skip pointers _pLAnim file->Skip(4 * 8); // Skip pointers _pFAnim file->Skip(4 * 8); // Skip pointers _pTAnim player._pSFrames = file->NextLE(); file->Skip(4); // skip _pSWidth player._pSFNum = file->NextLE(); file->Skip(4 * 8); // Skip pointers _pHAnim player._pHFrames = file->NextLE(); file->Skip(4); // skip _pHWidth file->Skip(4 * 8); // Skip pointers _pDAnim player._pDFrames = file->NextLE(); file->Skip(4); // skip _pDWidth file->Skip(4 * 8); // Skip pointers _pBAnim player._pBFrames = file->NextLE(); file->Skip(4); // skip _pBWidth LoadItems(file, NUM_INVLOC, player.InvBody); LoadItems(file, NUM_INV_GRID_ELEM, player.InvList); player._pNumInv = file->NextLE(); for (int8_t &cell : player.InvGrid) cell = file->NextLE(); LoadItems(file, MAXBELTITEMS, player.SpdList); LoadItemData(file, &player.HoldItem); player._pIMinDam = file->NextLE(); player._pIMaxDam = file->NextLE(); player._pIAC = file->NextLE(); player._pIBonusDam = file->NextLE(); player._pIBonusToHit = file->NextLE(); player._pIBonusAC = file->NextLE(); player._pIBonusDamMod = file->NextLE(); file->Skip(4); // Alignment player._pISpells = file->NextLE(); player._pIFlags = file->NextLE(); player._pIGetHit = file->NextLE(); player._pISplLvlAdd = file->NextLE(); file->Skip(1); // Unused file->Skip(2); // Alignment player._pISplDur = file->NextLE(); player._pIEnAc = file->NextLE(); player._pIFMinDam = file->NextLE(); player._pIFMaxDam = file->NextLE(); player._pILMinDam = file->NextLE(); player._pILMaxDam = file->NextLE(); player._pOilType = static_cast(file->NextLE()); player.pTownWarps = file->NextLE(); player.pDungMsgs = file->NextLE(); player.pLvlLoad = file->NextLE(); if (gbIsHellfireSaveGame) { player.pDungMsgs2 = file->NextLE(); player.pBattleNet = false; } else { player.pDungMsgs2 = 0; player.pBattleNet = file->NextBool8(); } player.pManaShield = file->NextBool8(); if (gbIsHellfireSaveGame) { player.pOriginalCathedral = file->NextBool8(); } else { file->Skip(1); player.pOriginalCathedral = true; } file->Skip(2); // Available bytes player.wReflections = file->NextLE(); file->Skip(14); // Available bytes player.pDiabloKillLevel = file->NextLE(); player.pDifficulty = static_cast<_difficulty>(file->NextLE()); player.pDamAcFlags = file->NextLE(); file->Skip(20); // Available bytes CalcPlrItemVals(p, false); // Omit pointer _pNData // Omit pointer _pWData // Omit pointer _pAData // Omit pointer _pLData // Omit pointer _pFData // Omit pointer _pTData // Omit pointer _pHData // Omit pointer _pDData // Omit pointer _pBData // Omit pointer pReserved } bool gbSkipSync = false; void LoadMonster(LoadHelper *file, MonsterStruct &monster) { monster._mMTidx = file->NextLE(); monster._mmode = static_cast(file->NextLE()); monster._mgoal = static_cast(file->NextLE()); file->Skip(3); // Alignment monster._mgoalvar1 = file->NextLE(); monster._mgoalvar2 = file->NextLE(); monster._mgoalvar3 = file->NextLE(); file->Skip(4); // Unused monster._pathcount = file->NextLE(); file->Skip(3); // Alignment monster.position.tile.x = file->NextLE(); monster.position.tile.y = file->NextLE(); monster.position.future.x = file->NextLE(); monster.position.future.y = file->NextLE(); monster.position.old.x = file->NextLE(); monster.position.old.y = file->NextLE(); monster.position.offset.deltaX = file->NextLE(); monster.position.offset.deltaY = file->NextLE(); monster.position.velocity.deltaX = file->NextLE(); monster.position.velocity.deltaY = file->NextLE(); monster._mdir = static_cast(file->NextLE()); monster._menemy = file->NextLE(); monster.enemyPosition.x = file->NextLE(); monster.enemyPosition.y = file->NextLE(); file->Skip(2); // Unused file->Skip(4); // Skip pointer _mAnimData monster.AnimInfo = {}; monster.AnimInfo.TicksPerFrame = file->NextLE(); monster.AnimInfo.TickCounterOfCurrentFrame = file->NextLE(); monster.AnimInfo.NumberOfFrames = file->NextLE(); monster.AnimInfo.CurrentFrame = file->NextLE(); file->Skip(4); // Skip _meflag monster._mDelFlag = file->NextBool32(); monster._mVar1 = file->NextLE(); monster._mVar2 = file->NextLE(); monster._mVar3 = file->NextLE(); monster.position.temp.x = file->NextLE(); monster.position.temp.y = file->NextLE(); monster.position.offset2.deltaX = file->NextLE(); monster.position.offset2.deltaY = file->NextLE(); file->Skip(4); // Skip actionFrame monster._mmaxhp = file->NextLE(); monster._mhitpoints = file->NextLE(); monster._mAi = static_cast<_mai_id>(file->NextLE()); monster._mint = file->NextLE(); file->Skip(2); // Alignment monster._mFlags = file->NextLE(); monster._msquelch = file->NextLE(); file->Skip(3); // Alignment file->Skip(4); // Unused monster.position.last.x = file->NextLE(); monster.position.last.y = file->NextLE(); monster._mRndSeed = file->NextLE(); monster._mAISeed = file->NextLE(); file->Skip(4); // Unused monster._uniqtype = file->NextLE(); monster._uniqtrans = file->NextLE(); monster._udeadval = file->NextLE(); monster.mWhoHit = file->NextLE(); monster.mLevel = file->NextLE(); file->Skip(1); // Alignment monster.mExp = file->NextLE(); if ((monster._mFlags & MFLAG_GOLEM) != 0) // Don't skip for golems monster.mHit = file->NextLE(); else file->Skip(1); // Skip mHit as it's already initialized monster.mMinDamage = file->NextLE(); monster.mMaxDamage = file->NextLE(); file->Skip(1); // Skip mHit2 as it's already initialized monster.mMinDamage2 = file->NextLE(); monster.mMaxDamage2 = file->NextLE(); monster.mArmorClass = file->NextLE(); file->Skip(1); // Alignment monster.mMagicRes = file->NextLE(); file->Skip(2); // Alignment monster.mtalkmsg = static_cast<_speech_id>(file->NextLE()); if (monster.mtalkmsg == TEXT_KING1) // Fix original bad mapping of NONE for monsters monster.mtalkmsg = TEXT_NONE; monster.leader = file->NextLE(); monster.leaderRelation = static_cast(file->NextLE()); monster.packsize = file->NextLE(); monster.mlid = file->NextLE(); if (monster.mlid == Players[MyPlayerId]._plid) monster.mlid = NO_LIGHT; // Correct incorect values in old saves // Omit pointer mName; // Omit pointer MType; // Omit pointer MData; if (gbSkipSync) return; SyncMonsterAnim(monster); } void LoadMissile(LoadHelper *file, int i) { MissileStruct *pMissile = &Missiles[i]; pMissile->_mitype = file->NextLE(); pMissile->position.tile.x = file->NextLE(); pMissile->position.tile.y = file->NextLE(); pMissile->position.offset.deltaX = file->NextLE(); pMissile->position.offset.deltaY = file->NextLE(); pMissile->position.velocity.deltaX = file->NextLE(); pMissile->position.velocity.deltaY = file->NextLE(); pMissile->position.start.x = file->NextLE(); pMissile->position.start.y = file->NextLE(); pMissile->position.traveled.deltaX = file->NextLE(); pMissile->position.traveled.deltaY = file->NextLE(); pMissile->_mimfnum = file->NextLE(); pMissile->_mispllvl = file->NextLE(); pMissile->_miDelFlag = file->NextBool32(); pMissile->_miAnimType = file->NextLE(); file->Skip(3); // Alignment pMissile->_miAnimFlags = static_cast(file->NextLE()); file->Skip(4); // Skip pointer _miAnimData pMissile->_miAnimDelay = file->NextLE(); pMissile->_miAnimLen = file->NextLE(); pMissile->_miAnimWidth = file->NextLE(); pMissile->_miAnimWidth2 = file->NextLE(); pMissile->_miAnimCnt = file->NextLE(); pMissile->_miAnimAdd = file->NextLE(); pMissile->_miAnimFrame = file->NextLE(); pMissile->_miDrawFlag = file->NextBool32(); pMissile->_miLightFlag = file->NextBool32(); pMissile->_miPreFlag = file->NextBool32(); pMissile->_miUniqTrans = file->NextLE(); pMissile->_mirange = file->NextLE(); pMissile->_misource = file->NextLE(); pMissile->_micaster = file->NextLE(); pMissile->_midam = file->NextLE(); pMissile->_miHitFlag = file->NextBool32(); pMissile->_midist = file->NextLE(); pMissile->_mlid = file->NextLE(); pMissile->_mirnd = file->NextLE(); pMissile->_miVar1 = file->NextLE(); pMissile->_miVar2 = file->NextLE(); pMissile->_miVar3 = file->NextLE(); pMissile->_miVar4 = file->NextLE(); pMissile->_miVar5 = file->NextLE(); pMissile->_miVar6 = file->NextLE(); pMissile->_miVar7 = file->NextLE(); pMissile->limitReached = file->NextBool32(); } void LoadObject(LoadHelper *file, int i) { ObjectStruct *pObject = &Objects[i]; pObject->_otype = static_cast<_object_id>(file->NextLE()); pObject->position.x = file->NextLE(); pObject->position.y = file->NextLE(); pObject->_oLight = file->NextBool32(); pObject->_oAnimFlag = file->NextLE(); file->Skip(4); // Skip pointer _oAnimData pObject->_oAnimDelay = file->NextLE(); pObject->_oAnimCnt = file->NextLE(); pObject->_oAnimLen = file->NextLE(); pObject->_oAnimFrame = file->NextLE(); pObject->_oAnimWidth = file->NextLE(); file->Skip(4); // Skip _oAnimWidth2 pObject->_oDelFlag = file->NextBool32(); pObject->_oBreak = file->NextLE(); file->Skip(3); // Alignment pObject->_oSolidFlag = file->NextBool32(); pObject->_oMissFlag = file->NextBool32(); pObject->_oSelFlag = file->NextLE(); file->Skip(3); // Alignment pObject->_oPreFlag = file->NextBool32(); pObject->_oTrapFlag = file->NextBool32(); pObject->_oDoorFlag = file->NextBool32(); pObject->_olid = file->NextLE(); pObject->_oRndSeed = file->NextLE(); pObject->_oVar1 = file->NextLE(); pObject->_oVar2 = file->NextLE(); pObject->_oVar3 = file->NextLE(); pObject->_oVar4 = file->NextLE(); pObject->_oVar5 = file->NextLE(); pObject->_oVar6 = file->NextLE(); pObject->bookMessage = static_cast<_speech_id>(file->NextLE()); pObject->_oVar8 = file->NextLE(); } void LoadItem(LoadHelper *file, int i) { LoadItemData(file, &Items[i]); GetItemFrm(i); } void LoadPremium(LoadHelper *file, int i) { LoadItemData(file, &premiumitems[i]); } void LoadQuest(LoadHelper *file, int i) { auto &quest = Quests[i]; quest._qlevel = file->NextLE(); file->Skip(); // _qtype, identical to _qidx quest._qactive = static_cast(file->NextLE()); quest._qlvltype = static_cast(file->NextLE()); quest.position.x = file->NextLE(); quest.position.y = file->NextLE(); quest._qslvl = static_cast<_setlevels>(file->NextLE()); quest._qidx = static_cast(file->NextLE()); if (gbIsHellfireSaveGame) { file->Skip(2); // Alignment quest._qmsg = static_cast<_speech_id>(file->NextLE()); } else { quest._qmsg = static_cast<_speech_id>(file->NextLE()); } quest._qvar1 = file->NextLE(); quest._qvar2 = file->NextLE(); file->Skip(2); // Alignment if (!gbIsHellfireSaveGame) file->Skip(1); // Alignment quest._qlog = file->NextBool32(); ReturnLvlX = file->NextBE(); ReturnLvlY = file->NextBE(); ReturnLevel = file->NextBE(); ReturnLevelType = static_cast(file->NextBE()); file->Skip(sizeof(int32_t)); // Skip DoomQuestState } void LoadLighting(LoadHelper *file, LightStruct *pLight) { pLight->position.tile.x = file->NextLE(); pLight->position.tile.y = file->NextLE(); pLight->_lradius = file->NextLE(); pLight->_lid = file->NextLE(); pLight->_ldel = file->NextBool32(); pLight->_lunflag = file->NextBool32(); file->Skip(4); // Unused pLight->position.old.x = file->NextLE(); pLight->position.old.y = file->NextLE(); pLight->oldRadius = file->NextLE(); pLight->position.offset.x = file->NextLE(); pLight->position.offset.y = file->NextLE(); pLight->_lflags = file->NextBool32(); } void LoadPortal(LoadHelper *file, int i) { PortalStruct *pPortal = &Portals[i]; pPortal->open = file->NextBool32(); pPortal->position.x = file->NextLE(); pPortal->position.y = file->NextLE(); pPortal->level = file->NextLE(); pPortal->ltype = static_cast(file->NextLE()); pPortal->setlvl = file->NextBool32(); } void ConvertLevels() { // Backup current level state bool tmpSetlevel = setlevel; _setlevels tmpSetlvlnum = setlvlnum; int tmpCurrlevel = currlevel; dungeon_type tmpLeveltype = leveltype; gbSkipSync = true; setlevel = false; // Convert regular levels for (int i = 0; i < giNumberOfLevels; i++) { currlevel = i; if (!LevelFileExists()) continue; leveltype = gnLevelTypeTbl[i]; LoadLevel(); SaveLevel(); } setlevel = true; // Convert quest levels for (auto &quest : Quests) { if (quest._qactive == QUEST_NOTAVAIL) { continue; } leveltype = quest._qlvltype; if (leveltype == DTYPE_NONE) { continue; } setlvlnum = quest._qslvl; if (!LevelFileExists()) continue; LoadLevel(); SaveLevel(); } gbSkipSync = false; // Restor current level state setlevel = tmpSetlevel; setlvlnum = tmpSetlvlnum; currlevel = tmpCurrlevel; leveltype = tmpLeveltype; } void LoadMatchingItems(LoadHelper *file, const int n, ItemStruct *pItem) { ItemStruct tempItem; for (int i = 0; i < n; i++) { LoadItemData(file, &tempItem); if (pItem[i].isEmpty() || tempItem.isEmpty()) continue; if (pItem[i]._iSeed != tempItem._iSeed) continue; pItem[i] = tempItem; } } void RemoveEmptyLevelItems() { for (int i = ActiveItemCount; i > 0; i--) { int ii = ActiveItems[i]; if (Items[ii].isEmpty()) { dItem[Items[ii].position.x][Items[ii].position.y] = 0; DeleteItem(ii, i); } } } void SaveItem(SaveHelper *file, ItemStruct *pItem) { auto idx = pItem->IDidx; if (!gbIsHellfire) idx = RemapItemIdxToDiablo(idx); if (gbIsSpawn) idx = RemapItemIdxToSpawn(idx); int iType = pItem->_itype; if (idx == -1) { idx = _item_indexes::IDI_GOLD; iType = ITYPE_NONE; } file->WriteLE(pItem->_iSeed); file->WriteLE(pItem->_iCreateInfo); file->Skip(2); // Alignment file->WriteLE(iType); file->WriteLE(pItem->position.x); file->WriteLE(pItem->position.y); file->WriteLE(pItem->_iAnimFlag ? 1 : 0); file->Skip(4); // Skip pointer _iAnimData file->WriteLE(pItem->AnimInfo.NumberOfFrames); file->WriteLE(pItem->AnimInfo.CurrentFrame); // write _iAnimWidth for vanilla compatibility file->WriteLE(ItemAnimWidth); // write _iAnimWidth2 for vanilla compatibility file->WriteLE(CalculateWidth2(ItemAnimWidth)); file->Skip(); // _delFlag, unused since 1.02 file->WriteLE(pItem->_iSelFlag); file->Skip(3); // Alignment file->WriteLE(pItem->_iPostDraw ? 1 : 0); file->WriteLE(pItem->_iIdentified ? 1 : 0); file->WriteLE(pItem->_iMagical); file->WriteBytes(pItem->_iName, 64); file->WriteBytes(pItem->_iIName, 64); file->WriteLE(pItem->_iLoc); file->WriteLE(pItem->_iClass); file->Skip(1); // Alignment file->WriteLE(pItem->_iCurs); file->WriteLE(pItem->_ivalue); file->WriteLE(pItem->_iIvalue); file->WriteLE(pItem->_iMinDam); file->WriteLE(pItem->_iMaxDam); file->WriteLE(pItem->_iAC); file->WriteLE(pItem->_iFlags); file->WriteLE(pItem->_iMiscId); file->WriteLE(pItem->_iSpell); file->WriteLE(pItem->_iCharges); file->WriteLE(pItem->_iMaxCharges); file->WriteLE(pItem->_iDurability); file->WriteLE(pItem->_iMaxDur); file->WriteLE(pItem->_iPLDam); file->WriteLE(pItem->_iPLToHit); file->WriteLE(pItem->_iPLAC); file->WriteLE(pItem->_iPLStr); file->WriteLE(pItem->_iPLMag); file->WriteLE(pItem->_iPLDex); file->WriteLE(pItem->_iPLVit); file->WriteLE(pItem->_iPLFR); file->WriteLE(pItem->_iPLLR); file->WriteLE(pItem->_iPLMR); file->WriteLE(pItem->_iPLMana); file->WriteLE(pItem->_iPLHP); file->WriteLE(pItem->_iPLDamMod); file->WriteLE(pItem->_iPLGetHit); file->WriteLE(pItem->_iPLLight); file->WriteLE(pItem->_iSplLvlAdd); file->WriteLE(pItem->_iRequest ? 1 : 0); file->Skip(2); // Alignment file->WriteLE(pItem->_iUid); file->WriteLE(pItem->_iFMinDam); file->WriteLE(pItem->_iFMaxDam); file->WriteLE(pItem->_iLMinDam); file->WriteLE(pItem->_iLMaxDam); file->WriteLE(pItem->_iPLEnAc); file->WriteLE(pItem->_iPrePower); file->WriteLE(pItem->_iSufPower); file->Skip(2); // Alignment file->WriteLE(pItem->_iVAdd1); file->WriteLE(pItem->_iVMult1); file->WriteLE(pItem->_iVAdd2); file->WriteLE(pItem->_iVMult2); file->WriteLE(pItem->_iMinStr); file->WriteLE(pItem->_iMinMag); file->WriteLE(pItem->_iMinDex); file->Skip(1); // Alignment file->WriteLE(pItem->_iStatFlag ? 1 : 0); file->WriteLE(idx); file->WriteLE(pItem->dwBuff); if (gbIsHellfire) file->WriteLE(pItem->_iDamAcFlags); } void SaveItems(SaveHelper *file, ItemStruct *pItem, const int n) { for (int i = 0; i < n; i++) { SaveItem(file, &pItem[i]); } } void SavePlayer(SaveHelper *file, int p) { auto &player = Players[p]; file->WriteLE(player._pmode); for (int8_t step : player.walkpath) file->WriteLE(step); file->WriteLE(player.plractive ? 1 : 0); file->Skip(2); // Alignment file->WriteLE(player.destAction); file->WriteLE(player.destParam1); file->WriteLE(player.destParam2); file->WriteLE(player.destParam3); file->WriteLE(player.destParam4); file->WriteLE(player.plrlevel); file->WriteLE(player.position.tile.x); file->WriteLE(player.position.tile.y); file->WriteLE(player.position.future.x); file->WriteLE(player.position.future.y); // For backwards compatibility const Point target = player.GetTargetPosition(); file->WriteLE(target.x); file->WriteLE(target.y); file->WriteLE(player.position.last.x); file->WriteLE(player.position.last.y); file->WriteLE(player.position.old.x); file->WriteLE(player.position.old.y); file->WriteLE(player.position.offset.deltaX); file->WriteLE(player.position.offset.deltaY); file->WriteLE(player.position.velocity.deltaX); file->WriteLE(player.position.velocity.deltaY); file->WriteLE(player._pdir); file->Skip(4); // Unused file->WriteLE(player._pgfxnum); file->Skip(4); // Skip pointer _pAnimData file->WriteLE(std::max(0, player.AnimInfo.TicksPerFrame - 1)); file->WriteLE(player.AnimInfo.TickCounterOfCurrentFrame); file->WriteLE(player.AnimInfo.NumberOfFrames); file->WriteLE(player.AnimInfo.CurrentFrame); // write _pAnimWidth for vanilla compatibility int animWidth = player.AnimInfo.pCelSprite == nullptr ? 96 : player.AnimInfo.pCelSprite->Width(); file->WriteLE(animWidth); // write _pAnimWidth2 for vanilla compatibility file->WriteLE(CalculateWidth2(animWidth)); file->Skip(); // Skip _peflag file->WriteLE(player._plid); file->WriteLE(player._pvid); file->WriteLE(player._pSpell); file->WriteLE(player._pSplType); file->WriteLE(player._pSplFrom); file->Skip(2); // Alignment file->WriteLE(player._pTSpell); file->Skip(); // Skip _pTSplType file->Skip(3); // Alignment file->WriteLE(player._pRSpell); file->WriteLE(player._pRSplType); file->Skip(3); // Alignment file->WriteLE(player._pSBkSpell); file->Skip(); // Skip _pSBkSplType for (int8_t spellLevel : player._pSplLvl) file->WriteLE(spellLevel); file->Skip(7); // Alignment file->WriteLE(player._pMemSpells); file->WriteLE(player._pAblSpells); file->WriteLE(player._pScrlSpells); file->WriteLE(player._pSpellFlags); file->Skip(3); // Alignment for (auto &spellId : player._pSplHotKey) file->WriteLE(spellId); for (auto &spellType : player._pSplTHotKey) file->WriteLE(spellType); file->WriteLE(player.UsesRangedWeapon() ? 1 : 0); file->WriteLE(player._pBlockFlag ? 1 : 0); file->WriteLE(player._pInvincible ? 1 : 0); file->WriteLE(player._pLightRad); file->WriteLE(player._pLvlChanging ? 1 : 0); file->WriteBytes(player._pName, PLR_NAME_LEN); file->WriteLE(static_cast(player._pClass)); file->Skip(3); // Alignment file->WriteLE(player._pStrength); file->WriteLE(player._pBaseStr); file->WriteLE(player._pMagic); file->WriteLE(player._pBaseMag); file->WriteLE(player._pDexterity); file->WriteLE(player._pBaseDex); file->WriteLE(player._pVitality); file->WriteLE(player._pBaseVit); file->WriteLE(player._pStatPts); file->WriteLE(player._pDamageMod); file->WriteLE(player._pBaseToBlk); file->WriteLE(player._pHPBase); file->WriteLE(player._pMaxHPBase); file->WriteLE(player._pHitPoints); file->WriteLE(player._pMaxHP); file->Skip(); // Skip _pHPPer file->WriteLE(player._pManaBase); file->WriteLE(player._pMaxManaBase); file->WriteLE(player._pMana); file->WriteLE(player._pMaxMana); file->Skip(); // Skip _pManaPer file->WriteLE(player._pLevel); file->WriteLE(player._pMaxLvl); file->Skip(2); // Alignment file->WriteLE(player._pExperience); file->Skip(); // Skip _pMaxExp file->WriteLE(player._pNextExper); file->WriteLE(player._pArmorClass); file->WriteLE(player._pMagResist); file->WriteLE(player._pFireResist); file->WriteLE(player._pLghtResist); file->WriteLE(player._pGold); file->WriteLE(player._pInfraFlag ? 1 : 0); file->WriteLE(player.position.temp.x); file->WriteLE(player.position.temp.y); file->WriteLE(player.tempDirection); file->WriteLE(player._pVar4); file->WriteLE(player._pVar5); file->WriteLE(player.position.offset2.deltaX); file->WriteLE(player.position.offset2.deltaY); file->Skip(); // Skip _pVar8 for (uint8_t i = 0; i < giNumberOfLevels; i++) file->WriteLE(player._pLvlVisited[i] ? 1 : 0); for (uint8_t i = 0; i < giNumberOfLevels; i++) file->WriteLE(player._pSLvlVisited[i] ? 1 : 0); // only 10 used file->Skip(2); // Alignment file->Skip(); // Skip _pGFXLoad file->Skip(4 * 8); // Skip pointers _pNAnim file->WriteLE(player._pNFrames); file->Skip(4); // Skip _pNWidth file->Skip(4 * 8); // Skip pointers _pWAnim file->WriteLE(player._pWFrames); file->Skip(4); // Skip _pWWidth file->Skip(4 * 8); // Skip pointers _pAAnim file->WriteLE(player._pAFrames); file->Skip(4); // Skip _pAWidth file->WriteLE(player._pAFNum); file->Skip(4 * 8); // Skip pointers _pLAnim file->Skip(4 * 8); // Skip pointers _pFAnim file->Skip(4 * 8); // Skip pointers _pTAnim file->WriteLE(player._pSFrames); file->Skip(4); // Skip _pSWidth file->WriteLE(player._pSFNum); file->Skip(4 * 8); // Skip pointers _pHAnim file->WriteLE(player._pHFrames); file->Skip(4); // Skip _pHWidth file->Skip(4 * 8); // Skip pointers _pDAnim file->WriteLE(player._pDFrames); file->Skip(4); // Skip _pDWidth file->Skip(4 * 8); // Skip pointers _pBAnim file->WriteLE(player._pBFrames); file->Skip(4); // Skip _pBWidth SaveItems(file, player.InvBody, NUM_INVLOC); SaveItems(file, player.InvList, NUM_INV_GRID_ELEM); file->WriteLE(player._pNumInv); for (int8_t cell : player.InvGrid) file->WriteLE(cell); SaveItems(file, player.SpdList, MAXBELTITEMS); SaveItem(file, &player.HoldItem); file->WriteLE(player._pIMinDam); file->WriteLE(player._pIMaxDam); file->WriteLE(player._pIAC); file->WriteLE(player._pIBonusDam); file->WriteLE(player._pIBonusToHit); file->WriteLE(player._pIBonusAC); file->WriteLE(player._pIBonusDamMod); file->Skip(4); // Alignment file->WriteLE(player._pISpells); file->WriteLE(player._pIFlags); file->WriteLE(player._pIGetHit); file->WriteLE(player._pISplLvlAdd); file->Skip(); // Skip _pISplCost file->Skip(2); // Alignment file->WriteLE(player._pISplDur); file->WriteLE(player._pIEnAc); file->WriteLE(player._pIFMinDam); file->WriteLE(player._pIFMaxDam); file->WriteLE(player._pILMinDam); file->WriteLE(player._pILMaxDam); file->WriteLE(player._pOilType); file->WriteLE(player.pTownWarps); file->WriteLE(player.pDungMsgs); file->WriteLE(player.pLvlLoad); if (gbIsHellfire) file->WriteLE(player.pDungMsgs2); else file->WriteLE(player.pBattleNet ? 1 : 0); file->WriteLE(player.pManaShield ? 1 : 0); file->WriteLE(player.pOriginalCathedral ? 1 : 0); file->Skip(2); // Available bytes file->WriteLE(player.wReflections); file->Skip(14); // Available bytes file->WriteLE(player.pDiabloKillLevel); file->WriteLE(player.pDifficulty); file->WriteLE(player.pDamAcFlags); file->Skip(20); // Available bytes // Omit pointer _pNData // Omit pointer _pWData // Omit pointer _pAData // Omit pointer _pLData // Omit pointer _pFData // Omit pointer _pTData // Omit pointer _pHData // Omit pointer _pDData // Omit pointer _pBData // Omit pointer pReserved } void SaveMonster(SaveHelper *file, MonsterStruct &monster) { file->WriteLE(monster._mMTidx); file->WriteLE(monster._mmode); file->WriteLE(monster._mgoal); file->Skip(3); // Alignment file->WriteLE(monster._mgoalvar1); file->WriteLE(monster._mgoalvar2); file->WriteLE(monster._mgoalvar3); file->Skip(4); // Unused file->WriteLE(monster._pathcount); file->Skip(3); // Alignment file->WriteLE(monster.position.tile.x); file->WriteLE(monster.position.tile.y); file->WriteLE(monster.position.future.x); file->WriteLE(monster.position.future.y); file->WriteLE(monster.position.old.x); file->WriteLE(monster.position.old.y); file->WriteLE(monster.position.offset.deltaX); file->WriteLE(monster.position.offset.deltaY); file->WriteLE(monster.position.velocity.deltaX); file->WriteLE(monster.position.velocity.deltaY); file->WriteLE(monster._mdir); file->WriteLE(monster._menemy); file->WriteLE(monster.enemyPosition.x); file->WriteLE(monster.enemyPosition.y); file->Skip(2); // Unused file->Skip(4); // Skip pointer _mAnimData file->WriteLE(monster.AnimInfo.TicksPerFrame); file->WriteLE(monster.AnimInfo.TickCounterOfCurrentFrame); file->WriteLE(monster.AnimInfo.NumberOfFrames); file->WriteLE(monster.AnimInfo.CurrentFrame); file->Skip(); // Skip _meflag file->WriteLE(monster._mDelFlag ? 1 : 0); file->WriteLE(monster._mVar1); file->WriteLE(monster._mVar2); file->WriteLE(monster._mVar3); file->WriteLE(monster.position.temp.x); file->WriteLE(monster.position.temp.y); file->WriteLE(monster.position.offset2.deltaX); file->WriteLE(monster.position.offset2.deltaY); file->Skip(); // Skip _mVar8 file->WriteLE(monster._mmaxhp); file->WriteLE(monster._mhitpoints); file->WriteLE(monster._mAi); file->WriteLE(monster._mint); file->Skip(2); // Alignment file->WriteLE(monster._mFlags); file->WriteLE(monster._msquelch); file->Skip(3); // Alignment file->Skip(4); // Unused file->WriteLE(monster.position.last.x); file->WriteLE(monster.position.last.y); file->WriteLE(monster._mRndSeed); file->WriteLE(monster._mAISeed); file->Skip(4); // Unused file->WriteLE(monster._uniqtype); file->WriteLE(monster._uniqtrans); file->WriteLE(monster._udeadval); file->WriteLE(monster.mWhoHit); file->WriteLE(monster.mLevel); file->Skip(1); // Alignment file->WriteLE(monster.mExp); file->WriteLE(std::min(monster.mHit, std::numeric_limits::max())); // For backwards compatibility file->WriteLE(monster.mMinDamage); file->WriteLE(monster.mMaxDamage); file->WriteLE(std::min(monster.mHit2, std::numeric_limits::max())); // For backwards compatibility file->WriteLE(monster.mMinDamage2); file->WriteLE(monster.mMaxDamage2); file->WriteLE(monster.mArmorClass); file->Skip(1); // Alignment file->WriteLE(monster.mMagicRes); file->Skip(2); // Alignment file->WriteLE(monster.mtalkmsg == TEXT_NONE ? 0 : monster.mtalkmsg); // Replicate original bad mapping of none for monsters file->WriteLE(monster.leader); file->WriteLE(static_cast(monster.leaderRelation)); file->WriteLE(monster.packsize); file->WriteLE(monster.mlid); // Omit pointer mName; // Omit pointer MType; // Omit pointer MData; } void SaveMissile(SaveHelper *file, int i) { MissileStruct *pMissile = &Missiles[i]; file->WriteLE(pMissile->_mitype); file->WriteLE(pMissile->position.tile.x); file->WriteLE(pMissile->position.tile.y); file->WriteLE(pMissile->position.offset.deltaX); file->WriteLE(pMissile->position.offset.deltaY); file->WriteLE(pMissile->position.velocity.deltaX); file->WriteLE(pMissile->position.velocity.deltaY); file->WriteLE(pMissile->position.start.x); file->WriteLE(pMissile->position.start.y); file->WriteLE(pMissile->position.traveled.deltaX); file->WriteLE(pMissile->position.traveled.deltaY); file->WriteLE(pMissile->_mimfnum); file->WriteLE(pMissile->_mispllvl); file->WriteLE(pMissile->_miDelFlag ? 1 : 0); file->WriteLE(pMissile->_miAnimType); file->Skip(3); // Alignment file->WriteLE(static_cast(pMissile->_miAnimFlags)); file->Skip(4); // Skip pointer _miAnimData file->WriteLE(pMissile->_miAnimDelay); file->WriteLE(pMissile->_miAnimLen); file->WriteLE(pMissile->_miAnimWidth); file->WriteLE(pMissile->_miAnimWidth2); file->WriteLE(pMissile->_miAnimCnt); file->WriteLE(pMissile->_miAnimAdd); file->WriteLE(pMissile->_miAnimFrame); file->WriteLE(pMissile->_miDrawFlag ? 1 : 0); file->WriteLE(pMissile->_miLightFlag ? 1 : 0); file->WriteLE(pMissile->_miPreFlag ? 1 : 0); file->WriteLE(pMissile->_miUniqTrans); file->WriteLE(pMissile->_mirange); file->WriteLE(pMissile->_misource); file->WriteLE(pMissile->_micaster); file->WriteLE(pMissile->_midam); file->WriteLE(pMissile->_miHitFlag ? 1 : 0); file->WriteLE(pMissile->_midist); file->WriteLE(pMissile->_mlid); file->WriteLE(pMissile->_mirnd); file->WriteLE(pMissile->_miVar1); file->WriteLE(pMissile->_miVar2); file->WriteLE(pMissile->_miVar3); file->WriteLE(pMissile->_miVar4); file->WriteLE(pMissile->_miVar5); file->WriteLE(pMissile->_miVar6); file->WriteLE(pMissile->_miVar7); file->WriteLE(pMissile->limitReached ? 1 : 0); } void SaveObject(SaveHelper *file, int i) { ObjectStruct *pObject = &Objects[i]; file->WriteLE(pObject->_otype); file->WriteLE(pObject->position.x); file->WriteLE(pObject->position.y); file->WriteLE(pObject->_oLight ? 1 : 0); file->WriteLE(pObject->_oAnimFlag); file->Skip(4); // Skip pointer _oAnimData file->WriteLE(pObject->_oAnimDelay); file->WriteLE(pObject->_oAnimCnt); file->WriteLE(pObject->_oAnimLen); file->WriteLE(pObject->_oAnimFrame); file->WriteLE(pObject->_oAnimWidth); file->WriteLE(CalculateWidth2(pObject->_oAnimWidth)); // Write _oAnimWidth2 for vanilla compatibility file->WriteLE(pObject->_oDelFlag ? 1 : 0); file->WriteLE(pObject->_oBreak); file->Skip(3); // Alignment file->WriteLE(pObject->_oSolidFlag ? 1 : 0); file->WriteLE(pObject->_oMissFlag ? 1 : 0); file->WriteLE(pObject->_oSelFlag); file->Skip(3); // Alignment file->WriteLE(pObject->_oPreFlag ? 1 : 0); file->WriteLE(pObject->_oTrapFlag ? 1 : 0); file->WriteLE(pObject->_oDoorFlag ? 1 : 0); file->WriteLE(pObject->_olid); file->WriteLE(pObject->_oRndSeed); file->WriteLE(pObject->_oVar1); file->WriteLE(pObject->_oVar2); file->WriteLE(pObject->_oVar3); file->WriteLE(pObject->_oVar4); file->WriteLE(pObject->_oVar5); file->WriteLE(pObject->_oVar6); file->WriteLE(pObject->bookMessage); file->WriteLE(pObject->_oVar8); } void SavePremium(SaveHelper *file, int i) { SaveItem(file, &premiumitems[i]); } void SaveQuest(SaveHelper *file, int i) { auto &quest = Quests[i]; file->WriteLE(quest._qlevel); file->WriteLE(quest._qidx); // _qtype for compatability, used in DRLG_CheckQuests file->WriteLE(quest._qactive); file->WriteLE(quest._qlvltype); file->WriteLE(quest.position.x); file->WriteLE(quest.position.y); file->WriteLE(quest._qslvl); file->WriteLE(quest._qidx); if (gbIsHellfire) { file->Skip(2); // Alignment file->WriteLE(quest._qmsg); } else { file->WriteLE(quest._qmsg); } file->WriteLE(quest._qvar1); file->WriteLE(quest._qvar2); file->Skip(2); // Alignment if (!gbIsHellfire) file->Skip(1); // Alignment file->WriteLE(quest._qlog ? 1 : 0); file->WriteBE(ReturnLvlX); file->WriteBE(ReturnLvlY); file->WriteBE(ReturnLevel); file->WriteBE(ReturnLevelType); file->Skip(sizeof(int32_t)); // Skip DoomQuestState } void SaveLighting(SaveHelper *file, LightStruct *pLight) { file->WriteLE(pLight->position.tile.x); file->WriteLE(pLight->position.tile.y); file->WriteLE(pLight->_lradius); file->WriteLE(pLight->_lid); file->WriteLE(pLight->_ldel ? 1 : 0); file->WriteLE(pLight->_lunflag ? 1 : 0); file->Skip(4); // Unused file->WriteLE(pLight->position.old.x); file->WriteLE(pLight->position.old.y); file->WriteLE(pLight->oldRadius); file->WriteLE(pLight->position.offset.x); file->WriteLE(pLight->position.offset.y); file->WriteLE(pLight->_lflags ? 1 : 0); } void SavePortal(SaveHelper *file, int i) { PortalStruct *pPortal = &Portals[i]; file->WriteLE(pPortal->open ? 1 : 0); file->WriteLE(pPortal->position.x); file->WriteLE(pPortal->position.y); file->WriteLE(pPortal->level); file->WriteLE(pPortal->ltype); file->WriteLE(pPortal->setlvl ? 1 : 0); } const int DiabloItemSaveSize = 368; const int HellfireItemSaveSize = 372; } // namespace void RemoveInvalidItem(ItemStruct *pItem) { bool isInvalid = !IsItemAvailable(pItem->IDidx) || !IsUniqueAvailable(pItem->_iUid); if (!gbIsHellfire) { isInvalid = isInvalid || (pItem->_itype == ITYPE_STAFF && GetSpellStaffLevel(pItem->_iSpell) == -1); isInvalid = isInvalid || (pItem->_iMiscId == IMISC_BOOK && GetSpellBookLevel(pItem->_iSpell) == -1); isInvalid = isInvalid || pItem->_iDamAcFlags != 0; isInvalid = isInvalid || pItem->_iPrePower > IDI_LASTDIABLO; isInvalid = isInvalid || pItem->_iSufPower > IDI_LASTDIABLO; } if (isInvalid) { pItem->_itype = ITYPE_NONE; } } _item_indexes RemapItemIdxFromDiablo(_item_indexes i) { constexpr auto GetItemIdValue = [](int i) -> int { if (i == IDI_SORCERER) { return 166; } if (i >= 156) { i += 5; // Hellfire exclusive items } if (i >= 88) { i += 1; // Scroll of Search } if (i >= 83) { i += 4; // Oils } return i; }; return static_cast<_item_indexes>(GetItemIdValue(i)); } _item_indexes RemapItemIdxToDiablo(_item_indexes i) { constexpr auto GetItemIdValue = [](int i) -> int { if (i == 166) { return IDI_SORCERER; } if ((i >= 83 && i <= 86) || i == 92 || i >= 161) { return -1; // Hellfire exclusive items } if (i >= 93) { i -= 1; // Scroll of Search } if (i >= 87) { i -= 4; // Oils } return i; }; return static_cast<_item_indexes>(GetItemIdValue(i)); } _item_indexes RemapItemIdxFromSpawn(_item_indexes i) { constexpr auto GetItemIdValue = [](int i) { if (i >= 62) { i += 9; // Medium and heavy armors } if (i >= 96) { i += 1; // Scroll of Stone Curse } if (i >= 98) { i += 1; // Scroll of Guardian } if (i >= 99) { i += 1; // Scroll of ... } if (i >= 101) { i += 1; // Scroll of Golem } if (i >= 102) { i += 1; // Scroll of None } if (i >= 104) { i += 1; // Scroll of Apocalypse } return i; }; return static_cast<_item_indexes>(GetItemIdValue(i)); } _item_indexes RemapItemIdxToSpawn(_item_indexes i) { constexpr auto GetItemIdValue = [](int i) { if (i >= 104) { i -= 1; // Scroll of Apocalypse } if (i >= 102) { i -= 1; // Scroll of None } if (i >= 101) { i -= 1; // Scroll of Golem } if (i >= 99) { i -= 1; // Scroll of ... } if (i >= 98) { i -= 1; // Scroll of Guardian } if (i >= 96) { i -= 1; // Scroll of Stone Curse } if (i >= 71) { i -= 9; // Medium and heavy armors } return i; }; return static_cast<_item_indexes>(GetItemIdValue(i)); } bool IsHeaderValid(uint32_t magicNumber) { gbIsHellfireSaveGame = false; if (magicNumber == LoadLE32("SHAR")) { return true; } if (magicNumber == LoadLE32("SHLF")) { gbIsHellfireSaveGame = true; return true; } if (!gbIsSpawn && magicNumber == LoadLE32("RETL")) { return true; } if (!gbIsSpawn && magicNumber == LoadLE32("HELF")) { gbIsHellfireSaveGame = true; return true; } return false; } void LoadHotkeys() { LoadHelper file("hotkeys"); if (!file.IsValid()) return; auto &myPlayer = Players[MyPlayerId]; for (auto &spellId : myPlayer._pSplHotKey) { spellId = static_cast(file.NextLE()); } for (auto &spellType : myPlayer._pSplTHotKey) { spellType = static_cast(file.NextLE()); } myPlayer._pRSpell = static_cast(file.NextLE()); myPlayer._pRSplType = static_cast(file.NextLE()); } void SaveHotkeys() { auto &myPlayer = Players[MyPlayerId]; const size_t nHotkeyTypes = sizeof(myPlayer._pSplHotKey) / sizeof(myPlayer._pSplHotKey[0]); const size_t nHotkeySpells = sizeof(myPlayer._pSplTHotKey) / sizeof(myPlayer._pSplTHotKey[0]); SaveHelper file("hotkeys", (nHotkeyTypes * 4) + nHotkeySpells + 4 + 1); for (auto &spellId : myPlayer._pSplHotKey) { file.WriteLE(spellId); } for (auto &spellType : myPlayer._pSplTHotKey) { file.WriteLE(spellType); } file.WriteLE(myPlayer._pRSpell); file.WriteLE(myPlayer._pRSplType); } void LoadHeroItems(PlayerStruct &player) { LoadHelper file("heroitems"); if (!file.IsValid()) return; gbIsHellfireSaveGame = file.NextBool8(); LoadMatchingItems(&file, NUM_INVLOC, player.InvBody); LoadMatchingItems(&file, NUM_INV_GRID_ELEM, player.InvList); LoadMatchingItems(&file, MAXBELTITEMS, player.SpdList); gbIsHellfireSaveGame = gbIsHellfire; } void RemoveEmptyInventory(PlayerStruct &player) { for (int i = NUM_INV_GRID_ELEM; i > 0; i--) { int8_t idx = player.InvGrid[i - 1]; if (idx > 0 && player.InvList[idx - 1].isEmpty()) { player.RemoveInvItem(idx - 1); } } } /** * @brief Load game state * @param firstflag Can be set to false if we are simply reloading the current game */ void LoadGame(bool firstflag) { FreeGameMem(); pfile_remove_temp_files(); LoadHelper file("game"); if (!file.IsValid()) app_fatal("%s", _("Unable to open save file archive")); if (!IsHeaderValid(file.NextLE())) app_fatal("%s", _("Invalid save file")); if (gbIsHellfireSaveGame) { giNumberOfLevels = 25; giNumberQuests = 24; giNumberOfSmithPremiumItems = 15; } else { // Todo initialize additional levels and quests if we are running Hellfire giNumberOfLevels = 17; giNumberQuests = 16; giNumberOfSmithPremiumItems = 6; } setlevel = file.NextBool8(); setlvlnum = static_cast<_setlevels>(file.NextBE()); currlevel = file.NextBE(); leveltype = static_cast(file.NextBE()); if (!setlevel) leveltype = gnLevelTypeTbl[currlevel]; int viewX = file.NextBE(); int viewY = file.NextBE(); invflag = file.NextBool8(); chrflag = file.NextBool8(); int tmpNummonsters = file.NextBE(); int tmpNumitems = file.NextBE(); int tmpNummissiles = file.NextBE(); int tmpNobjects = file.NextBE(); if (!gbIsHellfire && currlevel > 17) app_fatal("%s", _("Player is on a Hellfire only level")); for (uint8_t i = 0; i < giNumberOfLevels; i++) { glSeedTbl[i] = file.NextBE(); file.Skip(4); // Skip loading gnLevelTypeTbl } LoadPlayer(&file, MyPlayerId); sgGameInitInfo.nDifficulty = Players[MyPlayerId].pDifficulty; if (sgGameInitInfo.nDifficulty < DIFF_NORMAL || sgGameInitInfo.nDifficulty > DIFF_HELL) sgGameInitInfo.nDifficulty = DIFF_NORMAL; for (int i = 0; i < giNumberQuests; i++) LoadQuest(&file, i); for (int i = 0; i < MAXPORTAL; i++) LoadPortal(&file, i); if (gbIsHellfireSaveGame != gbIsHellfire) { ConvertLevels(); RemoveEmptyInventory(Players[MyPlayerId]); } LoadGameLevel(firstflag, ENTRY_LOAD); SyncInitPlr(MyPlayerId); SyncPlrAnim(MyPlayerId); ViewX = viewX; ViewY = viewY; ActiveMonsterCount = tmpNummonsters; ActiveItemCount = tmpNumitems; ActiveMissileCount = tmpNummissiles; ActiveObjectCount = tmpNobjects; for (int &monstkill : MonsterKillCounts) monstkill = file.NextBE(); if (leveltype != DTYPE_TOWN) { for (int &monsterId : ActiveMonsters) monsterId = file.NextBE(); for (int i = 0; i < ActiveMonsterCount; i++) LoadMonster(&file, Monsters[ActiveMonsters[i]]); for (int &missileId : ActiveMissiles) missileId = file.NextLE(); for (int &missileId : AvailableMissiles) missileId = file.NextLE(); for (int i = 0; i < ActiveMissileCount; i++) LoadMissile(&file, ActiveMissiles[i]); for (int &objectId : ActiveObjects) objectId = file.NextLE(); for (int &objectId : AvailableObjects) objectId = file.NextLE(); for (int i = 0; i < ActiveObjectCount; i++) LoadObject(&file, ActiveObjects[i]); for (int i = 0; i < ActiveObjectCount; i++) SyncObjectAnim(Objects[ActiveObjects[i]]); ActiveLightCount = file.NextBE(); for (uint8_t &lightId : ActiveLights) lightId = file.NextLE(); for (int i = 0; i < ActiveLightCount; i++) LoadLighting(&file, &Lights[ActiveLights[i]]); VisionId = file.NextBE(); VisionCount = file.NextBE(); for (int i = 0; i < VisionCount; i++) LoadLighting(&file, &VisionList[i]); } for (int &itemId : ActiveItems) itemId = file.NextLE(); for (int &itemId : AvailableItems) itemId = file.NextLE(); for (int i = 0; i < ActiveItemCount; i++) LoadItem(&file, ActiveItems[i]); for (bool &uniqueItemFlag : UniqueItemFlags) uniqueItemFlag = file.NextBool8(); for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dLight[i][j] = file.NextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dFlags[i][j] = file.NextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dPlayer[i][j] = file.NextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dItem[i][j] = file.NextLE(); } if (leveltype != DTYPE_TOWN) { for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dMonster[i][j] = file.NextBE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dDead[i][j] = file.NextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dObject[i][j] = file.NextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dLight[i][j] = file.NextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dPreLight[i][j] = file.NextLE(); } for (int j = 0; j < DMAXY; j++) { for (int i = 0; i < DMAXX; i++) // NOLINT(modernize-loop-convert) AutomapView[i][j] = file.NextBool8(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dMissile[i][j] = file.NextLE(); } } numpremium = file.NextBE(); premiumlevel = file.NextBE(); for (int i = 0; i < giNumberOfSmithPremiumItems; i++) LoadPremium(&file, i); if (gbIsHellfire && !gbIsHellfireSaveGame) SpawnPremium(MyPlayerId); AutomapActive = file.NextBool8(); AutoMapScale = file.NextBE(); AutomapZoomReset(); ResyncQuests(); if (leveltype != DTYPE_TOWN) ProcessLightList(); RedoPlayerVision(); ProcessVisionList(); missiles_process_charge(); NewCursor(CURSOR_HAND); gbProcessPlayers = true; if (gbIsHellfireSaveGame != gbIsHellfire) { RemoveEmptyLevelItems(); SaveGame(); } gbIsHellfireSaveGame = gbIsHellfire; } void SaveHeroItems(PlayerStruct &player) { size_t itemCount = NUM_INVLOC + NUM_INV_GRID_ELEM + MAXBELTITEMS; SaveHelper file("heroitems", itemCount * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t)); file.WriteLE(gbIsHellfire ? 1 : 0); SaveItems(&file, player.InvBody, NUM_INVLOC); SaveItems(&file, player.InvList, NUM_INV_GRID_ELEM); SaveItems(&file, player.SpdList, MAXBELTITEMS); } // 256 kilobytes + 3 bytes (demo leftover) for file magic (262147) // final game uses 4-byte magic instead of 3 #define FILEBUFF ((256 * 1024) + 3) void SaveGameData() { SaveHelper file("game", FILEBUFF); if (gbIsSpawn && !gbIsHellfire) file.WriteLE(LoadLE32("SHAR")); else if (gbIsSpawn && gbIsHellfire) file.WriteLE(LoadLE32("SHLF")); else if (!gbIsSpawn && gbIsHellfire) file.WriteLE(LoadLE32("HELF")); else if (!gbIsSpawn && !gbIsHellfire) file.WriteLE(LoadLE32("RETL")); else app_fatal("%s", _("Invalid game state")); if (gbIsHellfire) { giNumberOfLevels = 25; giNumberQuests = 24; giNumberOfSmithPremiumItems = 15; } else { giNumberOfLevels = 17; giNumberQuests = 16; giNumberOfSmithPremiumItems = 6; } file.WriteLE(setlevel ? 1 : 0); file.WriteBE(setlvlnum); file.WriteBE(currlevel); file.WriteBE(leveltype); file.WriteBE(ViewX); file.WriteBE(ViewY); file.WriteLE(invflag ? 1 : 0); file.WriteLE(chrflag ? 1 : 0); file.WriteBE(ActiveMonsterCount); file.WriteBE(ActiveItemCount); file.WriteBE(ActiveMissileCount); file.WriteBE(ActiveObjectCount); for (uint8_t i = 0; i < giNumberOfLevels; i++) { file.WriteBE(glSeedTbl[i]); file.WriteBE(gnLevelTypeTbl[i]); } Players[MyPlayerId].pDifficulty = sgGameInitInfo.nDifficulty; SavePlayer(&file, MyPlayerId); for (int i = 0; i < giNumberQuests; i++) SaveQuest(&file, i); for (int i = 0; i < MAXPORTAL; i++) SavePortal(&file, i); for (int monstkill : MonsterKillCounts) file.WriteBE(monstkill); if (leveltype != DTYPE_TOWN) { for (int monsterId : ActiveMonsters) file.WriteBE(monsterId); for (int i = 0; i < ActiveMonsterCount; i++) SaveMonster(&file, Monsters[ActiveMonsters[i]]); for (int missileId : ActiveMissiles) file.WriteLE(missileId); for (int missileId : AvailableMissiles) file.WriteLE(missileId); for (int i = 0; i < ActiveMissileCount; i++) SaveMissile(&file, ActiveMissiles[i]); for (int objectId : ActiveObjects) file.WriteLE(objectId); for (int objectId : AvailableObjects) file.WriteLE(objectId); for (int i = 0; i < ActiveObjectCount; i++) SaveObject(&file, ActiveObjects[i]); file.WriteBE(ActiveLightCount); for (uint8_t lightId : ActiveLights) file.WriteLE(lightId); for (int i = 0; i < ActiveLightCount; i++) SaveLighting(&file, &Lights[ActiveLights[i]]); file.WriteBE(VisionId); file.WriteBE(VisionCount); for (int i = 0; i < VisionCount; i++) SaveLighting(&file, &VisionList[i]); } for (int itemId : ActiveItems) file.WriteLE(itemId); for (int itemId : AvailableItems) file.WriteLE(itemId); for (int i = 0; i < ActiveItemCount; i++) SaveItem(&file, &Items[ActiveItems[i]]); for (bool uniqueItemFlag : UniqueItemFlags) file.WriteLE(uniqueItemFlag ? 1 : 0); for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dLight[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dFlags[i][j] & ~(BFLAG_MISSILE | BFLAG_VISIBLE | BFLAG_DEAD_PLAYER)); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dPlayer[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dItem[i][j]); } if (leveltype != DTYPE_TOWN) { for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteBE(dMonster[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dDead[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dObject[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dLight[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dPreLight[i][j]); } for (int j = 0; j < DMAXY; j++) { for (int i = 0; i < DMAXX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(AutomapView[i][j] ? 1 : 0); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dMissile[i][j]); } } file.WriteBE(numpremium); file.WriteBE(premiumlevel); for (int i = 0; i < giNumberOfSmithPremiumItems; i++) SavePremium(&file, i); file.WriteLE(AutomapActive ? 1 : 0); file.WriteBE(AutoMapScale); } void SaveGame() { gbValidSaveFile = true; pfile_write_hero(/*writeGameData=*/true); } void SaveLevel() { PFileScopedArchiveWriter scopedWriter; DoUnVision(Players[MyPlayerId].position.tile, Players[MyPlayerId]._pLightRad); // fix for vision staying on the level if (currlevel == 0) glSeedTbl[0] = AdvanceRndSeed(); char szName[MAX_PATH]; GetTempLevelNames(szName); SaveHelper file(szName, FILEBUFF); if (leveltype != DTYPE_TOWN) { for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dDead[i][j]); } } file.WriteBE(ActiveMonsterCount); file.WriteBE(ActiveItemCount); file.WriteBE(ActiveObjectCount); if (leveltype != DTYPE_TOWN) { for (int monsterId : ActiveMonsters) file.WriteBE(monsterId); for (int i = 0; i < ActiveMonsterCount; i++) SaveMonster(&file, Monsters[ActiveMonsters[i]]); for (int objectId : ActiveObjects) file.WriteLE(objectId); for (int objectId : AvailableObjects) file.WriteLE(objectId); for (int i = 0; i < ActiveObjectCount; i++) SaveObject(&file, ActiveObjects[i]); } for (int itemId : ActiveItems) file.WriteLE(itemId); for (int itemId : AvailableItems) file.WriteLE(itemId); for (int i = 0; i < ActiveItemCount; i++) SaveItem(&file, &Items[ActiveItems[i]]); for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dFlags[i][j] & ~(BFLAG_MISSILE | BFLAG_VISIBLE | BFLAG_DEAD_PLAYER)); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dItem[i][j]); } if (leveltype != DTYPE_TOWN) { for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteBE(dMonster[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dObject[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dLight[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dPreLight[i][j]); } for (int j = 0; j < DMAXY; j++) { for (int i = 0; i < DMAXX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(AutomapView[i][j] ? 1 : 0); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) file.WriteLE(dMissile[i][j]); } } if (!setlevel) Players[MyPlayerId]._pLvlVisited[currlevel] = true; else Players[MyPlayerId]._pSLvlVisited[setlvlnum] = true; } void LoadLevel() { char szName[MAX_PATH]; GetPermLevelNames(szName); LoadHelper file(szName); if (!file.IsValid()) app_fatal("%s", _("Unable to open save file archive")); if (leveltype != DTYPE_TOWN) { for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dDead[i][j] = file.NextLE(); } SyncUniqDead(); } ActiveMonsterCount = file.NextBE(); ActiveItemCount = file.NextBE(); ActiveObjectCount = file.NextBE(); if (leveltype != DTYPE_TOWN) { for (int &monsterId : ActiveMonsters) monsterId = file.NextBE(); for (int i = 0; i < ActiveMonsterCount; i++) LoadMonster(&file, Monsters[ActiveMonsters[i]]); for (int &objectId : ActiveObjects) objectId = file.NextLE(); for (int &objectId : AvailableObjects) objectId = file.NextLE(); for (int i = 0; i < ActiveObjectCount; i++) LoadObject(&file, ActiveObjects[i]); if (!gbSkipSync) { for (int i = 0; i < ActiveObjectCount; i++) SyncObjectAnim(Objects[ActiveObjects[i]]); } } for (int &itemId : ActiveItems) itemId = file.NextLE(); for (int &itemId : AvailableItems) itemId = file.NextLE(); for (int i = 0; i < ActiveItemCount; i++) LoadItem(&file, ActiveItems[i]); for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dFlags[i][j] = file.NextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dItem[i][j] = file.NextLE(); } if (leveltype != DTYPE_TOWN) { for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dMonster[i][j] = file.NextBE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dObject[i][j] = file.NextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dLight[i][j] = file.NextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dPreLight[i][j] = file.NextLE(); } for (int j = 0; j < DMAXY; j++) { for (int i = 0; i < DMAXX; i++) // NOLINT(modernize-loop-convert) AutomapView[i][j] = file.NextBool8(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) dMissile[i][j] = 0; /// BUGFIX: supposed to load saved missiles with "file.NextLE()"? } } if (gbIsHellfireSaveGame != gbIsHellfire) { RemoveEmptyLevelItems(); } if (!gbSkipSync) { AutomapZoomReset(); ResyncQuests(); SyncPortals(); UpdateLighting = true; } for (auto &player : Players) { if (player.plractive && currlevel == player.plrlevel) Lights[player._plid]._lunflag = true; } } } // namespace devilution