/** * @file loadsave.cpp * * Implementation of save game functionality. */ #include "loadsave.h" #include #include #include "automap.h" #include "codec.h" #include "control.h" #include "cursor.h" #include "dead.h" #include "doom.h" #include "engine.h" #include "init.h" #include "inv.h" #include "lighting.h" #include "missiles.h" #include "mpqapi.h" #include "pfile.h" #include "stores.h" #include "utils/language.h" namespace devilution { bool gbIsHellfireSaveGame; uint8_t giNumberOfLevels; uint8_t giNumberQuests; uint8_t giNumberOfSmithPremiumItems; namespace { 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; uint32_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(uint32_t size = 1) { return m_buffer != nullptr && m_size >= (m_cur + size); } void skip(uint32_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; uint32_t m_cur = 0; uint32_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(uint32_t len = 1) { return m_buffer != nullptr && m_capacity >= (m_cur + len); } void skip(uint32_t 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 encoded_len = codec_get_encoded_len(m_cur); const char *const password = pfile_get_password(); codec_encode(m_buffer.get(), m_cur, encoded_len, password); mpqapi_write_file(m_szFileName, m_buffer.get(), encoded_len); } }; } // 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; } } static 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->_iAnimLen = file->nextLE(); pItem->_iAnimFrame = 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 = 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->nextLE(); 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 = 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); } static void LoadItems(LoadHelper *file, const int n, ItemStruct *pItem) { for (int i = 0; i < n; i++) { LoadItemData(file, &pItem[i]); } } static void LoadPlayer(LoadHelper *file, int p) { auto &player = plr[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.x = file->nextLE(); player.position.offset.y = file->nextLE(); player.position.velocity.x = file->nextLE(); player.position.velocity.y = file->nextLE(); player._pdir = static_cast(file->nextLE()); file->skip(4); // Unused player._pgfxnum = file->nextLE(); file->skip(4); // Skip pointer pData player.AnimInfo.DelayLen = file->nextLE(); player.AnimInfo.DelayCounter = 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()); player._pTSplType = static_cast(file->nextLE()); 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()); player._pSBkSplType = static_cast(file->nextLE()); 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()); player._pwtype = static_cast(file->nextLE()); 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 = ToBlkTbl[static_cast(player._pClass)]; player._pHPBase = file->nextLE(); player._pMaxHPBase = file->nextLE(); player._pHitPoints = file->nextLE(); player._pMaxHP = file->nextLE(); player._pHPPer = file->nextLE(); player._pManaBase = file->nextLE(); player._pMaxManaBase = file->nextLE(); player._pMana = file->nextLE(); player._pMaxMana = file->nextLE(); player._pManaPer = file->nextLE(); player._pLevel = file->nextLE(); player._pMaxLvl = file->nextLE(); file->skip(2); // Alignment player._pExperience = file->nextLE(); player._pMaxExp = file->nextLE(); player._pNextExper = file->nextLE(); 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.x = file->nextLE(); player.position.offset2.y = 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; static void LoadMonster(LoadHelper *file, int i) { MonsterStruct *pMonster = &monster[i]; pMonster->_mMTidx = file->nextLE(); pMonster->_mmode = static_cast(file->nextLE()); pMonster->_mgoal = static_cast(file->nextLE()); file->skip(3); // Alignment pMonster->_mgoalvar1 = file->nextLE(); pMonster->_mgoalvar2 = file->nextLE(); pMonster->_mgoalvar3 = file->nextLE(); file->skip(4); // Unused pMonster->_pathcount = file->nextLE(); file->skip(3); // Alignment pMonster->position.tile.x = file->nextLE(); pMonster->position.tile.y = file->nextLE(); pMonster->position.future.x = file->nextLE(); pMonster->position.future.y = file->nextLE(); pMonster->position.old.x = file->nextLE(); pMonster->position.old.y = file->nextLE(); pMonster->position.offset.x = file->nextLE(); pMonster->position.offset.y = file->nextLE(); pMonster->position.velocity.x = file->nextLE(); pMonster->position.velocity.y = file->nextLE(); pMonster->_mdir = static_cast(file->nextLE()); pMonster->_menemy = file->nextLE(); pMonster->enemyPosition.x = file->nextLE(); pMonster->enemyPosition.y = file->nextLE(); file->skip(2); // Unused file->skip(4); // Skip pointer _mAnimData pMonster->_mAnimDelay = file->nextLE(); pMonster->_mAnimCnt = file->nextLE(); pMonster->_mAnimLen = file->nextLE(); pMonster->_mAnimFrame = file->nextLE(); file->skip(4); // Skip _meflag pMonster->_mDelFlag = file->nextBool32(); pMonster->_mVar1 = file->nextLE(); pMonster->_mVar2 = file->nextLE(); pMonster->_mVar3 = file->nextLE(); pMonster->position.temp.x = file->nextLE(); pMonster->position.temp.y = file->nextLE(); pMonster->position.offset2.x = file->nextLE(); pMonster->position.offset2.y = file->nextLE(); pMonster->actionFrame = file->nextLE(); pMonster->_mmaxhp = file->nextLE(); pMonster->_mhitpoints = file->nextLE(); pMonster->_mAi = static_cast<_mai_id>(file->nextLE()); pMonster->_mint = file->nextLE(); file->skip(2); // Alignment pMonster->_mFlags = file->nextLE(); pMonster->_msquelch = file->nextLE(); file->skip(3); // Alignment file->skip(4); // Unused pMonster->position.last.x = file->nextLE(); pMonster->position.last.y = file->nextLE(); pMonster->_mRndSeed = file->nextLE(); pMonster->_mAISeed = file->nextLE(); file->skip(4); // Unused pMonster->_uniqtype = file->nextLE(); pMonster->_uniqtrans = file->nextLE(); pMonster->_udeadval = file->nextLE(); pMonster->mWhoHit = file->nextLE(); pMonster->mLevel = file->nextLE(); file->skip(1); // Alignment pMonster->mExp = file->nextLE(); if (i < MAX_PLRS) // Don't skip for golems pMonster->mHit = file->nextLE(); else file->skip(1); // Skip mHit as it's already initialized pMonster->mMinDamage = file->nextLE(); pMonster->mMaxDamage = file->nextLE(); file->skip(1); // Skip mHit2 as it's already initialized pMonster->mMinDamage2 = file->nextLE(); pMonster->mMaxDamage2 = file->nextLE(); pMonster->mArmorClass = file->nextLE(); file->skip(1); // Alignment pMonster->mMagicRes = file->nextLE(); file->skip(2); // Alignment pMonster->mtalkmsg = static_cast<_speech_id>(file->nextLE()); if (pMonster->mtalkmsg == TEXT_KING1) // Fix original bad mapping of NONE for monsters pMonster->mtalkmsg = TEXT_NONE; pMonster->leader = file->nextLE(); pMonster->leaderflag = file->nextLE(); pMonster->packsize = file->nextLE(); pMonster->mlid = file->nextLE(); if (pMonster->mlid == plr[myplr]._plid) pMonster->mlid = NO_LIGHT; // Correct incorect values in old saves // Omit pointer mName; // Omit pointer MType; // Omit pointer MData; if (gbSkipSync) return; SyncMonsterAnim(i); } static void LoadMissile(LoadHelper *file, int i) { MissileStruct *pMissile = &missile[i]; pMissile->_mitype = file->nextLE(); pMissile->position.tile.x = file->nextLE(); pMissile->position.tile.y = file->nextLE(); pMissile->position.offset.x = file->nextLE(); pMissile->position.offset.y = file->nextLE(); pMissile->position.velocity.x = file->nextLE(); pMissile->position.velocity.y = file->nextLE(); pMissile->position.start.x = file->nextLE(); pMissile->position.start.y = file->nextLE(); pMissile->position.traveled.x = file->nextLE(); pMissile->position.traveled.y = file->nextLE(); pMissile->_mimfnum = file->nextLE(); pMissile->_mispllvl = file->nextLE(); pMissile->_miDelFlag = file->nextBool32(); pMissile->_miAnimType = file->nextLE(); file->skip(3); // Alignment pMissile->_miAnimFlags = 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->_miVar8 = file->nextBool32(); } static void LoadObject(LoadHelper *file, int i) { ObjectStruct *pObject = &object[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->_oVar7 = static_cast<_speech_id>(file->nextLE()); pObject->_oVar8 = file->nextLE(); } static void LoadItem(LoadHelper *file, int i) { LoadItemData(file, &items[i]); GetItemFrm(i); } static void LoadPremium(LoadHelper *file, int i) { LoadItemData(file, &premiumitems[i]); } static void LoadQuest(LoadHelper *file, int i) { QuestStruct *pQuest = &quests[i]; pQuest->_qlevel = file->nextLE(); pQuest->_qtype = file->nextLE(); pQuest->_qactive = static_cast(file->nextLE()); pQuest->_qlvltype = static_cast(file->nextLE()); pQuest->position.x = file->nextLE(); pQuest->position.y = file->nextLE(); pQuest->_qslvl = static_cast<_setlevels>(file->nextLE()); pQuest->_qidx = file->nextLE(); if (gbIsHellfireSaveGame) { file->skip(2); // Alignment pQuest->_qmsg = static_cast<_speech_id>(file->nextLE()); } else { pQuest->_qmsg = static_cast<_speech_id>(file->nextLE()); } pQuest->_qvar1 = file->nextLE(); pQuest->_qvar2 = file->nextLE(); file->skip(2); // Alignment if (!gbIsHellfireSaveGame) file->skip(1); // Alignment pQuest->_qlog = file->nextBool32(); ReturnLvlX = file->nextBE(); ReturnLvlY = file->nextBE(); ReturnLvl = file->nextBE(); ReturnLvlT = static_cast(file->nextBE()); DoomQuestState = file->nextBE(); } static void LoadLighting(LoadHelper *file, LightListStruct *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->oldRadious = file->nextLE(); pLight->position.offset.x = file->nextLE(); pLight->position.offset.y = file->nextLE(); pLight->_lflags = file->nextBool32(); } static void LoadPortal(LoadHelper *file, int i) { PortalStruct *pPortal = &portal[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(); } int RemapItemIdxFromDiablo(int i) { 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; } int RemapItemIdxToDiablo(int i) { 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; } int RemapItemIdxFromSpawn(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; } int RemapItemIdxToSpawn(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; } bool IsHeaderValid(uint32_t magicNumber) { gbIsHellfireSaveGame = false; if (magicNumber == LoadLE32("SHAR")) { return true; } if (magicNumber == LoadLE32("SHLF")) { gbIsHellfireSaveGame = true; return true; } else if (!gbIsSpawn && magicNumber == LoadLE32("RETL")) { return true; } else if (!gbIsSpawn && magicNumber == LoadLE32("HELF")) { gbIsHellfireSaveGame = true; return true; } return false; } static void ConvertLevels() { // Backup current level state bool _setlevel = setlevel; _setlevels _setlvlnum = setlvlnum; int _currlevel = currlevel; dungeon_type _leveltype = 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 = _setlevel; setlvlnum = _setlvlnum; currlevel = _currlevel; leveltype = _leveltype; } void LoadHotkeys() { LoadHelper file("hotkeys"); if (!file.isValid()) return; for (auto &spellId : plr[myplr]._pSplHotKey) { spellId = static_cast(file.nextLE()); } for (auto &spellType : plr[myplr]._pSplTHotKey) { spellType = static_cast(file.nextLE()); } plr[myplr]._pRSpell = static_cast(file.nextLE()); plr[myplr]._pRSplType = static_cast(file.nextLE()); } void SaveHotkeys() { const size_t nHotkeyTypes = sizeof(plr[myplr]._pSplHotKey) / sizeof(plr[myplr]._pSplHotKey[0]); const size_t nHotkeySpells = sizeof(plr[myplr]._pSplTHotKey) / sizeof(plr[myplr]._pSplTHotKey[0]); SaveHelper file("hotkeys", (nHotkeyTypes * 4) + nHotkeySpells + 4 + 1); for (auto &spellId : plr[myplr]._pSplHotKey) { file.writeLE(spellId); } for (auto &spellType : plr[myplr]._pSplTHotKey) { file.writeLE(spellType); } file.writeLE(plr[myplr]._pRSpell); file.writeLE(plr[myplr]._pRSplType); } static 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 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(int pnum) { for (int i = NUM_INV_GRID_ELEM; i > 0; i--) { int idx = plr[pnum].InvGrid[i - 1]; if (idx > 0 && plr[pnum].InvList[idx - 1].isEmpty()) { plr[pnum].RemoveInvItem(idx - 1); } }; } void RemoveEmptyLevelItems() { for (int i = numitems; i > 0; i--) { int ii = itemactive[i]; if (items[ii].isEmpty()) { dItem[items[ii].position.x][items[ii].position.y] = 0; DeleteItem(ii, i); } } } /** * @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 _nummonsters = file.nextBE(); int _numitems = file.nextBE(); int _nummissiles = file.nextBE(); int _nobjects = 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, myplr); sgGameInitInfo.nDifficulty = plr[myplr].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(myplr); } LoadGameLevel(firstflag, ENTRY_LOAD); SyncInitPlr(myplr); SyncPlrAnim(myplr); ViewX = _ViewX; ViewY = _ViewY; nummonsters = _nummonsters; numitems = _numitems; nummissiles = _nummissiles; nobjects = _nobjects; for (int &monstkill : monstkills) monstkill = file.nextBE(); if (leveltype != DTYPE_TOWN) { for (int &monsterId : monstactive) monsterId = file.nextBE(); for (int i = 0; i < nummonsters; i++) LoadMonster(&file, monstactive[i]); for (int &missileId : missileactive) missileId = file.nextLE(); for (int &missileId : missileavail) missileId = file.nextLE(); for (int i = 0; i < nummissiles; i++) LoadMissile(&file, missileactive[i]); for (int &objectId : objectactive) objectId = file.nextLE(); for (int &objectId : objectavail) objectId = file.nextLE(); for (int i = 0; i < nobjects; i++) LoadObject(&file, objectactive[i]); for (int i = 0; i < nobjects; i++) SyncObjectAnim(objectactive[i]); numlights = file.nextBE(); for (uint8_t &lightId : lightactive) lightId = file.nextLE(); for (int i = 0; i < numlights; i++) LoadLighting(&file, &LightList[lightactive[i]]); visionid = file.nextBE(); numvision = file.nextBE(); for (int i = 0; i < numvision; i++) LoadLighting(&file, &VisionList[i]); } for (int &itemId : itemactive) itemId = file.nextLE(); for (int &itemId : itemavail) itemId = file.nextLE(); for (int i = 0; i < numitems; i++) LoadItem(&file, itemactive[i]); for (bool &UniqueItemFlag : UniqueItemFlags) UniqueItemFlag = file.nextBool8(); for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dLight[i][j] = file.nextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dFlags[i][j] = file.nextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dPlayer[i][j] = file.nextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dItem[i][j] = file.nextLE(); } if (leveltype != DTYPE_TOWN) { for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dMonster[i][j] = file.nextBE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dDead[i][j] = file.nextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dObject[i][j] = file.nextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dLight[i][j] = file.nextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dPreLight[i][j] = file.nextLE(); } for (int j = 0; j < DMAXY; j++) { for (int i = 0; i < DMAXX; i++) AutomapView[i][j] = file.nextBool8(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) 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(myplr); AutomapActive = file.nextBool8(); AutoMapScale = file.nextBE(); AutomapZoomReset(); ResyncQuests(); if (leveltype != DTYPE_TOWN) ProcessLightList(); RedoPlayerVision(); ProcessVisionList(); missiles_process_charge(); ResetPal(); NewCursor(CURSOR_HAND); gbProcessPlayers = true; if (gbIsHellfireSaveGame != gbIsHellfire) { RemoveEmptyLevelItems(); SaveGame(); } gbIsHellfireSaveGame = gbIsHellfire; } static void SaveItem(SaveHelper *file, ItemStruct *pItem) { int idx = pItem->IDidx; if (!gbIsHellfire) idx = RemapItemIdxToDiablo(idx); if (gbIsSpawn) idx = RemapItemIdxToSpawn(idx); int iType = pItem->_itype; if (idx == -1) { idx = 0; 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); file->skip(4); // Skip pointer _iAnimData file->writeLE(pItem->_iAnimLen); file->writeLE(pItem->_iAnimFrame); // write _iAnimWidth for vanilla compatibility file->writeLE(ItemAnimWidth); // write _iAnimWidth2 for vanilla compatibility file->writeLE(CalculateWidth2(ItemAnimWidth)); file->skip(4); // Unused since 1.02 file->writeLE(pItem->_iSelFlag); file->skip(3); // Alignment file->writeLE(pItem->_iPostDraw); file->writeLE(pItem->_iIdentified); 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); 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); file->writeLE(idx); file->writeLE(pItem->dwBuff); if (gbIsHellfire) file->writeLE(pItem->_iDamAcFlags); } static void SaveItems(SaveHelper *file, ItemStruct *pItem, const int n) { for (int i = 0; i < n; i++) { SaveItem(file, &pItem[i]); } } static void SavePlayer(SaveHelper *file, int p) { auto &player = plr[p]; file->writeLE(player._pmode); for (int8_t step : player.walkpath) file->writeLE(step); file->writeLE(player.plractive); 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.x); file->writeLE(player.position.offset.y); file->writeLE(player.position.velocity.x); file->writeLE(player.position.velocity.y); file->writeLE(player._pdir); file->skip(4); // Unused file->writeLE(player._pgfxnum); file->skip(4); // Skip pointer _pAnimData file->writeLE(player.AnimInfo.DelayLen); file->writeLE(player.AnimInfo.DelayCounter); 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(4); // 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->writeLE(player._pTSplType); file->skip(3); // Alignment file->writeLE(player._pRSpell); file->writeLE(player._pRSplType); file->skip(3); // Alignment file->writeLE(player._pSBkSpell); file->writeLE(player._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._pwtype); file->writeLE(player._pBlockFlag); file->writeLE(player._pInvincible); file->writeLE(player._pLightRad); file->writeLE(player._pLvlChanging); 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->writeLE(player._pHPPer); file->writeLE(player._pManaBase); file->writeLE(player._pMaxManaBase); file->writeLE(player._pMana); file->writeLE(player._pMaxMana); file->writeLE(player._pManaPer); file->writeLE(player._pLevel); file->writeLE(player._pMaxLvl); file->skip(2); // Alignment file->writeLE(player._pExperience); file->writeLE(player._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); 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.x); file->writeLE(player.position.offset2.y); // Write actionFrame for vanilla compatibility file->writeLE(0); for (uint8_t i = 0; i < giNumberOfLevels; i++) file->writeLE(player._pLvlVisited[i]); for (uint8_t i = 0; i < giNumberOfLevels; i++) file->writeLE(player._pSLvlVisited[i]); // only 10 used file->skip(2); // Alignment // Write _pGFXLoad for vanilla compatibility file->writeLE(0); 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(1); // Unused 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); file->writeLE(player.pManaShield); file->writeLE(player.pOriginalCathedral); 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 } static void SaveMonster(SaveHelper *file, int i) { MonsterStruct *pMonster = &monster[i]; file->writeLE(pMonster->_mMTidx); file->writeLE(pMonster->_mmode); file->writeLE(pMonster->_mgoal); file->skip(3); // Alignment file->writeLE(pMonster->_mgoalvar1); file->writeLE(pMonster->_mgoalvar2); file->writeLE(pMonster->_mgoalvar3); file->skip(4); // Unused file->writeLE(pMonster->_pathcount); file->skip(3); // Alignment file->writeLE(pMonster->position.tile.x); file->writeLE(pMonster->position.tile.y); file->writeLE(pMonster->position.future.x); file->writeLE(pMonster->position.future.y); file->writeLE(pMonster->position.old.x); file->writeLE(pMonster->position.old.y); file->writeLE(pMonster->position.offset.x); file->writeLE(pMonster->position.offset.y); file->writeLE(pMonster->position.velocity.x); file->writeLE(pMonster->position.velocity.y); file->writeLE(pMonster->_mdir); file->writeLE(pMonster->_menemy); file->writeLE(pMonster->enemyPosition.x); file->writeLE(pMonster->enemyPosition.y); file->skip(2); // Unused file->skip(4); // Skip pointer _mAnimData file->writeLE(pMonster->_mAnimDelay); file->writeLE(pMonster->_mAnimCnt); file->writeLE(pMonster->_mAnimLen); file->writeLE(pMonster->_mAnimFrame); file->skip(4); // Skip _meflag file->writeLE(pMonster->_mDelFlag); file->writeLE(pMonster->_mVar1); file->writeLE(pMonster->_mVar2); file->writeLE(pMonster->_mVar3); file->writeLE(pMonster->position.temp.x); file->writeLE(pMonster->position.temp.y); file->writeLE(pMonster->position.offset2.x); file->writeLE(pMonster->position.offset2.y); file->writeLE(pMonster->actionFrame); file->writeLE(pMonster->_mmaxhp); file->writeLE(pMonster->_mhitpoints); file->writeLE(pMonster->_mAi); file->writeLE(pMonster->_mint); file->skip(2); // Alignment file->writeLE(pMonster->_mFlags); file->writeLE(pMonster->_msquelch); file->skip(3); // Alignment file->skip(4); // Unused file->writeLE(pMonster->position.last.x); file->writeLE(pMonster->position.last.y); file->writeLE(pMonster->_mRndSeed); file->writeLE(pMonster->_mAISeed); file->skip(4); // Unused file->writeLE(pMonster->_uniqtype); file->writeLE(pMonster->_uniqtrans); file->writeLE(pMonster->_udeadval); file->writeLE(pMonster->mWhoHit); file->writeLE(pMonster->mLevel); file->skip(1); // Alignment file->writeLE(pMonster->mExp); file->writeLE(pMonster->mHit < UINT8_MAX ? pMonster->mHit : UINT8_MAX); // For backwards compatibility file->writeLE(pMonster->mMinDamage); file->writeLE(pMonster->mMaxDamage); file->writeLE(pMonster->mHit2 < UINT8_MAX ? pMonster->mHit2 : UINT8_MAX); // For backwards compatibility file->writeLE(pMonster->mMinDamage2); file->writeLE(pMonster->mMaxDamage2); file->writeLE(pMonster->mArmorClass); file->skip(1); // Alignment file->writeLE(pMonster->mMagicRes); file->skip(2); // Alignment file->writeLE(pMonster->mtalkmsg == TEXT_NONE ? 0 : pMonster->mtalkmsg); // Replicate original bad mapping of none for monsters file->writeLE(pMonster->leader); file->writeLE(pMonster->leaderflag); file->writeLE(pMonster->packsize); file->writeLE(pMonster->mlid); // Omit pointer mName; // Omit pointer MType; // Omit pointer MData; } static void SaveMissile(SaveHelper *file, int i) { MissileStruct *pMissile = &missile[i]; file->writeLE(pMissile->_mitype); file->writeLE(pMissile->position.tile.x); file->writeLE(pMissile->position.tile.y); file->writeLE(pMissile->position.offset.x); file->writeLE(pMissile->position.offset.y); file->writeLE(pMissile->position.velocity.x); file->writeLE(pMissile->position.velocity.y); file->writeLE(pMissile->position.start.x); file->writeLE(pMissile->position.start.y); file->writeLE(pMissile->position.traveled.x); file->writeLE(pMissile->position.traveled.y); file->writeLE(pMissile->_mimfnum); file->writeLE(pMissile->_mispllvl); file->writeLE(pMissile->_miDelFlag); file->writeLE(pMissile->_miAnimType); file->skip(3); // Alignment file->writeLE(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); file->writeLE(pMissile->_miLightFlag); file->writeLE(pMissile->_miPreFlag); file->writeLE(pMissile->_miUniqTrans); file->writeLE(pMissile->_mirange); file->writeLE(pMissile->_misource); file->writeLE(pMissile->_micaster); file->writeLE(pMissile->_midam); file->writeLE(pMissile->_miHitFlag); 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->_miVar8); } static void SaveObject(SaveHelper *file, int i) { ObjectStruct *pObject = &object[i]; file->writeLE(pObject->_otype); file->writeLE(pObject->position.x); file->writeLE(pObject->position.y); file->writeLE(pObject->_oLight); 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); file->writeLE(pObject->_oBreak); file->skip(3); // Alignment file->writeLE(pObject->_oSolidFlag); file->writeLE(pObject->_oMissFlag); file->writeLE(pObject->_oSelFlag); file->skip(3); // Alignment file->writeLE(pObject->_oPreFlag); file->writeLE(pObject->_oTrapFlag); file->writeLE(pObject->_oDoorFlag); 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->_oVar7); file->writeLE(pObject->_oVar8); } static void SavePremium(SaveHelper *file, int i) { SaveItem(file, &premiumitems[i]); } static void SaveQuest(SaveHelper *file, int i) { QuestStruct *pQuest = &quests[i]; file->writeLE(pQuest->_qlevel); file->writeLE(pQuest->_qtype); file->writeLE(pQuest->_qactive); file->writeLE(pQuest->_qlvltype); file->writeLE(pQuest->position.x); file->writeLE(pQuest->position.y); file->writeLE(pQuest->_qslvl); file->writeLE(pQuest->_qidx); if (gbIsHellfire) { file->skip(2); // Alignment file->writeLE(pQuest->_qmsg); } else { file->writeLE(pQuest->_qmsg); } file->writeLE(pQuest->_qvar1); file->writeLE(pQuest->_qvar2); file->skip(2); // Alignment if (!gbIsHellfire) file->skip(1); // Alignment file->writeLE(pQuest->_qlog); file->writeBE(ReturnLvlX); file->writeBE(ReturnLvlY); file->writeBE(ReturnLvl); file->writeBE(ReturnLvlT); file->writeBE(DoomQuestState); } static void SaveLighting(SaveHelper *file, LightListStruct *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); file->writeLE(pLight->_lunflag); file->skip(4); // Unused file->writeLE(pLight->position.old.x); file->writeLE(pLight->position.old.y); file->writeLE(pLight->oldRadious); file->writeLE(pLight->position.offset.x); file->writeLE(pLight->position.offset.y); file->writeLE(pLight->_lflags); } static void SavePortal(SaveHelper *file, int i) { PortalStruct *pPortal = &portal[i]; file->writeLE(pPortal->open); file->writeLE(pPortal->position.x); file->writeLE(pPortal->position.y); file->writeLE(pPortal->level); file->writeLE(pPortal->ltype); file->writeLE(pPortal->setlvl); } const int DiabloItemSaveSize = 368; const int HellfireItemSaveSize = 372; void SaveHeroItems(PlayerStruct &player) { size_t items = NUM_INVLOC + NUM_INV_GRID_ELEM + MAXBELTITEMS; SaveHelper file("heroitems", items * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t)); file.writeLE(gbIsHellfire); 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); file.writeBE(setlvlnum); file.writeBE(currlevel); file.writeBE(leveltype); file.writeBE(ViewX); file.writeBE(ViewY); file.writeLE(invflag); file.writeLE(chrflag); file.writeBE(nummonsters); file.writeBE(numitems); file.writeBE(nummissiles); file.writeBE(nobjects); for (uint8_t i = 0; i < giNumberOfLevels; i++) { file.writeBE(glSeedTbl[i]); file.writeBE(gnLevelTypeTbl[i]); } plr[myplr].pDifficulty = sgGameInitInfo.nDifficulty; SavePlayer(&file, myplr); for (int i = 0; i < giNumberQuests; i++) SaveQuest(&file, i); for (int i = 0; i < MAXPORTAL; i++) SavePortal(&file, i); for (int monstkill : monstkills) file.writeBE(monstkill); if (leveltype != DTYPE_TOWN) { for (int monsterId : monstactive) file.writeBE(monsterId); for (int i = 0; i < nummonsters; i++) SaveMonster(&file, monstactive[i]); for (int missileId : missileactive) file.writeLE(missileId); for (int missileId : missileavail) file.writeLE(missileId); for (int i = 0; i < nummissiles; i++) SaveMissile(&file, missileactive[i]); for (int objectId : objectactive) file.writeLE(objectId); for (int objectId : objectavail) file.writeLE(objectId); for (int i = 0; i < nobjects; i++) SaveObject(&file, objectactive[i]); file.writeBE(numlights); for (uint8_t lightId : lightactive) file.writeLE(lightId); for (int i = 0; i < numlights; i++) SaveLighting(&file, &LightList[lightactive[i]]); file.writeBE(visionid); file.writeBE(numvision); for (int i = 0; i < numvision; i++) SaveLighting(&file, &VisionList[i]); } for (int itemId : itemactive) file.writeLE(itemId); for (int itemId : itemavail) file.writeLE(itemId); for (int i = 0; i < numitems; i++) SaveItem(&file, &items[itemactive[i]]); for (bool UniqueItemFlag : UniqueItemFlags) file.writeLE(UniqueItemFlag); for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) file.writeLE(dLight[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) 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++) file.writeLE(dPlayer[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) file.writeLE(dItem[i][j]); } if (leveltype != DTYPE_TOWN) { for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) file.writeBE(dMonster[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) file.writeLE(dDead[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) file.writeLE(dObject[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) file.writeLE(dLight[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) file.writeLE(dPreLight[i][j]); } for (int j = 0; j < DMAXY; j++) { for (int i = 0; i < DMAXX; i++) file.writeLE(AutomapView[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) file.writeLE(dMissile[i][j]); } } file.writeBE(numpremium); file.writeBE(premiumlevel); for (int i = 0; i < giNumberOfSmithPremiumItems; i++) SavePremium(&file, i); file.writeLE(AutomapActive); file.writeBE(AutoMapScale); } void SaveGame() { gbValidSaveFile = true; pfile_write_hero(/*write_game_data=*/true); } void SaveLevel() { PFileScopedArchiveWriter scoped_writer; DoUnVision(plr[myplr].position.tile, plr[myplr]._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++) file.writeLE(dDead[i][j]); } } file.writeBE(nummonsters); file.writeBE(numitems); file.writeBE(nobjects); if (leveltype != DTYPE_TOWN) { for (int monsterId : monstactive) file.writeBE(monsterId); for (int i = 0; i < nummonsters; i++) SaveMonster(&file, monstactive[i]); for (int objectId : objectactive) file.writeLE(objectId); for (int objectId : objectavail) file.writeLE(objectId); for (int i = 0; i < nobjects; i++) SaveObject(&file, objectactive[i]); } for (int itemId : itemactive) file.writeLE(itemId); for (int itemId : itemavail) file.writeLE(itemId); for (int i = 0; i < numitems; i++) SaveItem(&file, &items[itemactive[i]]); for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) 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++) file.writeLE(dItem[i][j]); } if (leveltype != DTYPE_TOWN) { for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) file.writeBE(dMonster[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) file.writeLE(dObject[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) file.writeLE(dLight[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) file.writeLE(dPreLight[i][j]); } for (int j = 0; j < DMAXY; j++) { for (int i = 0; i < DMAXX; i++) file.writeLE(AutomapView[i][j]); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) file.writeLE(dMissile[i][j]); } } if (!setlevel) plr[myplr]._pLvlVisited[currlevel] = true; else plr[myplr]._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++) dDead[i][j] = file.nextLE(); } SetDead(); } nummonsters = file.nextBE(); numitems = file.nextBE(); nobjects = file.nextBE(); if (leveltype != DTYPE_TOWN) { for (int &monsterId : monstactive) monsterId = file.nextBE(); for (int i = 0; i < nummonsters; i++) LoadMonster(&file, monstactive[i]); for (int &objectId : objectactive) objectId = file.nextLE(); for (int &objectId : objectavail) objectId = file.nextLE(); for (int i = 0; i < nobjects; i++) LoadObject(&file, objectactive[i]); if (!gbSkipSync) { for (int i = 0; i < nobjects; i++) SyncObjectAnim(objectactive[i]); } } for (int &itemId : itemactive) itemId = file.nextLE(); for (int &itemId : itemavail) itemId = file.nextLE(); for (int i = 0; i < numitems; i++) LoadItem(&file, itemactive[i]); for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dFlags[i][j] = file.nextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dItem[i][j] = file.nextLE(); } if (leveltype != DTYPE_TOWN) { for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dMonster[i][j] = file.nextBE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dObject[i][j] = file.nextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dLight[i][j] = file.nextLE(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dPreLight[i][j] = file.nextLE(); } for (int j = 0; j < DMAXY; j++) { for (int i = 0; i < DMAXX; i++) AutomapView[i][j] = file.nextBool8(); } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) dMissile[i][j] = 0; /// BUGFIX: supposed to load saved missiles with "file.nextLE()"? } } if (gbIsHellfireSaveGame != gbIsHellfire) { RemoveEmptyLevelItems(); } if (!gbSkipSync) { AutomapZoomReset(); ResyncQuests(); SyncPortals(); dolighting = true; } for (auto &player : plr) { if (player.plractive && currlevel == player.plrlevel) LightList[player._plid]._lunflag = true; } } } // namespace devilution