/** * @file loadsave.cpp * * Implementation of save game functionality. */ #include "all.h" DEVILUTION_BEGIN_NAMESPACE bool gbIsHellfireSaveGame; int giNumberOfLevels; int giNumberQuests; int 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 { Uint8 *m_buffer; Uint32 m_bufferPtr = 0; Uint32 m_bufferLen; template T next() { const auto size = sizeof(T); if (!isValid(size)) return 0; T value; memcpy(&value, &m_buffer[m_bufferPtr], size); m_bufferPtr += size; return value; } public: LoadHelper(const char *szFileName) { m_buffer = pfile_read(szFileName, &m_bufferLen); } bool isValid(Uint32 size = 1) { return m_buffer != nullptr && m_bufferLen >= (m_bufferPtr + size); } void skip(Uint32 size) { m_bufferPtr += size; } void nextBytes(void *bytes, size_t size) { if (!isValid(size)) return; memcpy(bytes, &m_buffer[m_bufferPtr], size); m_bufferPtr += size; } template T nextLE() { return SwapLE(next()); } template T nextBE() { return SwapBE(next()); } bool nextBool8() { return next() != 0; } bool nextBool32() { return next() != 0; } ~LoadHelper() { mem_free_dbg(m_buffer); } }; class SaveHelper { const char *m_szFileName; Uint8 *m_buffer; Uint32 m_bufferPtr = 0; Uint32 m_bufferLen; public: SaveHelper(const char *szFileName, size_t bufferLen) { m_szFileName = szFileName; m_bufferLen = bufferLen; m_buffer = DiabloAllocPtr(codec_get_encoded_len(m_bufferLen)); } bool isValid(Uint32 len = 1) { return m_buffer != nullptr && m_bufferLen >= (m_bufferPtr + len); } void skip(Uint32 len) { m_bufferPtr += len; } void writeBytes(const void *bytes, size_t len) { if (!isValid(len)) return; memcpy(&m_buffer[m_bufferPtr], bytes, len); m_bufferPtr += 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)); } void flush() { if (m_buffer == nullptr) return; pfile_write_save_file(m_szFileName, m_buffer, m_bufferPtr, codec_get_encoded_len(m_bufferPtr)); mem_free_dbg(m_buffer); m_buffer = nullptr; } ~SaveHelper() { flush(); } }; } 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 = (item_type)file->nextLE(); pItem->_ix = file->nextLE(); pItem->_iy = file->nextLE(); pItem->_iAnimFlag = file->nextBool32(); file->skip(4); // Skip pointer _iAnimData pItem->_iAnimLen = file->nextLE(); pItem->_iAnimFrame = file->nextLE(); pItem->_iAnimWidth = file->nextLE(); pItem->_iAnimWidth2 = file->nextLE(); 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 = (item_equip_type)file->nextLE(); pItem->_iClass = (item_class)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 = (item_misc_id)file->nextLE(); pItem->_iSpell = (spell_id)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 = (item_effect_type)file->nextLE(); pItem->_iSufPower = (item_effect_type)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 (!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) { PlayerStruct *pPlayer = &plr[p]; pPlayer->_pmode = (PLR_MODE)file->nextLE(); for (int i = 0; i < MAX_PATH_LENGTH; i++) { pPlayer->walkpath[i] = file->nextLE(); } pPlayer->plractive = file->nextBool8(); file->skip(2); // Alignment pPlayer->destAction = (action_id)file->nextLE(); pPlayer->destParam1 = file->nextLE(); pPlayer->destParam2 = file->nextLE(); pPlayer->destParam3 = (direction)file->nextLE(); pPlayer->destParam4 = file->nextLE(); pPlayer->plrlevel = file->nextLE(); pPlayer->_px = file->nextLE(); pPlayer->_py = file->nextLE(); pPlayer->_pfutx = file->nextLE(); pPlayer->_pfuty = file->nextLE(); pPlayer->_ptargx = file->nextLE(); pPlayer->_ptargy = file->nextLE(); pPlayer->_pownerx = file->nextLE(); pPlayer->_pownery = file->nextLE(); pPlayer->_poldx = file->nextLE(); pPlayer->_poldy = file->nextLE(); pPlayer->_pxoff = file->nextLE(); pPlayer->_pyoff = file->nextLE(); pPlayer->_pxvel = file->nextLE(); pPlayer->_pyvel = file->nextLE(); pPlayer->_pdir = (direction)file->nextLE(); file->skip(4); // Unused pPlayer->_pgfxnum = file->nextLE(); file->skip(4); // Skip pointer _pAnimData pPlayer->_pAnimDelay = file->nextLE(); pPlayer->_pAnimCnt = file->nextLE(); pPlayer->_pAnimLen = file->nextLE(); pPlayer->_pAnimFrame = file->nextLE(); pPlayer->_pAnimWidth = file->nextLE(); pPlayer->_pAnimWidth2 = file->nextLE(); file->skip(4); // Skip _peflag pPlayer->_plid = file->nextLE(); pPlayer->_pvid = file->nextLE(); pPlayer->_pSpell = (spell_id)file->nextLE(); pPlayer->_pSplType = (spell_type)file->nextLE(); pPlayer->_pSplFrom = file->nextLE(); file->skip(2); // Alignment pPlayer->_pTSpell = (spell_id)file->nextLE(); pPlayer->_pTSplType = (spell_type)file->nextLE(); file->skip(3); // Alignment pPlayer->_pRSpell = (spell_id)file->nextLE(); pPlayer->_pRSplType = (spell_type)file->nextLE(); file->skip(3); // Alignment pPlayer->_pSBkSpell = (spell_id)file->nextLE(); pPlayer->_pSBkSplType = (spell_type)file->nextLE(); for (int i = 0; i < 64; i++) pPlayer->_pSplLvl[i] = file->nextLE(); file->skip(7); // Alignment pPlayer->_pMemSpells = file->nextLE(); pPlayer->_pAblSpells = file->nextLE(); pPlayer->_pScrlSpells = file->nextLE(); pPlayer->_pSpellFlags = file->nextLE(); file->skip(3); // Alignment for (int i = 0; i < 4; i++) pPlayer->_pSplHotKey[i] = (spell_id)file->nextLE(); for (int i = 0; i < 4; i++) pPlayer->_pSplTHotKey[i] = (spell_type)file->nextLE(); pPlayer->_pwtype = (player_weapon_type)file->nextLE(); pPlayer->_pBlockFlag = file->nextBool8(); pPlayer->_pInvincible = file->nextBool8(); pPlayer->_pLightRad = file->nextLE(); pPlayer->_pLvlChanging = file->nextBool8(); file->nextBytes(pPlayer->_pName, PLR_NAME_LEN); pPlayer->_pClass = (plr_class)file->nextLE(); file->skip(3); // Alignment pPlayer->_pStrength = file->nextLE(); pPlayer->_pBaseStr = file->nextLE(); pPlayer->_pMagic = file->nextLE(); pPlayer->_pBaseMag = file->nextLE(); pPlayer->_pDexterity = file->nextLE(); pPlayer->_pBaseDex = file->nextLE(); pPlayer->_pVitality = file->nextLE(); pPlayer->_pBaseVit = file->nextLE(); pPlayer->_pStatPts = file->nextLE(); pPlayer->_pDamageMod = file->nextLE(); pPlayer->_pBaseToBlk = file->nextLE(); if (pPlayer->_pBaseToBlk == 0) pPlayer->_pBaseToBlk = ToBlkTbl[pPlayer->_pClass]; pPlayer->_pHPBase = file->nextLE(); pPlayer->_pMaxHPBase = file->nextLE(); pPlayer->_pHitPoints = file->nextLE(); pPlayer->_pMaxHP = file->nextLE(); pPlayer->_pHPPer = file->nextLE(); pPlayer->_pManaBase = file->nextLE(); pPlayer->_pMaxManaBase = file->nextLE(); pPlayer->_pMana = file->nextLE(); pPlayer->_pMaxMana = file->nextLE(); pPlayer->_pManaPer = file->nextLE(); pPlayer->_pLevel = file->nextLE(); pPlayer->_pMaxLvl = file->nextLE(); file->skip(2); // Alignment pPlayer->_pExperience = file->nextLE(); pPlayer->_pMaxExp = file->nextLE(); pPlayer->_pNextExper = file->nextLE(); pPlayer->_pArmorClass = file->nextLE(); pPlayer->_pMagResist = file->nextLE(); pPlayer->_pFireResist = file->nextLE(); pPlayer->_pLghtResist = file->nextLE(); pPlayer->_pGold = file->nextLE(); pPlayer->_pInfraFlag = file->nextBool32(); pPlayer->_pVar1 = file->nextLE(); pPlayer->_pVar2 = file->nextLE(); pPlayer->_pVar3 = (direction)file->nextLE(); pPlayer->_pVar4 = file->nextLE(); pPlayer->_pVar5 = file->nextLE(); pPlayer->_pVar6 = file->nextLE(); pPlayer->_pVar7 = file->nextLE(); pPlayer->_pVar8 = file->nextLE(); for (int i = 0; i < giNumberOfLevels; i++) pPlayer->_pLvlVisited[i] = file->nextBool8(); for (int i = 0; i < giNumberOfLevels; i++) pPlayer->_pSLvlVisited[i] = file->nextBool8(); file->skip(2); // Alignment pPlayer->_pGFXLoad = file->nextLE(); file->skip(4 * 8); // Skip pointers _pNAnim pPlayer->_pNFrames = file->nextLE(); pPlayer->_pNWidth = file->nextLE(); file->skip(4 * 8); // Skip pointers _pWAnim pPlayer->_pWFrames = file->nextLE(); pPlayer->_pWWidth = file->nextLE(); file->skip(4 * 8); // Skip pointers _pAAnim pPlayer->_pAFrames = file->nextLE(); pPlayer->_pAWidth = file->nextLE(); pPlayer->_pAFNum = file->nextLE(); file->skip(4 * 8); // Skip pointers _pLAnim file->skip(4 * 8); // Skip pointers _pFAnim file->skip(4 * 8); // Skip pointers _pTAnim pPlayer->_pSFrames = file->nextLE(); pPlayer->_pSWidth = file->nextLE(); pPlayer->_pSFNum = file->nextLE(); file->skip(4 * 8); // Skip pointers _pHAnim pPlayer->_pHFrames = file->nextLE(); pPlayer->_pHWidth = file->nextLE(); file->skip(4 * 8); // Skip pointers _pDAnim pPlayer->_pDFrames = file->nextLE(); pPlayer->_pDWidth = file->nextLE(); file->skip(4 * 8); // Skip pointers _pBAnim pPlayer->_pBFrames = file->nextLE(); pPlayer->_pBWidth = file->nextLE(); LoadItems(file, NUM_INVLOC, pPlayer->InvBody); LoadItems(file, NUM_INV_GRID_ELEM, pPlayer->InvList); pPlayer->_pNumInv = file->nextLE(); for (int i = 0; i < NUM_INV_GRID_ELEM; i++) pPlayer->InvGrid[i] = file->nextLE(); LoadItems(file, MAXBELTITEMS, pPlayer->SpdList); LoadItemData(file, &pPlayer->HoldItem); pPlayer->_pIMinDam = file->nextLE(); pPlayer->_pIMaxDam = file->nextLE(); pPlayer->_pIAC = file->nextLE(); pPlayer->_pIBonusDam = file->nextLE(); pPlayer->_pIBonusToHit = file->nextLE(); pPlayer->_pIBonusAC = file->nextLE(); pPlayer->_pIBonusDamMod = file->nextLE(); file->skip(4); // Alignment pPlayer->_pISpells = file->nextLE(); pPlayer->_pIFlags = file->nextLE(); pPlayer->_pIGetHit = file->nextLE(); pPlayer->_pISplLvlAdd = file->nextLE(); file->skip(1); // Unused file->skip(2); // Alignment pPlayer->_pISplDur = file->nextLE(); pPlayer->_pIEnAc = file->nextLE(); pPlayer->_pIFMinDam = file->nextLE(); pPlayer->_pIFMaxDam = file->nextLE(); pPlayer->_pILMinDam = file->nextLE(); pPlayer->_pILMaxDam = file->nextLE(); pPlayer->_pOilType = (item_misc_id)file->nextLE(); pPlayer->pTownWarps = file->nextLE(); pPlayer->pDungMsgs = file->nextLE(); pPlayer->pLvlLoad = file->nextLE(); if (gbIsHellfireSaveGame) { pPlayer->pDungMsgs2 = file->nextLE(); pPlayer->pBattleNet = false; } else { pPlayer->pDungMsgs2 = 0; pPlayer->pBattleNet = file->nextBool8(); } pPlayer->pManaShield = file->nextBool8(); if (gbIsHellfireSaveGame) { pPlayer->pOriginalCathedral = file->nextBool8(); } else { file->skip(1); pPlayer->pOriginalCathedral = true; } file->skip(2); // Available bytes pPlayer->wReflections = file->nextLE(); file->skip(14); // Available bytes pPlayer->pDiabloKillLevel = file->nextLE(); pPlayer->pDifficulty = file->nextLE(); pPlayer->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 = (MON_MODE)file->nextLE(); pMonster->_mgoal = 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->_mx = file->nextLE(); pMonster->_my = file->nextLE(); pMonster->_mfutx = file->nextLE(); pMonster->_mfuty = file->nextLE(); pMonster->_moldx = file->nextLE(); pMonster->_moldy = file->nextLE(); pMonster->_mxoff = file->nextLE(); pMonster->_myoff = file->nextLE(); pMonster->_mxvel = file->nextLE(); pMonster->_myvel = file->nextLE(); pMonster->_mdir = file->nextLE(); pMonster->_menemy = file->nextLE(); pMonster->_menemyx = file->nextLE(); pMonster->_menemyy = 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->_mVar4 = file->nextLE(); pMonster->_mVar5 = file->nextLE(); pMonster->_mVar6 = file->nextLE(); pMonster->_mVar7 = file->nextLE(); pMonster->_mVar8 = file->nextLE(); pMonster->_mmaxhp = file->nextLE(); pMonster->_mhitpoints = file->nextLE(); pMonster->_mAi = (_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->_lastx = file->nextLE(); pMonster->_lasty = 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(); 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 = file->nextLE(); 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->_mix = file->nextLE(); pMissile->_miy = file->nextLE(); pMissile->_mixoff = file->nextLE(); pMissile->_miyoff = file->nextLE(); pMissile->_mixvel = file->nextLE(); pMissile->_miyvel = file->nextLE(); pMissile->_misx = file->nextLE(); pMissile->_misy = file->nextLE(); pMissile->_mitxoff = file->nextLE(); pMissile->_mityoff = 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->nextLE(); } static void LoadObject(LoadHelper *file, int i) { ObjectStruct *pObject = &object[i]; pObject->_otype = file->nextLE(); pObject->_ox = file->nextLE(); pObject->_oy = 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(); pObject->_oAnimWidth2 = file->nextLE(); 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 = (_speech_id)file->nextLE(); pObject->_oVar8 = file->nextLE(); } static void LoadItem(LoadHelper *file, int i) { LoadItemData(file, &item[i]); GetItemFrm(i); } static void LoadPremium(LoadHelper *file, int i) { LoadItemData(file, &premiumitem[i]); } static void LoadQuest(LoadHelper *file, int i) { QuestStruct *pQuest = &quests[i]; pQuest->_qlevel = file->nextLE(); pQuest->_qtype = file->nextLE(); pQuest->_qactive = file->nextLE(); pQuest->_qlvltype = (dungeon_type)file->nextLE(); pQuest->_qtx = file->nextLE(); pQuest->_qty = file->nextLE(); pQuest->_qslvl = file->nextLE(); pQuest->_qidx = file->nextLE(); if (gbIsHellfireSaveGame) { file->skip(2); // Alignment pQuest->_qmsg = file->nextLE(); } else { pQuest->_qmsg = 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 = (dungeon_type)file->nextBE(); DoomQuestState = file->nextBE(); } static void LoadLighting(LoadHelper *file, LightListStruct *pLight) { pLight->_lx = file->nextLE(); pLight->_ly = file->nextLE(); pLight->_lradius = file->nextLE(); pLight->_lid = file->nextLE(); pLight->_ldel = file->nextBool32(); pLight->_lunflag = file->nextBool32(); file->skip(4); // Unused pLight->_lunx = file->nextLE(); pLight->_luny = file->nextLE(); pLight->_lunr = file->nextLE(); pLight->_xoff = file->nextLE(); pLight->_yoff = file->nextLE(); pLight->_lflags = file->nextBool32(); } static void LoadPortal(LoadHelper *file, int i) { PortalStruct *pPortal = &portal[i]; pPortal->open = file->nextBool32(); pPortal->x = file->nextLE(); pPortal->y = file->nextLE(); pPortal->level = file->nextLE(); pPortal->ltype = (dungeon_type)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; } bool IsHeaderValid(Uint32 magicNumber) { gbIsHellfireSaveGame = false; if (magicNumber == LOAD_LE32("SHAR")) { return true; } else if (magicNumber == LOAD_LE32("SHLF")) { gbIsHellfireSaveGame = true; return true; } else if (!gbIsSpawn && magicNumber == LOAD_LE32("RETL")) { return true; } else if (!gbIsSpawn && magicNumber == LOAD_LE32("HELF")) { gbIsHellfireSaveGame = true; return true; } return false; } static void ConvertLevels() { // Backup current level state bool _setlevel = setlevel; int _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 (int i = 0; i < MAXQUESTS; i++) { if (quests[i]._qactive == QUEST_NOTAVAIL) { continue; } leveltype = quests[i]._qlvltype; if (leveltype == DTYPE_NONE) { continue; } setlvlnum = quests[i]._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; 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]); for (size_t i = 0; i < nHotkeyTypes; i++) { plr[myplr]._pSplHotKey[i] = (spell_id)file.nextLE(); } for (size_t i = 0; i < nHotkeySpells; i++) { plr[myplr]._pSplTHotKey[i] = (spell_type)file.nextLE(); } plr[myplr]._pRSpell = (spell_id)file.nextLE(); plr[myplr]._pRSplType = (spell_type)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 (size_t i = 0; i < nHotkeyTypes; i++) { file.writeLE(plr[myplr]._pSplHotKey[i]); } for (size_t i = 0; i < nHotkeySpells; i++) { file.writeLE(plr[myplr]._pSplTHotKey[i]); } 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 *pPlayer) { LoadHelper file("heroitems"); if (!file.isValid()) return; gbIsHellfireSaveGame = file.nextBool8(); LoadMatchingItems(&file, NUM_INVLOC, pPlayer->InvBody); LoadMatchingItems(&file, NUM_INV_GRID_ELEM, pPlayer->InvList); LoadMatchingItems(&file, MAXBELTITEMS, pPlayer->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()) { RemoveInvItem(pnum, idx - 1); } }; } void RemoveEmptyLevelItems() { for (int i = numitems; i > 0; i--) { int ii = itemactive[i]; if (item[ii].isEmpty()) { dItem[item[ii]._ix][item[ii]._iy] = 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("Unable to open save file archive"); if (!IsHeaderValid(file.nextLE())) app_fatal("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 = file.nextBE(); currlevel = file.nextBE(); leveltype = (dungeon_type)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("Player is on a Hellfire only level"); for (int i = 0; i < giNumberOfLevels; i++) { glSeedTbl[i] = file.nextBE(); file.skip(4); // Skip loading gnLevelTypeTbl } LoadPlayer(&file, myplr); gnDifficulty = plr[myplr].pDifficulty; if (gnDifficulty < DIFF_NORMAL || gnDifficulty > DIFF_HELL) gnDifficulty = 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 i = 0; i < MAXMONSTERS; i++) monstkills[i] = file.nextBE(); if (leveltype != DTYPE_TOWN) { for (int i = 0; i < MAXMONSTERS; i++) monstactive[i] = file.nextBE(); for (int i = 0; i < nummonsters; i++) LoadMonster(&file, monstactive[i]); for (int i = 0; i < MAXMISSILES; i++) missileactive[i] = file.nextLE(); for (int i = 0; i < MAXMISSILES; i++) missileavail[i] = file.nextLE(); for (int i = 0; i < nummissiles; i++) LoadMissile(&file, missileactive[i]); for (int i = 0; i < MAXOBJECTS; i++) objectactive[i] = file.nextLE(); for (int i = 0; i < MAXOBJECTS; i++) objectavail[i] = 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 (int i = 0; i < MAXLIGHTS; i++) lightactive[i] = 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 i = 0; i < MAXITEMS; i++) itemactive[i] = file.nextLE(); for (int i = 0; i < MAXITEMS; i++) itemavail[i] = file.nextLE(); for (int i = 0; i < numitems; i++) LoadItem(&file, itemactive[i]); for (int i = 0; i < 128; i++) UniqueItemFlag[i] = 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); automapflag = file.nextBool8(); AutoMapScale = file.nextBE(); AutomapZoomReset(); ResyncQuests(); if (leveltype != DTYPE_TOWN) ProcessLightList(); RedoPlayerVision(); ProcessVisionList(); missiles_process_charge(); ResetPal(); SetCursor_(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); 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->_ix); file->writeLE(pItem->_iy); file->writeLE(pItem->_iAnimFlag); file->skip(4); // Skip pointer _iAnimData file->writeLE(pItem->_iAnimLen); file->writeLE(pItem->_iAnimFrame); file->writeLE(pItem->_iAnimWidth); file->writeLE(pItem->_iAnimWidth2); 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) { PlayerStruct *pPlayer = &plr[p]; file->writeLE(pPlayer->_pmode); for (int i = 0; i < MAX_PATH_LENGTH; i++) file->writeLE(pPlayer->walkpath[i]); file->writeLE(pPlayer->plractive); file->skip(2); // Alignment file->writeLE(pPlayer->destAction); file->writeLE(pPlayer->destParam1); file->writeLE(pPlayer->destParam2); file->writeLE(pPlayer->destParam3); file->writeLE(pPlayer->destParam4); file->writeLE(pPlayer->plrlevel); file->writeLE(pPlayer->_px); file->writeLE(pPlayer->_py); file->writeLE(pPlayer->_pfutx); file->writeLE(pPlayer->_pfuty); file->writeLE(pPlayer->_ptargx); file->writeLE(pPlayer->_ptargy); file->writeLE(pPlayer->_pownerx); file->writeLE(pPlayer->_pownery); file->writeLE(pPlayer->_poldx); file->writeLE(pPlayer->_poldy); file->writeLE(pPlayer->_pxoff); file->writeLE(pPlayer->_pyoff); file->writeLE(pPlayer->_pxvel); file->writeLE(pPlayer->_pyvel); file->writeLE(pPlayer->_pdir); file->skip(4); // Unused file->writeLE(pPlayer->_pgfxnum); file->skip(4); // Skip pointer _pAnimData file->writeLE(pPlayer->_pAnimDelay); file->writeLE(pPlayer->_pAnimCnt); file->writeLE(pPlayer->_pAnimLen); file->writeLE(pPlayer->_pAnimFrame); file->writeLE(pPlayer->_pAnimWidth); file->writeLE(pPlayer->_pAnimWidth2); file->skip(4); // Skip _peflag file->writeLE(pPlayer->_plid); file->writeLE(pPlayer->_pvid); file->writeLE(pPlayer->_pSpell); file->writeLE(pPlayer->_pSplType); file->writeLE(pPlayer->_pSplFrom); file->skip(2); // Alignment file->writeLE(pPlayer->_pTSpell); file->writeLE(pPlayer->_pTSplType); file->skip(3); // Alignment file->writeLE(pPlayer->_pRSpell); file->writeLE(pPlayer->_pRSplType); file->skip(3); // Alignment file->writeLE(pPlayer->_pSBkSpell); file->writeLE(pPlayer->_pSBkSplType); for (int i = 0; i < 64; i++) file->writeLE(pPlayer->_pSplLvl[i]); file->skip(7); // Alignment file->writeLE(pPlayer->_pMemSpells); file->writeLE(pPlayer->_pAblSpells); file->writeLE(pPlayer->_pScrlSpells); file->writeLE(pPlayer->_pSpellFlags); file->skip(3); // Alignment for (int i = 0; i < 4; i++) file->writeLE(pPlayer->_pSplHotKey[i]); for (int i = 0; i < 4; i++) file->writeLE(pPlayer->_pSplTHotKey[i]); file->writeLE(pPlayer->_pwtype); file->writeLE(pPlayer->_pBlockFlag); file->writeLE(pPlayer->_pInvincible); file->writeLE(pPlayer->_pLightRad); file->writeLE(pPlayer->_pLvlChanging); file->writeBytes(pPlayer->_pName, PLR_NAME_LEN); file->writeLE(pPlayer->_pClass); file->skip(3); // Alignment file->writeLE(pPlayer->_pStrength); file->writeLE(pPlayer->_pBaseStr); file->writeLE(pPlayer->_pMagic); file->writeLE(pPlayer->_pBaseMag); file->writeLE(pPlayer->_pDexterity); file->writeLE(pPlayer->_pBaseDex); file->writeLE(pPlayer->_pVitality); file->writeLE(pPlayer->_pBaseVit); file->writeLE(pPlayer->_pStatPts); file->writeLE(pPlayer->_pDamageMod); file->writeLE(pPlayer->_pBaseToBlk); file->writeLE(pPlayer->_pHPBase); file->writeLE(pPlayer->_pMaxHPBase); file->writeLE(pPlayer->_pHitPoints); file->writeLE(pPlayer->_pMaxHP); file->writeLE(pPlayer->_pHPPer); file->writeLE(pPlayer->_pManaBase); file->writeLE(pPlayer->_pMaxManaBase); file->writeLE(pPlayer->_pMana); file->writeLE(pPlayer->_pMaxMana); file->writeLE(pPlayer->_pManaPer); file->writeLE(pPlayer->_pLevel); file->writeLE(pPlayer->_pMaxLvl); file->skip(2); // Alignment file->writeLE(pPlayer->_pExperience); file->writeLE(pPlayer->_pMaxExp); file->writeLE(pPlayer->_pNextExper); file->writeLE(pPlayer->_pArmorClass); file->writeLE(pPlayer->_pMagResist); file->writeLE(pPlayer->_pFireResist); file->writeLE(pPlayer->_pLghtResist); file->writeLE(pPlayer->_pGold); file->writeLE(pPlayer->_pInfraFlag); file->writeLE(pPlayer->_pVar1); file->writeLE(pPlayer->_pVar2); file->writeLE(pPlayer->_pVar3); file->writeLE(pPlayer->_pVar4); file->writeLE(pPlayer->_pVar5); file->writeLE(pPlayer->_pVar6); file->writeLE(pPlayer->_pVar7); file->writeLE(pPlayer->_pVar8); for (int i = 0; i < giNumberOfLevels; i++) file->writeLE(pPlayer->_pLvlVisited[i]); for (int i = 0; i < giNumberOfLevels; i++) file->writeLE(pPlayer->_pSLvlVisited[i]); // only 10 used file->skip(2); // Alignment file->writeLE(pPlayer->_pGFXLoad); file->skip(4 * 8); // Skip pointers _pNAnim file->writeLE(pPlayer->_pNFrames); file->writeLE(pPlayer->_pNWidth); file->skip(4 * 8); // Skip pointers _pWAnim file->writeLE(pPlayer->_pWFrames); file->writeLE(pPlayer->_pWWidth); file->skip(4 * 8); // Skip pointers _pAAnim file->writeLE(pPlayer->_pAFrames); file->writeLE(pPlayer->_pAWidth); file->writeLE(pPlayer->_pAFNum); file->skip(4 * 8); // Skip pointers _pLAnim file->skip(4 * 8); // Skip pointers _pFAnim file->skip(4 * 8); // Skip pointers _pTAnim file->writeLE(pPlayer->_pSFrames); file->writeLE(pPlayer->_pSWidth); file->writeLE(pPlayer->_pSFNum); file->skip(4 * 8); // Skip pointers _pHAnim file->writeLE(pPlayer->_pHFrames); file->writeLE(pPlayer->_pHWidth); file->skip(4 * 8); // Skip pointers _pDAnim file->writeLE(pPlayer->_pDFrames); file->writeLE(pPlayer->_pDWidth); file->skip(4 * 8); // Skip pointers _pBAnim file->writeLE(pPlayer->_pBFrames); file->writeLE(pPlayer->_pBWidth); SaveItems(file, pPlayer->InvBody, NUM_INVLOC); SaveItems(file, pPlayer->InvList, NUM_INV_GRID_ELEM); file->writeLE(pPlayer->_pNumInv); for (int i = 0; i < NUM_INV_GRID_ELEM; i++) file->writeLE(pPlayer->InvGrid[i]); SaveItems(file, pPlayer->SpdList, MAXBELTITEMS); SaveItem(file, &pPlayer->HoldItem); file->writeLE(pPlayer->_pIMinDam); file->writeLE(pPlayer->_pIMaxDam); file->writeLE(pPlayer->_pIAC); file->writeLE(pPlayer->_pIBonusDam); file->writeLE(pPlayer->_pIBonusToHit); file->writeLE(pPlayer->_pIBonusAC); file->writeLE(pPlayer->_pIBonusDamMod); file->skip(4); // Alignment file->writeLE(pPlayer->_pISpells); file->writeLE(pPlayer->_pIFlags); file->writeLE(pPlayer->_pIGetHit); file->writeLE(pPlayer->_pISplLvlAdd); file->skip(1); // Unused file->skip(2); // Alignment file->writeLE(pPlayer->_pISplDur); file->writeLE(pPlayer->_pIEnAc); file->writeLE(pPlayer->_pIFMinDam); file->writeLE(pPlayer->_pIFMaxDam); file->writeLE(pPlayer->_pILMinDam); file->writeLE(pPlayer->_pILMaxDam); file->writeLE(pPlayer->_pOilType); file->writeLE(pPlayer->pTownWarps); file->writeLE(pPlayer->pDungMsgs); file->writeLE(pPlayer->pLvlLoad); if (gbIsHellfire) file->writeLE(pPlayer->pDungMsgs2); else file->writeLE(pPlayer->pBattleNet); file->writeLE(pPlayer->pManaShield); file->writeLE(pPlayer->pOriginalCathedral); file->skip(2); // Available bytes file->writeLE(pPlayer->wReflections); file->skip(14); // Available bytes file->writeLE(pPlayer->pDiabloKillLevel); file->writeLE(pPlayer->pDifficulty); file->writeLE(pPlayer->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->_mx); file->writeLE(pMonster->_my); file->writeLE(pMonster->_mfutx); file->writeLE(pMonster->_mfuty); file->writeLE(pMonster->_moldx); file->writeLE(pMonster->_moldy); file->writeLE(pMonster->_mxoff); file->writeLE(pMonster->_myoff); file->writeLE(pMonster->_mxvel); file->writeLE(pMonster->_myvel); file->writeLE(pMonster->_mdir); file->writeLE(pMonster->_menemy); file->writeLE(pMonster->_menemyx); file->writeLE(pMonster->_menemyy); 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->_mVar4); file->writeLE(pMonster->_mVar5); file->writeLE(pMonster->_mVar6); file->writeLE(pMonster->_mVar7); file->writeLE(pMonster->_mVar8); 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->_lastx); file->writeLE(pMonster->_lasty); 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 < SCHAR_MAX ? pMonster->mHit : SCHAR_MAX); // For backwards compatibility file->writeLE(pMonster->mMinDamage); file->writeLE(pMonster->mMaxDamage); file->writeLE(pMonster->mHit2 < SCHAR_MAX ? pMonster->mHit2 : SCHAR_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); 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->_mix); file->writeLE(pMissile->_miy); file->writeLE(pMissile->_mixoff); file->writeLE(pMissile->_miyoff); file->writeLE(pMissile->_mixvel); file->writeLE(pMissile->_miyvel); file->writeLE(pMissile->_misx); file->writeLE(pMissile->_misy); file->writeLE(pMissile->_mitxoff); file->writeLE(pMissile->_mityoff); 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->_ox); file->writeLE(pObject->_oy); 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(pObject->_oAnimWidth2); 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, &premiumitem[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->_qtx); file->writeLE(pQuest->_qty); 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->_lx); file->writeLE(pLight->_ly); file->writeLE(pLight->_lradius); file->writeLE(pLight->_lid); file->writeLE(pLight->_ldel); file->writeLE(pLight->_lunflag); file->skip(4); // Unused file->writeLE(pLight->_lunx); file->writeLE(pLight->_luny); file->writeLE(pLight->_lunr); file->writeLE(pLight->_xoff); file->writeLE(pLight->_yoff); file->writeLE(pLight->_lflags); } static void SavePortal(SaveHelper *file, int i) { PortalStruct *pPortal = &portal[i]; file->writeLE(pPortal->open); file->writeLE(pPortal->x); file->writeLE(pPortal->y); file->writeLE(pPortal->level); file->writeLE(pPortal->ltype); file->writeLE(pPortal->setlvl); } const int DiabloItemSaveSize = 368; const int HellfireItemSaveSize = 372; void SaveHeroItems(PlayerStruct *pPlayer) { size_t items = NUM_INVLOC + NUM_INV_GRID_ELEM + MAXBELTITEMS; SaveHelper file("heroitems", items * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(Uint8)); file.writeLE(gbIsHellfire); SaveItems(&file, pPlayer->InvBody, NUM_INVLOC); SaveItems(&file, pPlayer->InvList, NUM_INV_GRID_ELEM); SaveItems(&file, pPlayer->SpdList, MAXBELTITEMS); } void SaveGame() { SaveHelper file("game", FILEBUFF); if (gbIsSpawn && !gbIsHellfire) file.writeLE(LOAD_LE32("SHAR")); else if (gbIsSpawn && gbIsHellfire) file.writeLE(LOAD_LE32("SHLF")); else if (!gbIsSpawn && gbIsHellfire) file.writeLE(LOAD_LE32("HELF")); else if (!gbIsSpawn && !gbIsHellfire) file.writeLE(LOAD_LE32("RETL")); else app_fatal("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 (int i = 0; i < giNumberOfLevels; i++) { file.writeBE(glSeedTbl[i]); file.writeBE(gnLevelTypeTbl[i]); } plr[myplr].pDifficulty = gnDifficulty; 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 i = 0; i < MAXMONSTERS; i++) file.writeBE(monstkills[i]); if (leveltype != DTYPE_TOWN) { for (int i = 0; i < MAXMONSTERS; i++) file.writeBE(monstactive[i]); for (int i = 0; i < nummonsters; i++) SaveMonster(&file, monstactive[i]); for (int i = 0; i < MAXMISSILES; i++) file.writeLE(missileactive[i]); for (int i = 0; i < MAXMISSILES; i++) file.writeLE(missileavail[i]); for (int i = 0; i < nummissiles; i++) SaveMissile(&file, missileactive[i]); for (int i = 0; i < MAXOBJECTS; i++) file.writeLE(objectactive[i]); for (int i = 0; i < MAXOBJECTS; i++) file.writeLE(objectavail[i]); for (int i = 0; i < nobjects; i++) SaveObject(&file, objectactive[i]); file.writeBE(numlights); for (int i = 0; i < MAXLIGHTS; i++) file.writeLE(lightactive[i]); 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 i = 0; i < MAXITEMS; i++) file.writeLE(itemactive[i]); for (int i = 0; i < MAXITEMS; i++) file.writeLE(itemavail[i]); for (int i = 0; i < numitems; i++) SaveItem(&file, &item[itemactive[i]]); for (int i = 0; i < 128; i++) file.writeLE(UniqueItemFlag[i]); 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(automapflag); file.writeBE(AutoMapScale); file.flush(); gbValidSaveFile = TRUE; pfile_rename_temp_to_perm(); pfile_write_hero(); } void SaveLevel() { DoUnVision(plr[myplr]._px, plr[myplr]._py, 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 i = 0; i < MAXMONSTERS; i++) file.writeBE(monstactive[i]); for (int i = 0; i < nummonsters; i++) SaveMonster(&file, monstactive[i]); for (int i = 0; i < MAXOBJECTS; i++) file.writeLE(objectactive[i]); for (int i = 0; i < MAXOBJECTS; i++) file.writeLE(objectavail[i]); for (int i = 0; i < nobjects; i++) SaveObject(&file, objectactive[i]); } for (int i = 0; i < MAXITEMS; i++) file.writeLE(itemactive[i]); for (int i = 0; i < MAXITEMS; i++) file.writeLE(itemavail[i]); for (int i = 0; i < numitems; i++) SaveItem(&file, &item[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("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 i = 0; i < MAXMONSTERS; i++) monstactive[i] = file.nextBE(); for (int i = 0; i < nummonsters; i++) LoadMonster(&file, monstactive[i]); for (int i = 0; i < MAXOBJECTS; i++) objectactive[i] = file.nextLE(); for (int i = 0; i < MAXOBJECTS; i++) objectavail[i] = 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 i = 0; i < MAXITEMS; i++) itemactive[i] = file.nextLE(); for (int i = 0; i < MAXITEMS; i++) itemavail[i] = 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 (int i = 0; i < MAX_PLRS; i++) { if (plr[i].plractive && currlevel == plr[i].plrlevel) LightList[plr[i]._plid]._lunflag = TRUE; } } DEVILUTION_END_NAMESPACE