|
|
|
|
/**
|
|
|
|
|
* @file loadsave.cpp
|
|
|
|
|
*
|
|
|
|
|
* Implementation of save game functionality.
|
|
|
|
|
*/
|
|
|
|
|
#include "loadsave.h"
|
|
|
|
|
|
|
|
|
|
#include <climits>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
|
|
|
|
|
#include <SDL.h>
|
|
|
|
|
|
|
|
|
|
#include "automap.h"
|
|
|
|
|
#include "codec.h"
|
|
|
|
|
#include "control.h"
|
|
|
|
|
#include "cursor.h"
|
|
|
|
|
#include "dead.h"
|
|
|
|
|
#include "doom.h"
|
|
|
|
|
#include "engine.h"
|
|
|
|
|
#include "engine/point.hpp"
|
|
|
|
|
#include "engine/random.hpp"
|
|
|
|
|
#include "init.h"
|
|
|
|
|
#include "inv.h"
|
|
|
|
|
#include "lighting.h"
|
|
|
|
|
#include "missiles.h"
|
|
|
|
|
#include "mpqapi.h"
|
|
|
|
|
#include "pfile.h"
|
|
|
|
|
#include "stores.h"
|
|
|
|
|
#include "utils/endian.hpp"
|
|
|
|
|
#include "utils/language.h"
|
|
|
|
|
|
|
|
|
|
namespace devilution {
|
|
|
|
|
|
|
|
|
|
bool gbIsHellfireSaveGame;
|
|
|
|
|
uint8_t giNumberOfLevels;
|
|
|
|
|
uint8_t giNumberQuests;
|
|
|
|
|
uint8_t giNumberOfSmithPremiumItems;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
|
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 <class T>
|
|
|
|
|
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<byte[]> m_buffer_;
|
|
|
|
|
uint32_t m_cur_ = 0;
|
|
|
|
|
size_t m_size_;
|
|
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
|
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 <class T>
|
|
|
|
|
T nextLE()
|
|
|
|
|
{
|
|
|
|
|
return SwapLE(next<T>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
|
T nextBE()
|
|
|
|
|
{
|
|
|
|
|
return SwapBE(next<T>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nextBool8()
|
|
|
|
|
{
|
|
|
|
|
return next<uint8_t>() != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nextBool32()
|
|
|
|
|
{
|
|
|
|
|
return next<uint32_t>() != 0;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class SaveHelper {
|
|
|
|
|
const char *m_szFileName_;
|
|
|
|
|
std::unique_ptr<byte[]> 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)
|
|
|
|
|
{
|
|
|
|
|
std::memset(&m_buffer_[m_cur_], 0, len);
|
|
|
|
|
m_cur_ += len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void writeBytes(const void *bytes, size_t len)
|
|
|
|
|
{
|
|
|
|
|
if (!isValid(len))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
memcpy(&m_buffer_[m_cur_], bytes, len);
|
|
|
|
|
m_cur_ += len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
|
void writeLE(T value)
|
|
|
|
|
{
|
|
|
|
|
value = SwapLE(value);
|
|
|
|
|
writeBytes(&value, sizeof(value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
|
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<int32_t>();
|
|
|
|
|
pItem->_iCreateInfo = file->nextLE<uint16_t>();
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
pItem->_itype = static_cast<item_type>(file->nextLE<uint32_t>());
|
|
|
|
|
pItem->position.x = file->nextLE<int32_t>();
|
|
|
|
|
pItem->position.y = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iAnimFlag = file->nextBool32();
|
|
|
|
|
file->skip(4); // Skip pointer _iAnimData
|
|
|
|
|
pItem->AnimInfo = {};
|
|
|
|
|
pItem->AnimInfo.NumberOfFrames = file->nextLE<int32_t>();
|
|
|
|
|
pItem->AnimInfo.CurrentFrame = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(8); // Skip _iAnimWidth and _iAnimWidth2
|
|
|
|
|
file->skip(4); // Unused since 1.02
|
|
|
|
|
pItem->_iSelFlag = file->nextLE<uint8_t>();
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
pItem->_iPostDraw = file->nextBool32();
|
|
|
|
|
pItem->_iIdentified = file->nextBool32();
|
|
|
|
|
pItem->_iMagical = static_cast<item_quality>(file->nextLE<int8_t>());
|
|
|
|
|
file->nextBytes(pItem->_iName, 64);
|
|
|
|
|
file->nextBytes(pItem->_iIName, 64);
|
|
|
|
|
pItem->_iLoc = static_cast<item_equip_type>(file->nextLE<int8_t>());
|
|
|
|
|
pItem->_iClass = static_cast<item_class>(file->nextLE<uint8_t>());
|
|
|
|
|
file->skip(1); // Alignment
|
|
|
|
|
pItem->_iCurs = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_ivalue = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iIvalue = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iMinDam = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iMaxDam = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iAC = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iFlags = file->nextLE<uint32_t>();
|
|
|
|
|
pItem->_iMiscId = static_cast<item_misc_id>(file->nextLE<int32_t>());
|
|
|
|
|
pItem->_iSpell = static_cast<spell_id>(file->nextLE<int32_t>());
|
|
|
|
|
pItem->_iCharges = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iMaxCharges = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iDurability = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iMaxDur = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLDam = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLToHit = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLAC = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLStr = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLMag = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLDex = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLVit = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLFR = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLLR = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLMR = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLMana = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLHP = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLDamMod = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLGetHit = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLLight = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iSplLvlAdd = file->nextLE<int8_t>();
|
|
|
|
|
pItem->_iRequest = file->nextBool8();
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
pItem->_iUid = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iFMinDam = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iFMaxDam = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iLMinDam = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iLMaxDam = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPLEnAc = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iPrePower = static_cast<item_effect_type>(file->nextLE<int8_t>());
|
|
|
|
|
pItem->_iSufPower = static_cast<item_effect_type>(file->nextLE<int8_t>());
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
pItem->_iVAdd1 = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iVMult1 = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iVAdd2 = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iVMult2 = file->nextLE<int32_t>();
|
|
|
|
|
pItem->_iMinStr = file->nextLE<int8_t>();
|
|
|
|
|
pItem->_iMinMag = file->nextLE<uint8_t>();
|
|
|
|
|
pItem->_iMinDex = file->nextLE<int8_t>();
|
|
|
|
|
file->skip(1); // Alignment
|
|
|
|
|
pItem->_iStatFlag = file->nextBool32();
|
|
|
|
|
pItem->IDidx = static_cast<_item_indexes>(file->nextLE<int32_t>());
|
|
|
|
|
if (gbIsSpawn) {
|
|
|
|
|
pItem->IDidx = RemapItemIdxFromSpawn(pItem->IDidx);
|
|
|
|
|
}
|
|
|
|
|
if (!gbIsHellfireSaveGame) {
|
|
|
|
|
pItem->IDidx = RemapItemIdxFromDiablo(pItem->IDidx);
|
|
|
|
|
}
|
|
|
|
|
pItem->dwBuff = file->nextLE<uint32_t>();
|
|
|
|
|
if (gbIsHellfireSaveGame)
|
|
|
|
|
pItem->_iDamAcFlags = file->nextLE<uint32_t>();
|
|
|
|
|
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<PLR_MODE>(file->nextLE<int32_t>());
|
|
|
|
|
|
|
|
|
|
for (int8_t &step : player.walkpath) {
|
|
|
|
|
step = file->nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
player.plractive = file->nextBool8();
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
player.destAction = static_cast<action_id>(file->nextLE<int32_t>());
|
|
|
|
|
player.destParam1 = file->nextLE<int32_t>();
|
|
|
|
|
player.destParam2 = file->nextLE<int32_t>();
|
|
|
|
|
player.destParam3 = static_cast<Direction>(file->nextLE<int32_t>());
|
|
|
|
|
player.destParam4 = file->nextLE<int32_t>();
|
|
|
|
|
player.plrlevel = file->nextLE<int32_t>();
|
|
|
|
|
player.position.tile.x = file->nextLE<int32_t>();
|
|
|
|
|
player.position.tile.y = file->nextLE<int32_t>();
|
|
|
|
|
player.position.future.x = file->nextLE<int32_t>();
|
|
|
|
|
player.position.future.y = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(8); // Skip _ptargx and _ptargy
|
|
|
|
|
player.position.last.x = file->nextLE<int32_t>();
|
|
|
|
|
player.position.last.y = file->nextLE<int32_t>();
|
|
|
|
|
player.position.old.x = file->nextLE<int32_t>();
|
|
|
|
|
player.position.old.y = file->nextLE<int32_t>();
|
|
|
|
|
player.position.offset.deltaX = file->nextLE<int32_t>();
|
|
|
|
|
player.position.offset.deltaY = file->nextLE<int32_t>();
|
|
|
|
|
player.position.velocity.deltaX = file->nextLE<int32_t>();
|
|
|
|
|
player.position.velocity.deltaY = file->nextLE<int32_t>();
|
|
|
|
|
player._pdir = static_cast<Direction>(file->nextLE<int32_t>());
|
|
|
|
|
file->skip(4); // Unused
|
|
|
|
|
player._pgfxnum = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(4); // Skip pointer pData
|
|
|
|
|
player.AnimInfo = {};
|
|
|
|
|
player.AnimInfo.TicksPerFrame = file->nextLE<int32_t>() + 1;
|
|
|
|
|
player.AnimInfo.TickCounterOfCurrentFrame = file->nextLE<int32_t>();
|
|
|
|
|
player.AnimInfo.NumberOfFrames = file->nextLE<int32_t>();
|
|
|
|
|
player.AnimInfo.CurrentFrame = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(4); // Skip _pAnimWidth
|
|
|
|
|
file->skip(4); // Skip _pAnimWidth2
|
|
|
|
|
file->skip(4); // Skip _peflag
|
|
|
|
|
player._plid = file->nextLE<int32_t>();
|
|
|
|
|
player._pvid = file->nextLE<int32_t>();
|
|
|
|
|
|
|
|
|
|
player._pSpell = static_cast<spell_id>(file->nextLE<int32_t>());
|
|
|
|
|
player._pSplType = static_cast<spell_type>(file->nextLE<int8_t>());
|
|
|
|
|
player._pSplFrom = file->nextLE<int8_t>();
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
player._pTSpell = static_cast<spell_id>(file->nextLE<int32_t>());
|
|
|
|
|
player._pTSplType = static_cast<spell_type>(file->nextLE<int8_t>());
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
player._pRSpell = static_cast<spell_id>(file->nextLE<int32_t>());
|
|
|
|
|
player._pRSplType = static_cast<spell_type>(file->nextLE<int8_t>());
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
player._pSBkSpell = static_cast<spell_id>(file->nextLE<int32_t>());
|
|
|
|
|
player._pSBkSplType = static_cast<spell_type>(file->nextLE<int8_t>());
|
|
|
|
|
for (int8_t &spellLevel : player._pSplLvl)
|
|
|
|
|
spellLevel = file->nextLE<int8_t>();
|
|
|
|
|
file->skip(7); // Alignment
|
|
|
|
|
player._pMemSpells = file->nextLE<uint64_t>();
|
|
|
|
|
player._pAblSpells = file->nextLE<uint64_t>();
|
|
|
|
|
player._pScrlSpells = file->nextLE<uint64_t>();
|
|
|
|
|
player._pSpellFlags = file->nextLE<uint8_t>();
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
for (auto &spell : player._pSplHotKey)
|
|
|
|
|
spell = static_cast<spell_id>(file->nextLE<int32_t>());
|
|
|
|
|
for (auto &spellType : player._pSplTHotKey)
|
|
|
|
|
spellType = static_cast<spell_type>(file->nextLE<int8_t>());
|
|
|
|
|
|
|
|
|
|
player._pwtype = static_cast<player_weapon_type>(file->nextLE<int32_t>());
|
|
|
|
|
player._pBlockFlag = file->nextBool8();
|
|
|
|
|
player._pInvincible = file->nextBool8();
|
|
|
|
|
player._pLightRad = file->nextLE<int8_t>();
|
|
|
|
|
player._pLvlChanging = file->nextBool8();
|
|
|
|
|
|
|
|
|
|
file->nextBytes(player._pName, PLR_NAME_LEN);
|
|
|
|
|
player._pClass = static_cast<HeroClass>(file->nextLE<int8_t>());
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
player._pStrength = file->nextLE<int32_t>();
|
|
|
|
|
player._pBaseStr = file->nextLE<int32_t>();
|
|
|
|
|
player._pMagic = file->nextLE<int32_t>();
|
|
|
|
|
player._pBaseMag = file->nextLE<int32_t>();
|
|
|
|
|
player._pDexterity = file->nextLE<int32_t>();
|
|
|
|
|
player._pBaseDex = file->nextLE<int32_t>();
|
|
|
|
|
player._pVitality = file->nextLE<int32_t>();
|
|
|
|
|
player._pBaseVit = file->nextLE<int32_t>();
|
|
|
|
|
player._pStatPts = file->nextLE<int32_t>();
|
|
|
|
|
player._pDamageMod = file->nextLE<int32_t>();
|
|
|
|
|
player._pBaseToBlk = file->nextLE<int32_t>();
|
|
|
|
|
if (player._pBaseToBlk == 0)
|
|
|
|
|
player._pBaseToBlk = ToBlkTbl[static_cast<std::size_t>(player._pClass)];
|
|
|
|
|
player._pHPBase = file->nextLE<int32_t>();
|
|
|
|
|
player._pMaxHPBase = file->nextLE<int32_t>();
|
|
|
|
|
player._pHitPoints = file->nextLE<int32_t>();
|
|
|
|
|
player._pMaxHP = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(sizeof(int32_t)); // hpPer - derived value from hp and maxHP.
|
|
|
|
|
player._pManaBase = file->nextLE<int32_t>();
|
|
|
|
|
player._pMaxManaBase = file->nextLE<int32_t>();
|
|
|
|
|
player._pMana = file->nextLE<int32_t>();
|
|
|
|
|
player._pMaxMana = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(sizeof(int32_t)); // manaPer - derived value from mana and maxMana
|
|
|
|
|
player._pLevel = file->nextLE<int8_t>();
|
|
|
|
|
player._pMaxLvl = file->nextLE<int8_t>();
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
player._pExperience = file->nextLE<int32_t>();
|
|
|
|
|
player._pMaxExp = file->nextLE<int32_t>();
|
|
|
|
|
player._pNextExper = file->nextLE<int32_t>();
|
|
|
|
|
player._pArmorClass = file->nextLE<int8_t>();
|
|
|
|
|
player._pMagResist = file->nextLE<int8_t>();
|
|
|
|
|
player._pFireResist = file->nextLE<int8_t>();
|
|
|
|
|
player._pLghtResist = file->nextLE<int8_t>();
|
|
|
|
|
player._pGold = file->nextLE<int32_t>();
|
|
|
|
|
|
|
|
|
|
player._pInfraFlag = file->nextBool32();
|
|
|
|
|
player.position.temp.x = file->nextLE<int32_t>();
|
|
|
|
|
player.position.temp.y = file->nextLE<int32_t>();
|
|
|
|
|
player.tempDirection = static_cast<Direction>(file->nextLE<int32_t>());
|
|
|
|
|
player._pVar4 = file->nextLE<int32_t>();
|
|
|
|
|
player._pVar5 = file->nextLE<int32_t>();
|
|
|
|
|
player.position.offset2.deltaX = file->nextLE<int32_t>();
|
|
|
|
|
player.position.offset2.deltaY = file->nextLE<int32_t>();
|
|
|
|
|
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<int32_t>();
|
|
|
|
|
file->skip(4); // skip _pNWidth
|
|
|
|
|
file->skip(4 * 8); // Skip pointers _pWAnim
|
|
|
|
|
player._pWFrames = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(4); // skip _pWWidth
|
|
|
|
|
file->skip(4 * 8); // Skip pointers _pAAnim
|
|
|
|
|
player._pAFrames = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(4); // skip _pAWidth
|
|
|
|
|
player._pAFNum = file->nextLE<int32_t>();
|
|
|
|
|
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<int32_t>();
|
|
|
|
|
file->skip(4); // skip _pSWidth
|
|
|
|
|
player._pSFNum = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(4 * 8); // Skip pointers _pHAnim
|
|
|
|
|
player._pHFrames = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(4); // skip _pHWidth
|
|
|
|
|
file->skip(4 * 8); // Skip pointers _pDAnim
|
|
|
|
|
player._pDFrames = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(4); // skip _pDWidth
|
|
|
|
|
file->skip(4 * 8); // Skip pointers _pBAnim
|
|
|
|
|
player._pBFrames = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(4); // skip _pBWidth
|
|
|
|
|
|
|
|
|
|
LoadItems(file, NUM_INVLOC, player.InvBody);
|
|
|
|
|
LoadItems(file, NUM_INV_GRID_ELEM, player.InvList);
|
|
|
|
|
player._pNumInv = file->nextLE<int32_t>();
|
|
|
|
|
for (int8_t &cell : player.InvGrid)
|
|
|
|
|
cell = file->nextLE<int8_t>();
|
|
|
|
|
LoadItems(file, MAXBELTITEMS, player.SpdList);
|
|
|
|
|
LoadItemData(file, &player.HoldItem);
|
|
|
|
|
|
|
|
|
|
player._pIMinDam = file->nextLE<int32_t>();
|
|
|
|
|
player._pIMaxDam = file->nextLE<int32_t>();
|
|
|
|
|
player._pIAC = file->nextLE<int32_t>();
|
|
|
|
|
player._pIBonusDam = file->nextLE<int32_t>();
|
|
|
|
|
player._pIBonusToHit = file->nextLE<int32_t>();
|
|
|
|
|
player._pIBonusAC = file->nextLE<int32_t>();
|
|
|
|
|
player._pIBonusDamMod = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(4); // Alignment
|
|
|
|
|
|
|
|
|
|
player._pISpells = file->nextLE<uint64_t>();
|
|
|
|
|
player._pIFlags = file->nextLE<int32_t>();
|
|
|
|
|
player._pIGetHit = file->nextLE<int32_t>();
|
|
|
|
|
player._pISplLvlAdd = file->nextLE<int8_t>();
|
|
|
|
|
file->skip(1); // Unused
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
player._pISplDur = file->nextLE<int32_t>();
|
|
|
|
|
player._pIEnAc = file->nextLE<int32_t>();
|
|
|
|
|
player._pIFMinDam = file->nextLE<int32_t>();
|
|
|
|
|
player._pIFMaxDam = file->nextLE<int32_t>();
|
|
|
|
|
player._pILMinDam = file->nextLE<int32_t>();
|
|
|
|
|
player._pILMaxDam = file->nextLE<int32_t>();
|
|
|
|
|
player._pOilType = static_cast<item_misc_id>(file->nextLE<int32_t>());
|
|
|
|
|
player.pTownWarps = file->nextLE<uint8_t>();
|
|
|
|
|
player.pDungMsgs = file->nextLE<uint8_t>();
|
|
|
|
|
player.pLvlLoad = file->nextLE<uint8_t>();
|
|
|
|
|
|
|
|
|
|
if (gbIsHellfireSaveGame) {
|
|
|
|
|
player.pDungMsgs2 = file->nextLE<uint8_t>();
|
|
|
|
|
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<uint16_t>();
|
|
|
|
|
file->skip(14); // Available bytes
|
|
|
|
|
|
|
|
|
|
player.pDiabloKillLevel = file->nextLE<uint32_t>();
|
|
|
|
|
player.pDifficulty = static_cast<_difficulty>(file->nextLE<uint32_t>());
|
|
|
|
|
player.pDamAcFlags = file->nextLE<uint32_t>();
|
|
|
|
|
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<int32_t>();
|
|
|
|
|
pMonster->_mmode = static_cast<MON_MODE>(file->nextLE<int32_t>());
|
|
|
|
|
pMonster->_mgoal = static_cast<monster_goal>(file->nextLE<uint8_t>());
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
pMonster->_mgoalvar1 = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->_mgoalvar2 = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->_mgoalvar3 = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(4); // Unused
|
|
|
|
|
pMonster->_pathcount = file->nextLE<uint8_t>();
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
pMonster->position.tile.x = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->position.tile.y = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->position.future.x = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->position.future.y = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->position.old.x = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->position.old.y = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->position.offset.deltaX = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->position.offset.deltaY = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->position.velocity.deltaX = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->position.velocity.deltaY = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->_mdir = static_cast<Direction>(file->nextLE<int32_t>());
|
|
|
|
|
pMonster->_menemy = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->enemyPosition.x = file->nextLE<uint8_t>();
|
|
|
|
|
pMonster->enemyPosition.y = file->nextLE<uint8_t>();
|
|
|
|
|
file->skip(2); // Unused
|
|
|
|
|
|
|
|
|
|
file->skip(4); // Skip pointer _mAnimData
|
|
|
|
|
pMonster->AnimInfo = {};
|
|
|
|
|
pMonster->AnimInfo.TicksPerFrame = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->AnimInfo.TickCounterOfCurrentFrame = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->AnimInfo.NumberOfFrames = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->AnimInfo.CurrentFrame = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(4); // Skip _meflag
|
|
|
|
|
pMonster->_mDelFlag = file->nextBool32();
|
|
|
|
|
pMonster->_mVar1 = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->_mVar2 = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->_mVar3 = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->position.temp.x = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->position.temp.y = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->position.offset2.deltaX = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->position.offset2.deltaY = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(4); // Skip actionFrame
|
|
|
|
|
pMonster->_mmaxhp = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->_mhitpoints = file->nextLE<int32_t>();
|
|
|
|
|
|
|
|
|
|
pMonster->_mAi = static_cast<_mai_id>(file->nextLE<uint8_t>());
|
|
|
|
|
pMonster->_mint = file->nextLE<uint8_t>();
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
pMonster->_mFlags = file->nextLE<uint32_t>();
|
|
|
|
|
pMonster->_msquelch = file->nextLE<uint8_t>();
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
file->skip(4); // Unused
|
|
|
|
|
pMonster->position.last.x = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->position.last.y = file->nextLE<int32_t>();
|
|
|
|
|
pMonster->_mRndSeed = file->nextLE<uint32_t>();
|
|
|
|
|
pMonster->_mAISeed = file->nextLE<uint32_t>();
|
|
|
|
|
file->skip(4); // Unused
|
|
|
|
|
|
|
|
|
|
pMonster->_uniqtype = file->nextLE<uint8_t>();
|
|
|
|
|
pMonster->_uniqtrans = file->nextLE<uint8_t>();
|
|
|
|
|
pMonster->_udeadval = file->nextLE<int8_t>();
|
|
|
|
|
|
|
|
|
|
pMonster->mWhoHit = file->nextLE<int8_t>();
|
|
|
|
|
pMonster->mLevel = file->nextLE<int8_t>();
|
|
|
|
|
file->skip(1); // Alignment
|
|
|
|
|
pMonster->mExp = file->nextLE<uint16_t>();
|
|
|
|
|
|
|
|
|
|
if (i < MAX_PLRS) // Don't skip for golems
|
|
|
|
|
pMonster->mHit = file->nextLE<uint8_t>();
|
|
|
|
|
else
|
|
|
|
|
file->skip(1); // Skip mHit as it's already initialized
|
|
|
|
|
pMonster->mMinDamage = file->nextLE<uint8_t>();
|
|
|
|
|
pMonster->mMaxDamage = file->nextLE<uint8_t>();
|
|
|
|
|
file->skip(1); // Skip mHit2 as it's already initialized
|
|
|
|
|
pMonster->mMinDamage2 = file->nextLE<uint8_t>();
|
|
|
|
|
pMonster->mMaxDamage2 = file->nextLE<uint8_t>();
|
|
|
|
|
pMonster->mArmorClass = file->nextLE<uint8_t>();
|
|
|
|
|
file->skip(1); // Alignment
|
|
|
|
|
pMonster->mMagicRes = file->nextLE<uint16_t>();
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
|
|
|
|
|
pMonster->mtalkmsg = static_cast<_speech_id>(file->nextLE<int32_t>());
|
|
|
|
|
if (pMonster->mtalkmsg == TEXT_KING1) // Fix original bad mapping of NONE for monsters
|
|
|
|
|
pMonster->mtalkmsg = TEXT_NONE;
|
|
|
|
|
pMonster->leader = file->nextLE<uint8_t>();
|
|
|
|
|
pMonster->leaderflag = file->nextLE<uint8_t>();
|
|
|
|
|
pMonster->packsize = file->nextLE<uint8_t>();
|
|
|
|
|
pMonster->mlid = file->nextLE<int8_t>();
|
|
|
|
|
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<int32_t>();
|
|
|
|
|
pMissile->position.tile.x = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->position.tile.y = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->position.offset.deltaX = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->position.offset.deltaY = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->position.velocity.deltaX = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->position.velocity.deltaY = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->position.start.x = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->position.start.y = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->position.traveled.deltaX = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->position.traveled.deltaY = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_mimfnum = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_mispllvl = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miDelFlag = file->nextBool32();
|
|
|
|
|
pMissile->_miAnimType = file->nextLE<uint8_t>();
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
pMissile->_miAnimFlags = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(4); // Skip pointer _miAnimData
|
|
|
|
|
pMissile->_miAnimDelay = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miAnimLen = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miAnimWidth = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miAnimWidth2 = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miAnimCnt = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miAnimAdd = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miAnimFrame = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miDrawFlag = file->nextBool32();
|
|
|
|
|
pMissile->_miLightFlag = file->nextBool32();
|
|
|
|
|
pMissile->_miPreFlag = file->nextBool32();
|
|
|
|
|
pMissile->_miUniqTrans = file->nextLE<uint32_t>();
|
|
|
|
|
pMissile->_mirange = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_misource = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_micaster = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_midam = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miHitFlag = file->nextBool32();
|
|
|
|
|
pMissile->_midist = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_mlid = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_mirnd = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miVar1 = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miVar2 = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miVar3 = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miVar4 = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miVar5 = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miVar6 = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->_miVar7 = file->nextLE<int32_t>();
|
|
|
|
|
pMissile->limitReached = file->nextBool32();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void LoadObject(LoadHelper *file, int i)
|
|
|
|
|
{
|
|
|
|
|
ObjectStruct *pObject = &object[i];
|
|
|
|
|
|
|
|
|
|
pObject->_otype = static_cast<_object_id>(file->nextLE<int32_t>());
|
|
|
|
|
pObject->position.x = file->nextLE<int32_t>();
|
|
|
|
|
pObject->position.y = file->nextLE<int32_t>();
|
|
|
|
|
pObject->_oLight = file->nextBool32();
|
|
|
|
|
pObject->_oAnimFlag = file->nextLE<uint32_t>();
|
|
|
|
|
file->skip(4); // Skip pointer _oAnimData
|
|
|
|
|
pObject->_oAnimDelay = file->nextLE<int32_t>();
|
|
|
|
|
pObject->_oAnimCnt = file->nextLE<int32_t>();
|
|
|
|
|
pObject->_oAnimLen = file->nextLE<uint32_t>();
|
|
|
|
|
pObject->_oAnimFrame = file->nextLE<uint32_t>();
|
|
|
|
|
pObject->_oAnimWidth = file->nextLE<int32_t>();
|
|
|
|
|
file->skip(4); // Skip _oAnimWidth2
|
|
|
|
|
pObject->_oDelFlag = file->nextBool32();
|
|
|
|
|
pObject->_oBreak = file->nextLE<int8_t>();
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
pObject->_oSolidFlag = file->nextBool32();
|
|
|
|
|
pObject->_oMissFlag = file->nextBool32();
|
|
|
|
|
|
|
|
|
|
pObject->_oSelFlag = file->nextLE<int8_t>();
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
pObject->_oPreFlag = file->nextBool32();
|
|
|
|
|
pObject->_oTrapFlag = file->nextBool32();
|
|
|
|
|
pObject->_oDoorFlag = file->nextBool32();
|
|
|
|
|
pObject->_olid = file->nextLE<int32_t>();
|
|
|
|
|
pObject->_oRndSeed = file->nextLE<uint32_t>();
|
|
|
|
|
pObject->_oVar1 = file->nextLE<int32_t>();
|
|
|
|
|
pObject->_oVar2 = file->nextLE<int32_t>();
|
|
|
|
|
pObject->_oVar3 = file->nextLE<int32_t>();
|
|
|
|
|
pObject->_oVar4 = file->nextLE<int32_t>();
|
|
|
|
|
pObject->_oVar5 = file->nextLE<int32_t>();
|
|
|
|
|
pObject->_oVar6 = file->nextLE<uint32_t>();
|
|
|
|
|
pObject->_oVar7 = static_cast<_speech_id>(file->nextLE<int32_t>());
|
|
|
|
|
pObject->_oVar8 = file->nextLE<int32_t>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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<uint8_t>();
|
|
|
|
|
pQuest->_qtype = file->nextLE<uint8_t>();
|
|
|
|
|
pQuest->_qactive = static_cast<quest_state>(file->nextLE<uint8_t>());
|
|
|
|
|
pQuest->_qlvltype = static_cast<dungeon_type>(file->nextLE<uint8_t>());
|
|
|
|
|
pQuest->position.x = file->nextLE<int32_t>();
|
|
|
|
|
pQuest->position.y = file->nextLE<int32_t>();
|
|
|
|
|
pQuest->_qslvl = static_cast<_setlevels>(file->nextLE<uint8_t>());
|
|
|
|
|
pQuest->_qidx = file->nextLE<uint8_t>();
|
|
|
|
|
if (gbIsHellfireSaveGame) {
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
pQuest->_qmsg = static_cast<_speech_id>(file->nextLE<int32_t>());
|
|
|
|
|
} else {
|
|
|
|
|
pQuest->_qmsg = static_cast<_speech_id>(file->nextLE<uint8_t>());
|
|
|
|
|
}
|
|
|
|
|
pQuest->_qvar1 = file->nextLE<uint8_t>();
|
|
|
|
|
pQuest->_qvar2 = file->nextLE<uint8_t>();
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
if (!gbIsHellfireSaveGame)
|
|
|
|
|
file->skip(1); // Alignment
|
|
|
|
|
pQuest->_qlog = file->nextBool32();
|
|
|
|
|
|
|
|
|
|
ReturnLvlX = file->nextBE<int32_t>();
|
|
|
|
|
ReturnLvlY = file->nextBE<int32_t>();
|
|
|
|
|
ReturnLvl = file->nextBE<int32_t>();
|
|
|
|
|
ReturnLvlT = static_cast<dungeon_type>(file->nextBE<int32_t>());
|
|
|
|
|
DoomQuestState = file->nextBE<int32_t>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void LoadLighting(LoadHelper *file, LightListStruct *pLight)
|
|
|
|
|
{
|
|
|
|
|
pLight->position.tile.x = file->nextLE<int32_t>();
|
|
|
|
|
pLight->position.tile.y = file->nextLE<int32_t>();
|
|
|
|
|
pLight->_lradius = file->nextLE<int32_t>();
|
|
|
|
|
pLight->_lid = file->nextLE<int32_t>();
|
|
|
|
|
pLight->_ldel = file->nextBool32();
|
|
|
|
|
pLight->_lunflag = file->nextBool32();
|
|
|
|
|
file->skip(4); // Unused
|
|
|
|
|
pLight->position.old.x = file->nextLE<int32_t>();
|
|
|
|
|
pLight->position.old.y = file->nextLE<int32_t>();
|
|
|
|
|
pLight->oldRadius = file->nextLE<int32_t>();
|
|
|
|
|
pLight->position.offset.x = file->nextLE<int32_t>();
|
|
|
|
|
pLight->position.offset.y = file->nextLE<int32_t>();
|
|
|
|
|
pLight->_lflags = file->nextBool32();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void LoadPortal(LoadHelper *file, int i)
|
|
|
|
|
{
|
|
|
|
|
PortalStruct *pPortal = &portal[i];
|
|
|
|
|
|
|
|
|
|
pPortal->open = file->nextBool32();
|
|
|
|
|
pPortal->position.x = file->nextLE<int32_t>();
|
|
|
|
|
pPortal->position.y = file->nextLE<int32_t>();
|
|
|
|
|
pPortal->level = file->nextLE<int32_t>();
|
|
|
|
|
pPortal->ltype = static_cast<dungeon_type>(file->nextLE<int32_t>());
|
|
|
|
|
pPortal->setlvl = file->nextBool32();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_item_indexes RemapItemIdxFromDiablo(_item_indexes i)
|
|
|
|
|
{
|
|
|
|
|
constexpr auto GetItemIdValue = [](int i) -> int {
|
|
|
|
|
if (i == IDI_SORCERER) {
|
|
|
|
|
return 166;
|
|
|
|
|
}
|
|
|
|
|
if (i >= 156) {
|
|
|
|
|
i += 5; // Hellfire exclusive items
|
|
|
|
|
}
|
|
|
|
|
if (i >= 88) {
|
|
|
|
|
i += 1; // Scroll of Search
|
|
|
|
|
}
|
|
|
|
|
if (i >= 83) {
|
|
|
|
|
i += 4; // Oils
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return i;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return static_cast<_item_indexes>(GetItemIdValue(i));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_item_indexes RemapItemIdxToDiablo(_item_indexes i)
|
|
|
|
|
{
|
|
|
|
|
constexpr auto GetItemIdValue = [](int i) -> int {
|
|
|
|
|
if (i == 166) {
|
|
|
|
|
return IDI_SORCERER;
|
|
|
|
|
}
|
|
|
|
|
if ((i >= 83 && i <= 86) || i == 92 || i >= 161) {
|
|
|
|
|
return -1; // Hellfire exclusive items
|
|
|
|
|
}
|
|
|
|
|
if (i >= 93) {
|
|
|
|
|
i -= 1; // Scroll of Search
|
|
|
|
|
}
|
|
|
|
|
if (i >= 87) {
|
|
|
|
|
i -= 4; // Oils
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return i;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return static_cast<_item_indexes>(GetItemIdValue(i));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_item_indexes RemapItemIdxFromSpawn(_item_indexes i)
|
|
|
|
|
{
|
|
|
|
|
constexpr auto GetItemIdValue = [](int i) {
|
|
|
|
|
if (i >= 62) {
|
|
|
|
|
i += 9; // Medium and heavy armors
|
|
|
|
|
}
|
|
|
|
|
if (i >= 96) {
|
|
|
|
|
i += 1; // Scroll of Stone Curse
|
|
|
|
|
}
|
|
|
|
|
if (i >= 98) {
|
|
|
|
|
i += 1; // Scroll of Guardian
|
|
|
|
|
}
|
|
|
|
|
if (i >= 99) {
|
|
|
|
|
i += 1; // Scroll of ...
|
|
|
|
|
}
|
|
|
|
|
if (i >= 101) {
|
|
|
|
|
i += 1; // Scroll of Golem
|
|
|
|
|
}
|
|
|
|
|
if (i >= 102) {
|
|
|
|
|
i += 1; // Scroll of None
|
|
|
|
|
}
|
|
|
|
|
if (i >= 104) {
|
|
|
|
|
i += 1; // Scroll of Apocalypse
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return i;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return static_cast<_item_indexes>(GetItemIdValue(i));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_item_indexes RemapItemIdxToSpawn(_item_indexes i)
|
|
|
|
|
{
|
|
|
|
|
constexpr auto GetItemIdValue = [](int i) {
|
|
|
|
|
if (i >= 104) {
|
|
|
|
|
i -= 1; // Scroll of Apocalypse
|
|
|
|
|
}
|
|
|
|
|
if (i >= 102) {
|
|
|
|
|
i -= 1; // Scroll of None
|
|
|
|
|
}
|
|
|
|
|
if (i >= 101) {
|
|
|
|
|
i -= 1; // Scroll of Golem
|
|
|
|
|
}
|
|
|
|
|
if (i >= 99) {
|
|
|
|
|
i -= 1; // Scroll of ...
|
|
|
|
|
}
|
|
|
|
|
if (i >= 98) {
|
|
|
|
|
i -= 1; // Scroll of Guardian
|
|
|
|
|
}
|
|
|
|
|
if (i >= 96) {
|
|
|
|
|
i -= 1; // Scroll of Stone Curse
|
|
|
|
|
}
|
|
|
|
|
if (i >= 71) {
|
|
|
|
|
i -= 9; // Medium and heavy armors
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return i;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return static_cast<_item_indexes>(GetItemIdValue(i));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsHeaderValid(uint32_t magicNumber)
|
|
|
|
|
{
|
|
|
|
|
gbIsHellfireSaveGame = false;
|
|
|
|
|
if (magicNumber == LoadLE32("SHAR")) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (magicNumber == LoadLE32("SHLF")) {
|
|
|
|
|
gbIsHellfireSaveGame = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (!gbIsSpawn && magicNumber == LoadLE32("RETL")) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (!gbIsSpawn && magicNumber == LoadLE32("HELF")) {
|
|
|
|
|
gbIsHellfireSaveGame = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void ConvertLevels()
|
|
|
|
|
{
|
|
|
|
|
// Backup current level state
|
|
|
|
|
bool tmpSetlevel = setlevel;
|
|
|
|
|
_setlevels tmpSetlvlnum = setlvlnum;
|
|
|
|
|
int tmpCurrlevel = currlevel;
|
|
|
|
|
dungeon_type tmpLeveltype = leveltype;
|
|
|
|
|
|
|
|
|
|
gbSkipSync = true;
|
|
|
|
|
|
|
|
|
|
setlevel = false; // Convert regular levels
|
|
|
|
|
for (int i = 0; i < giNumberOfLevels; i++) {
|
|
|
|
|
currlevel = i;
|
|
|
|
|
if (!LevelFileExists())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
leveltype = gnLevelTypeTbl[i];
|
|
|
|
|
|
|
|
|
|
LoadLevel();
|
|
|
|
|
SaveLevel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setlevel = true; // Convert quest levels
|
|
|
|
|
for (auto &quest : quests) {
|
|
|
|
|
if (quest._qactive == QUEST_NOTAVAIL) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leveltype = quest._qlvltype;
|
|
|
|
|
if (leveltype == DTYPE_NONE) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setlvlnum = quest._qslvl;
|
|
|
|
|
if (!LevelFileExists())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
LoadLevel();
|
|
|
|
|
SaveLevel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gbSkipSync = false;
|
|
|
|
|
|
|
|
|
|
// Restor current level state
|
|
|
|
|
setlevel = tmpSetlevel;
|
|
|
|
|
setlvlnum = tmpSetlvlnum;
|
|
|
|
|
currlevel = tmpCurrlevel;
|
|
|
|
|
leveltype = tmpLeveltype;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadHotkeys()
|
|
|
|
|
{
|
|
|
|
|
LoadHelper file("hotkeys");
|
|
|
|
|
if (!file.isValid())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto &myPlayer = plr[myplr];
|
|
|
|
|
|
|
|
|
|
for (auto &spellId : myPlayer._pSplHotKey) {
|
|
|
|
|
spellId = static_cast<spell_id>(file.nextLE<int32_t>());
|
|
|
|
|
}
|
|
|
|
|
for (auto &spellType : myPlayer._pSplTHotKey) {
|
|
|
|
|
spellType = static_cast<spell_type>(file.nextLE<int8_t>());
|
|
|
|
|
}
|
|
|
|
|
myPlayer._pRSpell = static_cast<spell_id>(file.nextLE<int32_t>());
|
|
|
|
|
myPlayer._pRSplType = static_cast<spell_type>(file.nextLE<int8_t>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SaveHotkeys()
|
|
|
|
|
{
|
|
|
|
|
auto &myPlayer = plr[myplr];
|
|
|
|
|
|
|
|
|
|
const size_t nHotkeyTypes = sizeof(myPlayer._pSplHotKey) / sizeof(myPlayer._pSplHotKey[0]);
|
|
|
|
|
const size_t nHotkeySpells = sizeof(myPlayer._pSplTHotKey) / sizeof(myPlayer._pSplTHotKey[0]);
|
|
|
|
|
|
|
|
|
|
SaveHelper file("hotkeys", (nHotkeyTypes * 4) + nHotkeySpells + 4 + 1);
|
|
|
|
|
|
|
|
|
|
for (auto &spellId : myPlayer._pSplHotKey) {
|
|
|
|
|
file.writeLE<int32_t>(spellId);
|
|
|
|
|
}
|
|
|
|
|
for (auto &spellType : myPlayer._pSplTHotKey) {
|
|
|
|
|
file.writeLE<uint8_t>(spellType);
|
|
|
|
|
}
|
|
|
|
|
file.writeLE<int32_t>(myPlayer._pRSpell);
|
|
|
|
|
file.writeLE<uint8_t>(myPlayer._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(PlayerStruct &player)
|
|
|
|
|
{
|
|
|
|
|
for (int i = NUM_INV_GRID_ELEM; i > 0; i--) {
|
|
|
|
|
int idx = player.InvGrid[i - 1];
|
|
|
|
|
if (idx > 0 && player.InvList[idx - 1].isEmpty()) {
|
|
|
|
|
player.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<uint32_t>()))
|
|
|
|
|
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<uint32_t>());
|
|
|
|
|
currlevel = file.nextBE<uint32_t>();
|
|
|
|
|
leveltype = static_cast<dungeon_type>(file.nextBE<uint32_t>());
|
|
|
|
|
if (!setlevel)
|
|
|
|
|
leveltype = gnLevelTypeTbl[currlevel];
|
|
|
|
|
int viewX = file.nextBE<int32_t>();
|
|
|
|
|
int viewY = file.nextBE<int32_t>();
|
|
|
|
|
invflag = file.nextBool8();
|
|
|
|
|
chrflag = file.nextBool8();
|
|
|
|
|
int tmpNummonsters = file.nextBE<int32_t>();
|
|
|
|
|
int tmpNumitems = file.nextBE<int32_t>();
|
|
|
|
|
int tmpNummissiles = file.nextBE<int32_t>();
|
|
|
|
|
int tmpNobjects = file.nextBE<int32_t>();
|
|
|
|
|
|
|
|
|
|
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<uint32_t>();
|
|
|
|
|
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(plr[myplr]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LoadGameLevel(firstflag, ENTRY_LOAD);
|
|
|
|
|
SyncInitPlr(myplr);
|
|
|
|
|
SyncPlrAnim(myplr);
|
|
|
|
|
|
|
|
|
|
ViewX = viewX;
|
|
|
|
|
ViewY = viewY;
|
|
|
|
|
nummonsters = tmpNummonsters;
|
|
|
|
|
numitems = tmpNumitems;
|
|
|
|
|
nummissiles = tmpNummissiles;
|
|
|
|
|
nobjects = tmpNobjects;
|
|
|
|
|
|
|
|
|
|
for (int &monstkill : monstkills)
|
|
|
|
|
monstkill = file.nextBE<int32_t>();
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
for (int &monsterId : monstactive)
|
|
|
|
|
monsterId = file.nextBE<int32_t>();
|
|
|
|
|
for (int i = 0; i < nummonsters; i++)
|
|
|
|
|
LoadMonster(&file, monstactive[i]);
|
|
|
|
|
for (int &missileId : missileactive)
|
|
|
|
|
missileId = file.nextLE<int8_t>();
|
|
|
|
|
for (int &missileId : missileavail)
|
|
|
|
|
missileId = file.nextLE<int8_t>();
|
|
|
|
|
for (int i = 0; i < nummissiles; i++)
|
|
|
|
|
LoadMissile(&file, missileactive[i]);
|
|
|
|
|
for (int &objectId : objectactive)
|
|
|
|
|
objectId = file.nextLE<int8_t>();
|
|
|
|
|
for (int &objectId : objectavail)
|
|
|
|
|
objectId = file.nextLE<int8_t>();
|
|
|
|
|
for (int i = 0; i < nobjects; i++)
|
|
|
|
|
LoadObject(&file, objectactive[i]);
|
|
|
|
|
for (int i = 0; i < nobjects; i++)
|
|
|
|
|
SyncObjectAnim(objectactive[i]);
|
|
|
|
|
|
|
|
|
|
numlights = file.nextBE<int32_t>();
|
|
|
|
|
|
|
|
|
|
for (uint8_t &lightId : lightactive)
|
|
|
|
|
lightId = file.nextLE<uint8_t>();
|
|
|
|
|
for (int i = 0; i < numlights; i++)
|
|
|
|
|
LoadLighting(&file, &LightList[lightactive[i]]);
|
|
|
|
|
|
|
|
|
|
visionid = file.nextBE<int32_t>();
|
|
|
|
|
numvision = file.nextBE<int32_t>();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < numvision; i++)
|
|
|
|
|
LoadLighting(&file, &VisionList[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int &itemId : itemactive)
|
|
|
|
|
itemId = file.nextLE<int8_t>();
|
|
|
|
|
for (int &itemId : itemavail)
|
|
|
|
|
itemId = file.nextLE<int8_t>();
|
|
|
|
|
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++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dLight[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dFlags[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dPlayer[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dItem[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dMonster[i][j] = file.nextBE<int32_t>();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dDead[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dObject[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dLight[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dPreLight[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < DMAXY; j++) {
|
|
|
|
|
for (int i = 0; i < DMAXX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
AutomapView[i][j] = file.nextBool8();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dMissile[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
numpremium = file.nextBE<int32_t>();
|
|
|
|
|
premiumlevel = file.nextBE<int32_t>();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < giNumberOfSmithPremiumItems; i++)
|
|
|
|
|
LoadPremium(&file, i);
|
|
|
|
|
if (gbIsHellfire && !gbIsHellfireSaveGame)
|
|
|
|
|
SpawnPremium(myplr);
|
|
|
|
|
|
|
|
|
|
AutomapActive = file.nextBool8();
|
|
|
|
|
AutoMapScale = file.nextBE<int32_t>();
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
auto idx = pItem->IDidx;
|
|
|
|
|
if (!gbIsHellfire)
|
|
|
|
|
idx = RemapItemIdxToDiablo(idx);
|
|
|
|
|
if (gbIsSpawn)
|
|
|
|
|
idx = RemapItemIdxToSpawn(idx);
|
|
|
|
|
int iType = pItem->_itype;
|
|
|
|
|
if (idx == -1) {
|
|
|
|
|
idx = _item_indexes::IDI_GOLD;
|
|
|
|
|
iType = ITYPE_NONE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iSeed);
|
|
|
|
|
file->writeLE<int16_t>(pItem->_iCreateInfo);
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(iType);
|
|
|
|
|
file->writeLE<int32_t>(pItem->position.x);
|
|
|
|
|
file->writeLE<int32_t>(pItem->position.y);
|
|
|
|
|
file->writeLE<uint32_t>(pItem->_iAnimFlag ? 1 : 0);
|
|
|
|
|
file->skip(4); // Skip pointer _iAnimData
|
|
|
|
|
file->writeLE<int32_t>(pItem->AnimInfo.NumberOfFrames);
|
|
|
|
|
file->writeLE<int32_t>(pItem->AnimInfo.CurrentFrame);
|
|
|
|
|
// write _iAnimWidth for vanilla compatibility
|
|
|
|
|
file->writeLE<int32_t>(ItemAnimWidth);
|
|
|
|
|
// write _iAnimWidth2 for vanilla compatibility
|
|
|
|
|
file->writeLE<int32_t>(CalculateWidth2(ItemAnimWidth));
|
|
|
|
|
file->skip(4); // Unused since 1.02
|
|
|
|
|
file->writeLE<uint8_t>(pItem->_iSelFlag);
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
file->writeLE<uint32_t>(pItem->_iPostDraw ? 1 : 0);
|
|
|
|
|
file->writeLE<uint32_t>(pItem->_iIdentified ? 1 : 0);
|
|
|
|
|
file->writeLE<int8_t>(pItem->_iMagical);
|
|
|
|
|
file->writeBytes(pItem->_iName, 64);
|
|
|
|
|
file->writeBytes(pItem->_iIName, 64);
|
|
|
|
|
file->writeLE<int8_t>(pItem->_iLoc);
|
|
|
|
|
file->writeLE<uint8_t>(pItem->_iClass);
|
|
|
|
|
file->skip(1); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iCurs);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_ivalue);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iIvalue);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iMinDam);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iMaxDam);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iAC);
|
|
|
|
|
file->writeLE<uint32_t>(pItem->_iFlags);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iMiscId);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iSpell);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iCharges);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iMaxCharges);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iDurability);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iMaxDur);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLDam);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLToHit);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLAC);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLStr);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLMag);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLDex);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLVit);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLFR);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLLR);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLMR);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLMana);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLHP);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLDamMod);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLGetHit);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLLight);
|
|
|
|
|
file->writeLE<int8_t>(pItem->_iSplLvlAdd);
|
|
|
|
|
file->writeLE<int8_t>(pItem->_iRequest ? 1 : 0);
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iUid);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iFMinDam);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iFMaxDam);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iLMinDam);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iLMaxDam);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iPLEnAc);
|
|
|
|
|
file->writeLE<int8_t>(pItem->_iPrePower);
|
|
|
|
|
file->writeLE<int8_t>(pItem->_iSufPower);
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iVAdd1);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iVMult1);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iVAdd2);
|
|
|
|
|
file->writeLE<int32_t>(pItem->_iVMult2);
|
|
|
|
|
file->writeLE<int8_t>(pItem->_iMinStr);
|
|
|
|
|
file->writeLE<uint8_t>(pItem->_iMinMag);
|
|
|
|
|
file->writeLE<int8_t>(pItem->_iMinDex);
|
|
|
|
|
file->skip(1); // Alignment
|
|
|
|
|
file->writeLE<uint32_t>(pItem->_iStatFlag ? 1 : 0);
|
|
|
|
|
file->writeLE<int32_t>(idx);
|
|
|
|
|
file->writeLE<uint32_t>(pItem->dwBuff);
|
|
|
|
|
if (gbIsHellfire)
|
|
|
|
|
file->writeLE<uint32_t>(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<int32_t>(player._pmode);
|
|
|
|
|
for (int8_t step : player.walkpath)
|
|
|
|
|
file->writeLE<int8_t>(step);
|
|
|
|
|
file->writeLE<uint8_t>(player.plractive ? 1 : 0);
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(player.destAction);
|
|
|
|
|
file->writeLE<int32_t>(player.destParam1);
|
|
|
|
|
file->writeLE<int32_t>(player.destParam2);
|
|
|
|
|
file->writeLE<int32_t>(player.destParam3);
|
|
|
|
|
file->writeLE<int32_t>(player.destParam4);
|
|
|
|
|
file->writeLE<int32_t>(player.plrlevel);
|
|
|
|
|
file->writeLE<int32_t>(player.position.tile.x);
|
|
|
|
|
file->writeLE<int32_t>(player.position.tile.y);
|
|
|
|
|
file->writeLE<int32_t>(player.position.future.x);
|
|
|
|
|
file->writeLE<int32_t>(player.position.future.y);
|
|
|
|
|
|
|
|
|
|
// For backwards compatibility
|
|
|
|
|
const Point target = player.GetTargetPosition();
|
|
|
|
|
file->writeLE<int32_t>(target.x);
|
|
|
|
|
file->writeLE<int32_t>(target.y);
|
|
|
|
|
|
|
|
|
|
file->writeLE<int32_t>(player.position.last.x);
|
|
|
|
|
file->writeLE<int32_t>(player.position.last.y);
|
|
|
|
|
file->writeLE<int32_t>(player.position.old.x);
|
|
|
|
|
file->writeLE<int32_t>(player.position.old.y);
|
|
|
|
|
file->writeLE<int32_t>(player.position.offset.deltaX);
|
|
|
|
|
file->writeLE<int32_t>(player.position.offset.deltaY);
|
|
|
|
|
file->writeLE<int32_t>(player.position.velocity.deltaX);
|
|
|
|
|
file->writeLE<int32_t>(player.position.velocity.deltaY);
|
|
|
|
|
file->writeLE<int32_t>(player._pdir);
|
|
|
|
|
file->skip(4); // Unused
|
|
|
|
|
file->writeLE<int32_t>(player._pgfxnum);
|
|
|
|
|
file->skip(4); // Skip pointer _pAnimData
|
|
|
|
|
file->writeLE<int32_t>(std::max(0, player.AnimInfo.TicksPerFrame - 1));
|
|
|
|
|
file->writeLE<int32_t>(player.AnimInfo.TickCounterOfCurrentFrame);
|
|
|
|
|
file->writeLE<int32_t>(player.AnimInfo.NumberOfFrames);
|
|
|
|
|
file->writeLE<int32_t>(player.AnimInfo.CurrentFrame);
|
|
|
|
|
// write _pAnimWidth for vanilla compatibility
|
|
|
|
|
int animWidth = player.AnimInfo.pCelSprite == nullptr ? 96 : player.AnimInfo.pCelSprite->Width();
|
|
|
|
|
file->writeLE<int32_t>(animWidth);
|
|
|
|
|
// write _pAnimWidth2 for vanilla compatibility
|
|
|
|
|
file->writeLE<int32_t>(CalculateWidth2(animWidth));
|
|
|
|
|
file->skip(4); // Skip _peflag
|
|
|
|
|
file->writeLE<int32_t>(player._plid);
|
|
|
|
|
file->writeLE<int32_t>(player._pvid);
|
|
|
|
|
|
|
|
|
|
file->writeLE<int32_t>(player._pSpell);
|
|
|
|
|
file->writeLE<int8_t>(player._pSplType);
|
|
|
|
|
file->writeLE<int8_t>(player._pSplFrom);
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(player._pTSpell);
|
|
|
|
|
file->writeLE<int8_t>(player._pTSplType);
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(player._pRSpell);
|
|
|
|
|
file->writeLE<int8_t>(player._pRSplType);
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(player._pSBkSpell);
|
|
|
|
|
file->writeLE<int8_t>(player._pSBkSplType);
|
|
|
|
|
for (int8_t spellLevel : player._pSplLvl)
|
|
|
|
|
file->writeLE<int8_t>(spellLevel);
|
|
|
|
|
file->skip(7); // Alignment
|
|
|
|
|
file->writeLE<uint64_t>(player._pMemSpells);
|
|
|
|
|
file->writeLE<uint64_t>(player._pAblSpells);
|
|
|
|
|
file->writeLE<uint64_t>(player._pScrlSpells);
|
|
|
|
|
file->writeLE<uint8_t>(player._pSpellFlags);
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
for (auto &spellId : player._pSplHotKey)
|
|
|
|
|
file->writeLE<int32_t>(spellId);
|
|
|
|
|
for (auto &spellType : player._pSplTHotKey)
|
|
|
|
|
file->writeLE<int8_t>(spellType);
|
|
|
|
|
|
|
|
|
|
file->writeLE<int32_t>(player._pwtype);
|
|
|
|
|
file->writeLE<uint8_t>(player._pBlockFlag ? 1 : 0);
|
|
|
|
|
file->writeLE<uint8_t>(player._pInvincible ? 1 : 0);
|
|
|
|
|
file->writeLE<int8_t>(player._pLightRad);
|
|
|
|
|
file->writeLE<uint8_t>(player._pLvlChanging ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
file->writeBytes(player._pName, PLR_NAME_LEN);
|
|
|
|
|
file->writeLE<int8_t>(static_cast<int8_t>(player._pClass));
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(player._pStrength);
|
|
|
|
|
file->writeLE<int32_t>(player._pBaseStr);
|
|
|
|
|
file->writeLE<int32_t>(player._pMagic);
|
|
|
|
|
file->writeLE<int32_t>(player._pBaseMag);
|
|
|
|
|
file->writeLE<int32_t>(player._pDexterity);
|
|
|
|
|
file->writeLE<int32_t>(player._pBaseDex);
|
|
|
|
|
file->writeLE<int32_t>(player._pVitality);
|
|
|
|
|
file->writeLE<int32_t>(player._pBaseVit);
|
|
|
|
|
file->writeLE<int32_t>(player._pStatPts);
|
|
|
|
|
file->writeLE<int32_t>(player._pDamageMod);
|
|
|
|
|
|
|
|
|
|
file->writeLE<int32_t>(player._pBaseToBlk);
|
|
|
|
|
file->writeLE<int32_t>(player._pHPBase);
|
|
|
|
|
file->writeLE<int32_t>(player._pMaxHPBase);
|
|
|
|
|
file->writeLE<int32_t>(player._pHitPoints);
|
|
|
|
|
file->writeLE<int32_t>(player._pMaxHP);
|
|
|
|
|
file->skip(sizeof(int32_t)); // was hpPer, loads never use the stored value
|
|
|
|
|
file->writeLE<int32_t>(player._pManaBase);
|
|
|
|
|
file->writeLE<int32_t>(player._pMaxManaBase);
|
|
|
|
|
file->writeLE<int32_t>(player._pMana);
|
|
|
|
|
file->writeLE<int32_t>(player._pMaxMana);
|
|
|
|
|
file->skip(sizeof(int32_t)); // was manaPer, loads never use the stored value
|
|
|
|
|
file->writeLE<int8_t>(player._pLevel);
|
|
|
|
|
file->writeLE<int8_t>(player._pMaxLvl);
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(player._pExperience);
|
|
|
|
|
file->writeLE<int32_t>(player._pMaxExp);
|
|
|
|
|
file->writeLE<int32_t>(player._pNextExper);
|
|
|
|
|
file->writeLE<int8_t>(player._pArmorClass);
|
|
|
|
|
file->writeLE<int8_t>(player._pMagResist);
|
|
|
|
|
file->writeLE<int8_t>(player._pFireResist);
|
|
|
|
|
file->writeLE<int8_t>(player._pLghtResist);
|
|
|
|
|
file->writeLE<int32_t>(player._pGold);
|
|
|
|
|
|
|
|
|
|
file->writeLE<uint32_t>(player._pInfraFlag ? 1 : 0);
|
|
|
|
|
file->writeLE<int32_t>(player.position.temp.x);
|
|
|
|
|
file->writeLE<int32_t>(player.position.temp.y);
|
|
|
|
|
file->writeLE<int32_t>(player.tempDirection);
|
|
|
|
|
file->writeLE<int32_t>(player._pVar4);
|
|
|
|
|
file->writeLE<int32_t>(player._pVar5);
|
|
|
|
|
file->writeLE<int32_t>(player.position.offset2.deltaX);
|
|
|
|
|
file->writeLE<int32_t>(player.position.offset2.deltaY);
|
|
|
|
|
// Write actionFrame for vanilla compatibility
|
|
|
|
|
file->writeLE<int32_t>(0);
|
|
|
|
|
for (uint8_t i = 0; i < giNumberOfLevels; i++)
|
|
|
|
|
file->writeLE<uint8_t>(player._pLvlVisited[i] ? 1 : 0);
|
|
|
|
|
for (uint8_t i = 0; i < giNumberOfLevels; i++)
|
|
|
|
|
file->writeLE<uint8_t>(player._pSLvlVisited[i] ? 1 : 0); // only 10 used
|
|
|
|
|
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
|
|
|
|
|
// Write _pGFXLoad for vanilla compatibility
|
|
|
|
|
file->writeLE<int32_t>(0);
|
|
|
|
|
file->skip(4 * 8); // Skip pointers _pNAnim
|
|
|
|
|
file->writeLE<int32_t>(player._pNFrames);
|
|
|
|
|
file->skip(4); // Skip _pNWidth
|
|
|
|
|
file->skip(4 * 8); // Skip pointers _pWAnim
|
|
|
|
|
file->writeLE<int32_t>(player._pWFrames);
|
|
|
|
|
file->skip(4); // Skip _pWWidth
|
|
|
|
|
file->skip(4 * 8); // Skip pointers _pAAnim
|
|
|
|
|
file->writeLE<int32_t>(player._pAFrames);
|
|
|
|
|
file->skip(4); // Skip _pAWidth
|
|
|
|
|
file->writeLE<int32_t>(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<int32_t>(player._pSFrames);
|
|
|
|
|
file->skip(4); // Skip _pSWidth
|
|
|
|
|
file->writeLE<int32_t>(player._pSFNum);
|
|
|
|
|
file->skip(4 * 8); // Skip pointers _pHAnim
|
|
|
|
|
file->writeLE<int32_t>(player._pHFrames);
|
|
|
|
|
file->skip(4); // Skip _pHWidth
|
|
|
|
|
file->skip(4 * 8); // Skip pointers _pDAnim
|
|
|
|
|
file->writeLE<int32_t>(player._pDFrames);
|
|
|
|
|
file->skip(4); // Skip _pDWidth
|
|
|
|
|
file->skip(4 * 8); // Skip pointers _pBAnim
|
|
|
|
|
file->writeLE<int32_t>(player._pBFrames);
|
|
|
|
|
file->skip(4); // Skip _pBWidth
|
|
|
|
|
|
|
|
|
|
SaveItems(file, player.InvBody, NUM_INVLOC);
|
|
|
|
|
SaveItems(file, player.InvList, NUM_INV_GRID_ELEM);
|
|
|
|
|
file->writeLE<int32_t>(player._pNumInv);
|
|
|
|
|
for (int8_t cell : player.InvGrid)
|
|
|
|
|
file->writeLE<int8_t>(cell);
|
|
|
|
|
SaveItems(file, player.SpdList, MAXBELTITEMS);
|
|
|
|
|
SaveItem(file, &player.HoldItem);
|
|
|
|
|
|
|
|
|
|
file->writeLE<int32_t>(player._pIMinDam);
|
|
|
|
|
file->writeLE<int32_t>(player._pIMaxDam);
|
|
|
|
|
file->writeLE<int32_t>(player._pIAC);
|
|
|
|
|
file->writeLE<int32_t>(player._pIBonusDam);
|
|
|
|
|
file->writeLE<int32_t>(player._pIBonusToHit);
|
|
|
|
|
file->writeLE<int32_t>(player._pIBonusAC);
|
|
|
|
|
file->writeLE<int32_t>(player._pIBonusDamMod);
|
|
|
|
|
file->skip(4); // Alignment
|
|
|
|
|
|
|
|
|
|
file->writeLE<uint64_t>(player._pISpells);
|
|
|
|
|
file->writeLE<int32_t>(player._pIFlags);
|
|
|
|
|
file->writeLE<int32_t>(player._pIGetHit);
|
|
|
|
|
|
|
|
|
|
file->writeLE<int8_t>(player._pISplLvlAdd);
|
|
|
|
|
file->skip(1); // Unused
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(player._pISplDur);
|
|
|
|
|
file->writeLE<int32_t>(player._pIEnAc);
|
|
|
|
|
file->writeLE<int32_t>(player._pIFMinDam);
|
|
|
|
|
file->writeLE<int32_t>(player._pIFMaxDam);
|
|
|
|
|
file->writeLE<int32_t>(player._pILMinDam);
|
|
|
|
|
file->writeLE<int32_t>(player._pILMaxDam);
|
|
|
|
|
file->writeLE<int32_t>(player._pOilType);
|
|
|
|
|
file->writeLE<uint8_t>(player.pTownWarps);
|
|
|
|
|
file->writeLE<uint8_t>(player.pDungMsgs);
|
|
|
|
|
file->writeLE<uint8_t>(player.pLvlLoad);
|
|
|
|
|
if (gbIsHellfire)
|
|
|
|
|
file->writeLE<uint8_t>(player.pDungMsgs2);
|
|
|
|
|
else
|
|
|
|
|
file->writeLE<uint8_t>(player.pBattleNet ? 1 : 0);
|
|
|
|
|
file->writeLE<uint8_t>(player.pManaShield ? 1 : 0);
|
|
|
|
|
file->writeLE<uint8_t>(player.pOriginalCathedral ? 1 : 0);
|
|
|
|
|
file->skip(2); // Available bytes
|
|
|
|
|
file->writeLE<uint16_t>(player.wReflections);
|
|
|
|
|
file->skip(14); // Available bytes
|
|
|
|
|
|
|
|
|
|
file->writeLE<uint32_t>(player.pDiabloKillLevel);
|
|
|
|
|
file->writeLE<uint32_t>(player.pDifficulty);
|
|
|
|
|
file->writeLE<uint32_t>(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<int32_t>(pMonster->_mMTidx);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->_mmode);
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->_mgoal);
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(pMonster->_mgoalvar1);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->_mgoalvar2);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->_mgoalvar3);
|
|
|
|
|
file->skip(4); // Unused
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->_pathcount);
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.tile.x);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.tile.y);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.future.x);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.future.y);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.old.x);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.old.y);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.offset.deltaX);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.offset.deltaY);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.velocity.deltaX);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.velocity.deltaY);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->_mdir);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->_menemy);
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->enemyPosition.x);
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->enemyPosition.y);
|
|
|
|
|
file->skip(2); // Unused
|
|
|
|
|
|
|
|
|
|
file->skip(4); // Skip pointer _mAnimData
|
|
|
|
|
file->writeLE<int32_t>(pMonster->AnimInfo.TicksPerFrame);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->AnimInfo.TickCounterOfCurrentFrame);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->AnimInfo.NumberOfFrames);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->AnimInfo.CurrentFrame);
|
|
|
|
|
file->skip(4); // Skip _meflag
|
|
|
|
|
file->writeLE<uint32_t>(pMonster->_mDelFlag ? 1 : 0);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->_mVar1);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->_mVar2);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->_mVar3);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.temp.x);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.temp.y);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.offset2.deltaX);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.offset2.deltaY);
|
|
|
|
|
// Write actionFrame for vanilla compatibility
|
|
|
|
|
file->writeLE<int32_t>(0);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->_mmaxhp);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->_mhitpoints);
|
|
|
|
|
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->_mAi);
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->_mint);
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
file->writeLE<uint32_t>(pMonster->_mFlags);
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->_msquelch);
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
file->skip(4); // Unused
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.last.x);
|
|
|
|
|
file->writeLE<int32_t>(pMonster->position.last.y);
|
|
|
|
|
file->writeLE<uint32_t>(pMonster->_mRndSeed);
|
|
|
|
|
file->writeLE<uint32_t>(pMonster->_mAISeed);
|
|
|
|
|
file->skip(4); // Unused
|
|
|
|
|
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->_uniqtype);
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->_uniqtrans);
|
|
|
|
|
file->writeLE<int8_t>(pMonster->_udeadval);
|
|
|
|
|
|
|
|
|
|
file->writeLE<int8_t>(pMonster->mWhoHit);
|
|
|
|
|
file->writeLE<int8_t>(pMonster->mLevel);
|
|
|
|
|
file->skip(1); // Alignment
|
|
|
|
|
file->writeLE<uint16_t>(pMonster->mExp);
|
|
|
|
|
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->mHit < UINT8_MAX ? pMonster->mHit : UINT8_MAX); // For backwards compatibility
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->mMinDamage);
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->mMaxDamage);
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->mHit2 < UINT8_MAX ? pMonster->mHit2 : UINT8_MAX); // For backwards compatibility
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->mMinDamage2);
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->mMaxDamage2);
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->mArmorClass);
|
|
|
|
|
file->skip(1); // Alignment
|
|
|
|
|
file->writeLE<uint16_t>(pMonster->mMagicRes);
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
|
|
|
|
|
file->writeLE<int32_t>(pMonster->mtalkmsg == TEXT_NONE ? 0 : pMonster->mtalkmsg); // Replicate original bad mapping of none for monsters
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->leader);
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->leaderflag);
|
|
|
|
|
file->writeLE<uint8_t>(pMonster->packsize);
|
|
|
|
|
file->writeLE<int8_t>(pMonster->mlid);
|
|
|
|
|
|
|
|
|
|
// Omit pointer mName;
|
|
|
|
|
// Omit pointer MType;
|
|
|
|
|
// Omit pointer MData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void SaveMissile(SaveHelper *file, int i)
|
|
|
|
|
{
|
|
|
|
|
MissileStruct *pMissile = &missile[i];
|
|
|
|
|
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_mitype);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->position.tile.x);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->position.tile.y);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->position.offset.deltaX);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->position.offset.deltaY);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->position.velocity.deltaX);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->position.velocity.deltaY);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->position.start.x);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->position.start.y);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->position.traveled.deltaX);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->position.traveled.deltaY);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_mimfnum);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_mispllvl);
|
|
|
|
|
file->writeLE<uint32_t>(pMissile->_miDelFlag ? 1 : 0);
|
|
|
|
|
file->writeLE<uint8_t>(pMissile->_miAnimType);
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miAnimFlags);
|
|
|
|
|
file->skip(4); // Skip pointer _miAnimData
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miAnimDelay);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miAnimLen);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miAnimWidth);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miAnimWidth2);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miAnimCnt);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miAnimAdd);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miAnimFrame);
|
|
|
|
|
file->writeLE<uint32_t>(pMissile->_miDrawFlag ? 1 : 0);
|
|
|
|
|
file->writeLE<uint32_t>(pMissile->_miLightFlag ? 1 : 0);
|
|
|
|
|
file->writeLE<uint32_t>(pMissile->_miPreFlag ? 1 : 0);
|
|
|
|
|
file->writeLE<uint32_t>(pMissile->_miUniqTrans);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_mirange);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_misource);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_micaster);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_midam);
|
|
|
|
|
file->writeLE<uint32_t>(pMissile->_miHitFlag ? 1 : 0);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_midist);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_mlid);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_mirnd);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miVar1);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miVar2);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miVar3);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miVar4);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miVar5);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miVar6);
|
|
|
|
|
file->writeLE<int32_t>(pMissile->_miVar7);
|
|
|
|
|
file->writeLE<uint32_t>(pMissile->limitReached ? 1 : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void SaveObject(SaveHelper *file, int i)
|
|
|
|
|
{
|
|
|
|
|
ObjectStruct *pObject = &object[i];
|
|
|
|
|
|
|
|
|
|
file->writeLE<int32_t>(pObject->_otype);
|
|
|
|
|
file->writeLE<int32_t>(pObject->position.x);
|
|
|
|
|
file->writeLE<int32_t>(pObject->position.y);
|
|
|
|
|
file->writeLE<uint32_t>(pObject->_oLight ? 1 : 0);
|
|
|
|
|
file->writeLE<uint32_t>(pObject->_oAnimFlag);
|
|
|
|
|
file->skip(4); // Skip pointer _oAnimData
|
|
|
|
|
file->writeLE<int32_t>(pObject->_oAnimDelay);
|
|
|
|
|
file->writeLE<int32_t>(pObject->_oAnimCnt);
|
|
|
|
|
file->writeLE<uint32_t>(pObject->_oAnimLen);
|
|
|
|
|
file->writeLE<uint32_t>(pObject->_oAnimFrame);
|
|
|
|
|
file->writeLE<int32_t>(pObject->_oAnimWidth);
|
|
|
|
|
file->writeLE<int32_t>(CalculateWidth2(pObject->_oAnimWidth));
|
|
|
|
|
// Write _oAnimWidth2 for vanilla compatibility
|
|
|
|
|
file->writeLE<uint32_t>(pObject->_oDelFlag ? 1 : 0);
|
|
|
|
|
file->writeLE<int8_t>(pObject->_oBreak);
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
file->writeLE<uint32_t>(pObject->_oSolidFlag ? 1 : 0);
|
|
|
|
|
file->writeLE<uint32_t>(pObject->_oMissFlag ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
file->writeLE<int8_t>(pObject->_oSelFlag);
|
|
|
|
|
file->skip(3); // Alignment
|
|
|
|
|
file->writeLE<uint32_t>(pObject->_oPreFlag ? 1 : 0);
|
|
|
|
|
file->writeLE<uint32_t>(pObject->_oTrapFlag ? 1 : 0);
|
|
|
|
|
file->writeLE<uint32_t>(pObject->_oDoorFlag ? 1 : 0);
|
|
|
|
|
file->writeLE<int32_t>(pObject->_olid);
|
|
|
|
|
file->writeLE<uint32_t>(pObject->_oRndSeed);
|
|
|
|
|
file->writeLE<int32_t>(pObject->_oVar1);
|
|
|
|
|
file->writeLE<int32_t>(pObject->_oVar2);
|
|
|
|
|
file->writeLE<int32_t>(pObject->_oVar3);
|
|
|
|
|
file->writeLE<int32_t>(pObject->_oVar4);
|
|
|
|
|
file->writeLE<int32_t>(pObject->_oVar5);
|
|
|
|
|
file->writeLE<uint32_t>(pObject->_oVar6);
|
|
|
|
|
file->writeLE<int32_t>(pObject->_oVar7);
|
|
|
|
|
file->writeLE<int32_t>(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<uint8_t>(pQuest->_qlevel);
|
|
|
|
|
file->writeLE<uint8_t>(pQuest->_qtype);
|
|
|
|
|
file->writeLE<uint8_t>(pQuest->_qactive);
|
|
|
|
|
file->writeLE<uint8_t>(pQuest->_qlvltype);
|
|
|
|
|
file->writeLE<int32_t>(pQuest->position.x);
|
|
|
|
|
file->writeLE<int32_t>(pQuest->position.y);
|
|
|
|
|
file->writeLE<uint8_t>(pQuest->_qslvl);
|
|
|
|
|
file->writeLE<uint8_t>(pQuest->_qidx);
|
|
|
|
|
if (gbIsHellfire) {
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
file->writeLE<int32_t>(pQuest->_qmsg);
|
|
|
|
|
} else {
|
|
|
|
|
file->writeLE<uint8_t>(pQuest->_qmsg);
|
|
|
|
|
}
|
|
|
|
|
file->writeLE<uint8_t>(pQuest->_qvar1);
|
|
|
|
|
file->writeLE<uint8_t>(pQuest->_qvar2);
|
|
|
|
|
file->skip(2); // Alignment
|
|
|
|
|
if (!gbIsHellfire)
|
|
|
|
|
file->skip(1); // Alignment
|
|
|
|
|
file->writeLE<uint32_t>(pQuest->_qlog ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
file->writeBE<int32_t>(ReturnLvlX);
|
|
|
|
|
file->writeBE<int32_t>(ReturnLvlY);
|
|
|
|
|
file->writeBE<int32_t>(ReturnLvl);
|
|
|
|
|
file->writeBE<int32_t>(ReturnLvlT);
|
|
|
|
|
file->writeBE<int32_t>(DoomQuestState);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void SaveLighting(SaveHelper *file, LightListStruct *pLight)
|
|
|
|
|
{
|
|
|
|
|
file->writeLE<int32_t>(pLight->position.tile.x);
|
|
|
|
|
file->writeLE<int32_t>(pLight->position.tile.y);
|
|
|
|
|
file->writeLE<int32_t>(pLight->_lradius);
|
|
|
|
|
file->writeLE<int32_t>(pLight->_lid);
|
|
|
|
|
file->writeLE<uint32_t>(pLight->_ldel ? 1 : 0);
|
|
|
|
|
file->writeLE<uint32_t>(pLight->_lunflag ? 1 : 0);
|
|
|
|
|
file->skip(4); // Unused
|
|
|
|
|
file->writeLE<int32_t>(pLight->position.old.x);
|
|
|
|
|
file->writeLE<int32_t>(pLight->position.old.y);
|
|
|
|
|
file->writeLE<int32_t>(pLight->oldRadius);
|
|
|
|
|
file->writeLE<int32_t>(pLight->position.offset.x);
|
|
|
|
|
file->writeLE<int32_t>(pLight->position.offset.y);
|
|
|
|
|
file->writeLE<uint32_t>(pLight->_lflags ? 1 : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void SavePortal(SaveHelper *file, int i)
|
|
|
|
|
{
|
|
|
|
|
PortalStruct *pPortal = &portal[i];
|
|
|
|
|
|
|
|
|
|
file->writeLE<uint32_t>(pPortal->open ? 1 : 0);
|
|
|
|
|
file->writeLE<int32_t>(pPortal->position.x);
|
|
|
|
|
file->writeLE<int32_t>(pPortal->position.y);
|
|
|
|
|
file->writeLE<int32_t>(pPortal->level);
|
|
|
|
|
file->writeLE<int32_t>(pPortal->ltype);
|
|
|
|
|
file->writeLE<uint32_t>(pPortal->setlvl ? 1 : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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<uint8_t>(gbIsHellfire ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
SaveItems(&file, player.InvBody, NUM_INVLOC);
|
|
|
|
|
SaveItems(&file, player.InvList, NUM_INV_GRID_ELEM);
|
|
|
|
|
SaveItems(&file, player.SpdList, MAXBELTITEMS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 256 kilobytes + 3 bytes (demo leftover) for file magic (262147)
|
|
|
|
|
// final game uses 4-byte magic instead of 3
|
|
|
|
|
#define FILEBUFF ((256 * 1024) + 3)
|
|
|
|
|
|
|
|
|
|
void SaveGameData()
|
|
|
|
|
{
|
|
|
|
|
SaveHelper file("game", FILEBUFF);
|
|
|
|
|
|
|
|
|
|
if (gbIsSpawn && !gbIsHellfire)
|
|
|
|
|
file.writeLE<uint32_t>(LoadLE32("SHAR"));
|
|
|
|
|
else if (gbIsSpawn && gbIsHellfire)
|
|
|
|
|
file.writeLE<uint32_t>(LoadLE32("SHLF"));
|
|
|
|
|
else if (!gbIsSpawn && gbIsHellfire)
|
|
|
|
|
file.writeLE<uint32_t>(LoadLE32("HELF"));
|
|
|
|
|
else if (!gbIsSpawn && !gbIsHellfire)
|
|
|
|
|
file.writeLE<uint32_t>(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<uint8_t>(setlevel ? 1 : 0);
|
|
|
|
|
file.writeBE<uint32_t>(setlvlnum);
|
|
|
|
|
file.writeBE<uint32_t>(currlevel);
|
|
|
|
|
file.writeBE<uint32_t>(leveltype);
|
|
|
|
|
file.writeBE<int32_t>(ViewX);
|
|
|
|
|
file.writeBE<int32_t>(ViewY);
|
|
|
|
|
file.writeLE<uint8_t>(invflag ? 1 : 0);
|
|
|
|
|
file.writeLE<uint8_t>(chrflag ? 1 : 0);
|
|
|
|
|
file.writeBE<int32_t>(nummonsters);
|
|
|
|
|
file.writeBE<int32_t>(numitems);
|
|
|
|
|
file.writeBE<int32_t>(nummissiles);
|
|
|
|
|
file.writeBE<int32_t>(nobjects);
|
|
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < giNumberOfLevels; i++) {
|
|
|
|
|
file.writeBE<uint32_t>(glSeedTbl[i]);
|
|
|
|
|
file.writeBE<int32_t>(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<int32_t>(monstkill);
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
for (int monsterId : monstactive)
|
|
|
|
|
file.writeBE<int32_t>(monsterId);
|
|
|
|
|
for (int i = 0; i < nummonsters; i++)
|
|
|
|
|
SaveMonster(&file, monstactive[i]);
|
|
|
|
|
for (int missileId : missileactive)
|
|
|
|
|
file.writeLE<int8_t>(missileId);
|
|
|
|
|
for (int missileId : missileavail)
|
|
|
|
|
file.writeLE<int8_t>(missileId);
|
|
|
|
|
for (int i = 0; i < nummissiles; i++)
|
|
|
|
|
SaveMissile(&file, missileactive[i]);
|
|
|
|
|
for (int objectId : objectactive)
|
|
|
|
|
file.writeLE<int8_t>(objectId);
|
|
|
|
|
for (int objectId : objectavail)
|
|
|
|
|
file.writeLE<int8_t>(objectId);
|
|
|
|
|
for (int i = 0; i < nobjects; i++)
|
|
|
|
|
SaveObject(&file, objectactive[i]);
|
|
|
|
|
|
|
|
|
|
file.writeBE<int32_t>(numlights);
|
|
|
|
|
|
|
|
|
|
for (uint8_t lightId : lightactive)
|
|
|
|
|
file.writeLE<uint8_t>(lightId);
|
|
|
|
|
for (int i = 0; i < numlights; i++)
|
|
|
|
|
SaveLighting(&file, &LightList[lightactive[i]]);
|
|
|
|
|
|
|
|
|
|
file.writeBE<int32_t>(visionid);
|
|
|
|
|
file.writeBE<int32_t>(numvision);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < numvision; i++)
|
|
|
|
|
SaveLighting(&file, &VisionList[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int itemId : itemactive)
|
|
|
|
|
file.writeLE<int8_t>(itemId);
|
|
|
|
|
for (int itemId : itemavail)
|
|
|
|
|
file.writeLE<int8_t>(itemId);
|
|
|
|
|
for (int i = 0; i < numitems; i++)
|
|
|
|
|
SaveItem(&file, &items[itemactive[i]]);
|
|
|
|
|
for (bool UniqueItemFlag : UniqueItemFlags)
|
|
|
|
|
file.writeLE<uint8_t>(UniqueItemFlag ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dLight[i][j]);
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dFlags[i][j] & ~(BFLAG_MISSILE | BFLAG_VISIBLE | BFLAG_DEAD_PLAYER));
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dPlayer[i][j]);
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dItem[i][j]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeBE<int32_t>(dMonster[i][j]);
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dDead[i][j]);
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dObject[i][j]);
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dLight[i][j]);
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dPreLight[i][j]);
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < DMAXY; j++) {
|
|
|
|
|
for (int i = 0; i < DMAXX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<uint8_t>(AutomapView[i][j] ? 1 : 0);
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dMissile[i][j]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file.writeBE<int32_t>(numpremium);
|
|
|
|
|
file.writeBE<int32_t>(premiumlevel);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < giNumberOfSmithPremiumItems; i++)
|
|
|
|
|
SavePremium(&file, i);
|
|
|
|
|
|
|
|
|
|
file.writeLE<uint8_t>(AutomapActive ? 1 : 0);
|
|
|
|
|
file.writeBE<int32_t>(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++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dDead[i][j]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file.writeBE<int32_t>(nummonsters);
|
|
|
|
|
file.writeBE<int32_t>(numitems);
|
|
|
|
|
file.writeBE<int32_t>(nobjects);
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
for (int monsterId : monstactive)
|
|
|
|
|
file.writeBE<int32_t>(monsterId);
|
|
|
|
|
for (int i = 0; i < nummonsters; i++)
|
|
|
|
|
SaveMonster(&file, monstactive[i]);
|
|
|
|
|
for (int objectId : objectactive)
|
|
|
|
|
file.writeLE<int8_t>(objectId);
|
|
|
|
|
for (int objectId : objectavail)
|
|
|
|
|
file.writeLE<int8_t>(objectId);
|
|
|
|
|
for (int i = 0; i < nobjects; i++)
|
|
|
|
|
SaveObject(&file, objectactive[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int itemId : itemactive)
|
|
|
|
|
file.writeLE<int8_t>(itemId);
|
|
|
|
|
for (int itemId : itemavail)
|
|
|
|
|
file.writeLE<int8_t>(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++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dFlags[i][j] & ~(BFLAG_MISSILE | BFLAG_VISIBLE | BFLAG_DEAD_PLAYER));
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dItem[i][j]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeBE<int32_t>(dMonster[i][j]);
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dObject[i][j]);
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dLight[i][j]);
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(dPreLight[i][j]);
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < DMAXY; j++) {
|
|
|
|
|
for (int i = 0; i < DMAXX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<uint8_t>(AutomapView[i][j] ? 1 : 0);
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.writeLE<int8_t>(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++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dDead[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
SetDead();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nummonsters = file.nextBE<int32_t>();
|
|
|
|
|
numitems = file.nextBE<int32_t>();
|
|
|
|
|
nobjects = file.nextBE<int32_t>();
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
for (int &monsterId : monstactive)
|
|
|
|
|
monsterId = file.nextBE<int32_t>();
|
|
|
|
|
for (int i = 0; i < nummonsters; i++)
|
|
|
|
|
LoadMonster(&file, monstactive[i]);
|
|
|
|
|
for (int &objectId : objectactive)
|
|
|
|
|
objectId = file.nextLE<int8_t>();
|
|
|
|
|
for (int &objectId : objectavail)
|
|
|
|
|
objectId = file.nextLE<int8_t>();
|
|
|
|
|
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<int8_t>();
|
|
|
|
|
for (int &itemId : itemavail)
|
|
|
|
|
itemId = file.nextLE<int8_t>();
|
|
|
|
|
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++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dFlags[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dItem[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dMonster[i][j] = file.nextBE<int32_t>();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dObject[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dLight[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dPreLight[i][j] = file.nextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < DMAXY; j++) {
|
|
|
|
|
for (int i = 0; i < DMAXX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
AutomapView[i][j] = file.nextBool8();
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dMissile[i][j] = 0; /// BUGFIX: supposed to load saved missiles with "file.nextLE<int8_t>()"?
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|