|
|
|
|
/**
|
|
|
|
|
* @file loadsave.cpp
|
|
|
|
|
*
|
|
|
|
|
* Implementation of save game functionality.
|
|
|
|
|
*/
|
|
|
|
|
#include "loadsave.h"
|
|
|
|
|
|
|
|
|
|
#include <climits>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <numeric>
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
|
|
|
|
|
#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 "menu.h"
|
|
|
|
|
#include "missiles.h"
|
|
|
|
|
#include "mpq/mpq_writer.hpp"
|
|
|
|
|
#include "pfile.h"
|
|
|
|
|
#include "qol/stash.h"
|
|
|
|
|
#include "stores.h"
|
|
|
|
|
#include "utils/endian.hpp"
|
|
|
|
|
#include "utils/language.h"
|
|
|
|
|
|
|
|
|
|
namespace devilution {
|
|
|
|
|
|
|
|
|
|
bool gbIsHellfireSaveGame;
|
|
|
|
|
uint8_t giNumberOfLevels;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
constexpr size_t MaxMissilesForSaveGame = 125;
|
|
|
|
|
|
|
|
|
|
uint8_t giNumberQuests;
|
|
|
|
|
uint8_t giNumberOfSmithPremiumItems;
|
|
|
|
|
|
|
|
|
|
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 static_cast<T>(SDL_SwapBE64(in));
|
|
|
|
|
default:
|
|
|
|
|
return in;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class LoadHelper {
|
|
|
|
|
std::unique_ptr<byte[]> m_buffer_;
|
|
|
|
|
size_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(std::optional<MpqArchive> archive, const char *szFileName)
|
|
|
|
|
{
|
|
|
|
|
if (archive)
|
|
|
|
|
m_buffer_ = ReadArchive(*archive, szFileName, &m_size_);
|
|
|
|
|
else
|
|
|
|
|
m_buffer_ = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsValid(size_t size = 1)
|
|
|
|
|
{
|
|
|
|
|
return m_buffer_ != nullptr
|
|
|
|
|
&& m_size_ >= (m_cur_ + size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
constexpr void Skip(size_t count = 1)
|
|
|
|
|
{
|
|
|
|
|
Skip(sizeof(T) * count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Skip(size_t size)
|
|
|
|
|
{
|
|
|
|
|
m_cur_ += size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NextBytes(void *bytes, size_t size)
|
|
|
|
|
{
|
|
|
|
|
if (!IsValid(size))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
memcpy(bytes, &m_buffer_[m_cur_], size);
|
|
|
|
|
m_cur_ += size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <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 {
|
|
|
|
|
MpqWriter &m_mpqWriter;
|
|
|
|
|
const char *m_szFileName_;
|
|
|
|
|
std::unique_ptr<byte[]> m_buffer_;
|
|
|
|
|
size_t m_cur_ = 0;
|
|
|
|
|
size_t m_capacity_;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
SaveHelper(MpqWriter &mpqWriter, const char *szFileName, size_t bufferLen)
|
|
|
|
|
: m_mpqWriter(mpqWriter)
|
|
|
|
|
, m_szFileName_(szFileName)
|
|
|
|
|
, m_buffer_(new byte[codec_get_encoded_len(bufferLen)])
|
|
|
|
|
, m_capacity_(bufferLen)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsValid(size_t len = 1)
|
|
|
|
|
{
|
|
|
|
|
return m_buffer_ != nullptr
|
|
|
|
|
&& m_capacity_ >= (m_cur_ + len);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
constexpr void Skip(size_t count = 1)
|
|
|
|
|
{
|
|
|
|
|
Skip(sizeof(T) * count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Skip(size_t len)
|
|
|
|
|
{
|
|
|
|
|
std::memset(&m_buffer_[m_cur_], 0, len);
|
|
|
|
|
m_cur_ += len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WriteBytes(const void *bytes, size_t len)
|
|
|
|
|
{
|
|
|
|
|
if (!IsValid(len))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
memcpy(&m_buffer_[m_cur_], bytes, len);
|
|
|
|
|
m_cur_ += len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <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 encodedLen = codec_get_encoded_len(m_cur_);
|
|
|
|
|
const char *const password = pfile_get_password();
|
|
|
|
|
codec_encode(m_buffer_.get(), m_cur_, encodedLen, password);
|
|
|
|
|
m_mpqWriter.WriteFile(m_szFileName_, m_buffer_.get(), encodedLen);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void LoadItemData(LoadHelper &file, Item &item)
|
|
|
|
|
{
|
|
|
|
|
item._iSeed = file.NextLE<int32_t>();
|
|
|
|
|
item._iCreateInfo = file.NextLE<uint16_t>();
|
|
|
|
|
file.Skip(2); // Alignment
|
|
|
|
|
item._itype = static_cast<ItemType>(file.NextLE<uint32_t>());
|
|
|
|
|
item.position.x = file.NextLE<int32_t>();
|
|
|
|
|
item.position.y = file.NextLE<int32_t>();
|
|
|
|
|
item._iAnimFlag = file.NextBool32();
|
|
|
|
|
file.Skip(4); // Skip pointer _iAnimData
|
|
|
|
|
item.AnimInfo = {};
|
|
|
|
|
item.AnimInfo.NumberOfFrames = file.NextLE<int32_t>();
|
|
|
|
|
item.AnimInfo.CurrentFrame = file.NextLE<int32_t>() - 1;
|
|
|
|
|
file.Skip(8); // Skip _iAnimWidth and _iAnimWidth2
|
|
|
|
|
file.Skip(4); // Unused since 1.02
|
|
|
|
|
item._iSelFlag = file.NextLE<uint8_t>();
|
|
|
|
|
file.Skip(3); // Alignment
|
|
|
|
|
item._iPostDraw = file.NextBool32();
|
|
|
|
|
item._iIdentified = file.NextBool32();
|
|
|
|
|
item._iMagical = static_cast<item_quality>(file.NextLE<int8_t>());
|
|
|
|
|
file.NextBytes(item._iName, 64);
|
|
|
|
|
file.NextBytes(item._iIName, 64);
|
|
|
|
|
item._iLoc = static_cast<item_equip_type>(file.NextLE<int8_t>());
|
|
|
|
|
item._iClass = static_cast<item_class>(file.NextLE<uint8_t>());
|
|
|
|
|
file.Skip(1); // Alignment
|
|
|
|
|
item._iCurs = file.NextLE<int32_t>();
|
|
|
|
|
item._ivalue = file.NextLE<int32_t>();
|
|
|
|
|
item._iIvalue = file.NextLE<int32_t>();
|
|
|
|
|
item._iMinDam = file.NextLE<int32_t>();
|
|
|
|
|
item._iMaxDam = file.NextLE<int32_t>();
|
|
|
|
|
item._iAC = file.NextLE<int32_t>();
|
|
|
|
|
item._iFlags = static_cast<ItemSpecialEffect>(file.NextLE<uint32_t>());
|
|
|
|
|
item._iMiscId = static_cast<item_misc_id>(file.NextLE<int32_t>());
|
|
|
|
|
item._iSpell = static_cast<spell_id>(file.NextLE<int32_t>());
|
|
|
|
|
item._iCharges = file.NextLE<int32_t>();
|
|
|
|
|
item._iMaxCharges = file.NextLE<int32_t>();
|
|
|
|
|
item._iDurability = file.NextLE<int32_t>();
|
|
|
|
|
item._iMaxDur = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLDam = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLToHit = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLAC = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLStr = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLMag = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLDex = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLVit = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLFR = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLLR = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLMR = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLMana = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLHP = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLDamMod = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLGetHit = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLLight = file.NextLE<int32_t>();
|
|
|
|
|
item._iSplLvlAdd = file.NextLE<int8_t>();
|
|
|
|
|
item._iRequest = file.NextBool8();
|
|
|
|
|
file.Skip(2); // Alignment
|
|
|
|
|
item._iUid = file.NextLE<int32_t>();
|
|
|
|
|
item._iFMinDam = file.NextLE<int32_t>();
|
|
|
|
|
item._iFMaxDam = file.NextLE<int32_t>();
|
|
|
|
|
item._iLMinDam = file.NextLE<int32_t>();
|
|
|
|
|
item._iLMaxDam = file.NextLE<int32_t>();
|
|
|
|
|
item._iPLEnAc = file.NextLE<int32_t>();
|
|
|
|
|
item._iPrePower = static_cast<item_effect_type>(file.NextLE<int8_t>());
|
|
|
|
|
item._iSufPower = static_cast<item_effect_type>(file.NextLE<int8_t>());
|
|
|
|
|
file.Skip(2); // Alignment
|
|
|
|
|
item._iVAdd1 = file.NextLE<int32_t>();
|
|
|
|
|
item._iVMult1 = file.NextLE<int32_t>();
|
|
|
|
|
item._iVAdd2 = file.NextLE<int32_t>();
|
|
|
|
|
item._iVMult2 = file.NextLE<int32_t>();
|
|
|
|
|
item._iMinStr = file.NextLE<int8_t>();
|
|
|
|
|
item._iMinMag = file.NextLE<uint8_t>();
|
|
|
|
|
item._iMinDex = file.NextLE<int8_t>();
|
|
|
|
|
file.Skip(1); // Alignment
|
|
|
|
|
item._iStatFlag = file.NextBool32();
|
|
|
|
|
item.IDidx = static_cast<_item_indexes>(file.NextLE<int32_t>());
|
|
|
|
|
if (gbIsSpawn) {
|
|
|
|
|
item.IDidx = RemapItemIdxFromSpawn(item.IDidx);
|
|
|
|
|
}
|
|
|
|
|
if (!gbIsHellfireSaveGame) {
|
|
|
|
|
item.IDidx = RemapItemIdxFromDiablo(item.IDidx);
|
|
|
|
|
}
|
|
|
|
|
item.dwBuff = file.NextLE<uint32_t>();
|
|
|
|
|
if (gbIsHellfireSaveGame)
|
|
|
|
|
item._iDamAcFlags = static_cast<ItemSpecialEffectHf>(file.NextLE<uint32_t>());
|
|
|
|
|
else
|
|
|
|
|
item._iDamAcFlags = ItemSpecialEffectHf::None;
|
|
|
|
|
|
|
|
|
|
RemoveInvalidItem(item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadPlayer(LoadHelper &file, Player &player)
|
|
|
|
|
{
|
|
|
|
|
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 = file.NextLE<int32_t>();
|
|
|
|
|
player.destParam4 = file.NextLE<int32_t>();
|
|
|
|
|
player.plrlevel = file.NextLE<uint32_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>() - 1;
|
|
|
|
|
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>());
|
|
|
|
|
file.Skip<int8_t>(); // Skip _pTSplType
|
|
|
|
|
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>());
|
|
|
|
|
file.Skip<int8_t>(); // Skip _pSBkSplType
|
|
|
|
|
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 = static_cast<SpellFlag>(file.NextLE<uint8_t>());
|
|
|
|
|
file.Skip(3); // Alignment
|
|
|
|
|
|
|
|
|
|
// Extra hotkeys: to keep single player save compatibility, read only 4 hotkeys here, rely on LoadHotkeys for the rest
|
|
|
|
|
for (size_t i = 0; i < 4; i++) {
|
|
|
|
|
player._pSplHotKey[i] = static_cast<spell_id>(file.NextLE<int32_t>());
|
|
|
|
|
}
|
|
|
|
|
for (size_t i = 0; i < 4; i++) {
|
|
|
|
|
player._pSplTHotKey[i] = static_cast<spell_type>(file.NextLE<uint8_t>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file.Skip<int32_t>(); // Skip _pwtype
|
|
|
|
|
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 = BlockBonuses[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)); // Skip _pHPPer - always derived 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)); // Skip _pManaPer - always derived from mana and maxMana
|
|
|
|
|
player._pLevel = file.NextLE<int8_t>();
|
|
|
|
|
player._pMaxLvl = file.NextLE<int8_t>();
|
|
|
|
|
file.Skip(2); // Alignment
|
|
|
|
|
player._pExperience = file.NextLE<uint32_t>();
|
|
|
|
|
file.Skip<uint32_t>(); // Skip _pMaxExp - unused
|
|
|
|
|
player._pNextExper = file.NextLE<uint32_t>(); // This can be calculated based on pLevel (which in turn could be calculated based on pExperience)
|
|
|
|
|
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.spellLevel = file.NextLE<int32_t>();
|
|
|
|
|
file.Skip(4); // skip _pVar5, was used for storing position of a tile which should have its HorizontalMovingPlayer flag removed after walking
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
for (Item &item : player.InvBody)
|
|
|
|
|
LoadItemData(file, item);
|
|
|
|
|
|
|
|
|
|
for (Item &item : player.InvList)
|
|
|
|
|
LoadItemData(file, item);
|
|
|
|
|
|
|
|
|
|
player._pNumInv = file.NextLE<int32_t>();
|
|
|
|
|
|
|
|
|
|
for (int8_t &cell : player.InvGrid)
|
|
|
|
|
cell = file.NextLE<int8_t>();
|
|
|
|
|
|
|
|
|
|
for (Item &item : player.SpdList)
|
|
|
|
|
LoadItemData(file, item);
|
|
|
|
|
|
|
|
|
|
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 = static_cast<ItemSpecialEffect>(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 = static_cast<ItemSpecialEffectHf>(file.NextLE<uint32_t>());
|
|
|
|
|
file.Skip(20); // Available bytes
|
|
|
|
|
CalcPlrItemVals(player, false);
|
|
|
|
|
|
|
|
|
|
// Omit pointer _pNData
|
|
|
|
|
// Omit pointer _pWData
|
|
|
|
|
// Omit pointer _pAData
|
|
|
|
|
// Omit pointer _pLData
|
|
|
|
|
// Omit pointer _pFData
|
|
|
|
|
// Omit pointer _pTData
|
|
|
|
|
// Omit pointer _pHData
|
|
|
|
|
// Omit pointer _pDData
|
|
|
|
|
// Omit pointer _pBData
|
|
|
|
|
// Omit pointer pReserved
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool gbSkipSync = false;
|
|
|
|
|
|
|
|
|
|
void LoadMonster(LoadHelper *file, Monster &monster)
|
|
|
|
|
{
|
|
|
|
|
monster._mMTidx = file->NextLE<int32_t>();
|
|
|
|
|
monster._mmode = static_cast<MonsterMode>(file->NextLE<int32_t>());
|
|
|
|
|
monster._mgoal = static_cast<monster_goal>(file->NextLE<uint8_t>());
|
|
|
|
|
file->Skip(3); // Alignment
|
|
|
|
|
monster._mgoalvar1 = file->NextLE<int32_t>();
|
|
|
|
|
monster._mgoalvar2 = file->NextLE<int32_t>();
|
|
|
|
|
monster._mgoalvar3 = file->NextLE<int32_t>();
|
|
|
|
|
file->Skip(4); // Unused
|
|
|
|
|
monster._pathcount = file->NextLE<uint8_t>();
|
|
|
|
|
file->Skip(3); // Alignment
|
|
|
|
|
monster.position.tile.x = file->NextLE<int32_t>();
|
|
|
|
|
monster.position.tile.y = file->NextLE<int32_t>();
|
|
|
|
|
monster.position.future.x = file->NextLE<int32_t>();
|
|
|
|
|
monster.position.future.y = file->NextLE<int32_t>();
|
|
|
|
|
monster.position.old.x = file->NextLE<int32_t>();
|
|
|
|
|
monster.position.old.y = file->NextLE<int32_t>();
|
|
|
|
|
monster.position.offset.deltaX = file->NextLE<int32_t>();
|
|
|
|
|
monster.position.offset.deltaY = file->NextLE<int32_t>();
|
|
|
|
|
monster.position.velocity.deltaX = file->NextLE<int32_t>();
|
|
|
|
|
monster.position.velocity.deltaY = file->NextLE<int32_t>();
|
|
|
|
|
monster._mdir = static_cast<Direction>(file->NextLE<int32_t>());
|
|
|
|
|
monster._menemy = file->NextLE<int32_t>();
|
|
|
|
|
monster.enemyPosition.x = file->NextLE<uint8_t>();
|
|
|
|
|
monster.enemyPosition.y = file->NextLE<uint8_t>();
|
|
|
|
|
file->Skip(2); // Unused
|
|
|
|
|
|
|
|
|
|
file->Skip(4); // Skip pointer _mAnimData
|
|
|
|
|
monster.AnimInfo = {};
|
|
|
|
|
monster.AnimInfo.TicksPerFrame = file->NextLE<int32_t>();
|
|
|
|
|
monster.AnimInfo.TickCounterOfCurrentFrame = file->NextLE<int32_t>();
|
|
|
|
|
monster.AnimInfo.NumberOfFrames = file->NextLE<int32_t>();
|
|
|
|
|
monster.AnimInfo.CurrentFrame = file->NextLE<int32_t>() - 1;
|
|
|
|
|
file->Skip(4); // Skip _meflag
|
|
|
|
|
monster._mDelFlag = file->NextBool32();
|
|
|
|
|
monster._mVar1 = file->NextLE<int32_t>();
|
|
|
|
|
monster._mVar2 = file->NextLE<int32_t>();
|
|
|
|
|
monster._mVar3 = file->NextLE<int32_t>();
|
|
|
|
|
monster.position.temp.x = file->NextLE<int32_t>();
|
|
|
|
|
monster.position.temp.y = file->NextLE<int32_t>();
|
|
|
|
|
monster.position.offset2.deltaX = file->NextLE<int32_t>();
|
|
|
|
|
monster.position.offset2.deltaY = file->NextLE<int32_t>();
|
|
|
|
|
file->Skip(4); // Skip actionFrame
|
|
|
|
|
monster._mmaxhp = file->NextLE<int32_t>();
|
|
|
|
|
monster._mhitpoints = file->NextLE<int32_t>();
|
|
|
|
|
|
|
|
|
|
monster._mAi = static_cast<_mai_id>(file->NextLE<uint8_t>());
|
|
|
|
|
monster._mint = file->NextLE<uint8_t>();
|
|
|
|
|
file->Skip(2); // Alignment
|
|
|
|
|
monster._mFlags = file->NextLE<uint32_t>();
|
|
|
|
|
monster._msquelch = file->NextLE<uint8_t>();
|
|
|
|
|
file->Skip(3); // Alignment
|
|
|
|
|
file->Skip(4); // Unused
|
|
|
|
|
monster.position.last.x = file->NextLE<int32_t>();
|
|
|
|
|
monster.position.last.y = file->NextLE<int32_t>();
|
|
|
|
|
monster._mRndSeed = file->NextLE<uint32_t>();
|
|
|
|
|
monster._mAISeed = file->NextLE<uint32_t>();
|
|
|
|
|
file->Skip(4); // Unused
|
|
|
|
|
|
|
|
|
|
monster._uniqtype = file->NextLE<uint8_t>();
|
|
|
|
|
monster._uniqtrans = file->NextLE<uint8_t>();
|
|
|
|
|
monster._udeadval = file->NextLE<int8_t>();
|
|
|
|
|
|
|
|
|
|
monster.mWhoHit = file->NextLE<int8_t>();
|
|
|
|
|
monster.mLevel = file->NextLE<int8_t>();
|
|
|
|
|
file->Skip(1); // Alignment
|
|
|
|
|
monster.mExp = file->NextLE<uint16_t>();
|
|
|
|
|
|
|
|
|
|
if ((monster._mFlags & MFLAG_GOLEM) != 0) // Don't skip for golems
|
|
|
|
|
monster.mHit = file->NextLE<uint8_t>();
|
|
|
|
|
else
|
|
|
|
|
file->Skip(1); // Skip mHit as it's already initialized
|
|
|
|
|
monster.mMinDamage = file->NextLE<uint8_t>();
|
|
|
|
|
monster.mMaxDamage = file->NextLE<uint8_t>();
|
|
|
|
|
file->Skip(1); // Skip mHit2 as it's already initialized
|
|
|
|
|
monster.mMinDamage2 = file->NextLE<uint8_t>();
|
|
|
|
|
monster.mMaxDamage2 = file->NextLE<uint8_t>();
|
|
|
|
|
monster.mArmorClass = file->NextLE<uint8_t>();
|
|
|
|
|
file->Skip(1); // Alignment
|
|
|
|
|
monster.mMagicRes = file->NextLE<uint16_t>();
|
|
|
|
|
file->Skip(2); // Alignment
|
|
|
|
|
|
|
|
|
|
monster.mtalkmsg = static_cast<_speech_id>(file->NextLE<int32_t>());
|
|
|
|
|
if (monster.mtalkmsg == TEXT_KING1) // Fix original bad mapping of NONE for monsters
|
|
|
|
|
monster.mtalkmsg = TEXT_NONE;
|
|
|
|
|
monster.leader = file->NextLE<uint8_t>();
|
|
|
|
|
monster.leaderRelation = static_cast<LeaderRelation>(file->NextLE<uint8_t>());
|
|
|
|
|
monster.packsize = file->NextLE<uint8_t>();
|
|
|
|
|
monster.mlid = file->NextLE<int8_t>();
|
|
|
|
|
if (monster.mlid == 0)
|
|
|
|
|
monster.mlid = NO_LIGHT; // Correct incorect values in old saves
|
|
|
|
|
|
|
|
|
|
if ((monster._mFlags & MFLAG_BERSERK) != 0) {
|
|
|
|
|
int lightRadius = (currlevel < 17 || currlevel > 20) ? 3 : 9;
|
|
|
|
|
monster.mlid = AddLight(monster.position.tile, lightRadius);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Omit pointer mName;
|
|
|
|
|
// Omit pointer MType;
|
|
|
|
|
// Omit pointer MData;
|
|
|
|
|
|
|
|
|
|
if (gbSkipSync)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
SyncMonsterAnim(monster);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Recalculate the pack size of monster group that may have underflown
|
|
|
|
|
*/
|
|
|
|
|
void SyncPackSize(Monster &leader)
|
|
|
|
|
{
|
|
|
|
|
if (leader._uniqtype == 0)
|
|
|
|
|
return;
|
|
|
|
|
if (leader._mAi != AI_SCAV)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
leader.packsize = 0;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < ActiveMonsterCount; i++) {
|
|
|
|
|
auto &minion = Monsters[ActiveMonsters[i]];
|
|
|
|
|
if (minion.leaderRelation == LeaderRelation::Leashed && &Monsters[minion.leader] == &leader)
|
|
|
|
|
leader.packsize++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadMissile(LoadHelper *file)
|
|
|
|
|
{
|
|
|
|
|
Missile missile = {};
|
|
|
|
|
missile._mitype = static_cast<missile_id>(file->NextLE<int32_t>());
|
|
|
|
|
missile.position.tile.x = file->NextLE<int32_t>();
|
|
|
|
|
missile.position.tile.y = file->NextLE<int32_t>();
|
|
|
|
|
missile.position.offset.deltaX = file->NextLE<int32_t>();
|
|
|
|
|
missile.position.offset.deltaY = file->NextLE<int32_t>();
|
|
|
|
|
missile.position.velocity.deltaX = file->NextLE<int32_t>();
|
|
|
|
|
missile.position.velocity.deltaY = file->NextLE<int32_t>();
|
|
|
|
|
missile.position.start.x = file->NextLE<int32_t>();
|
|
|
|
|
missile.position.start.y = file->NextLE<int32_t>();
|
|
|
|
|
missile.position.traveled.deltaX = file->NextLE<int32_t>();
|
|
|
|
|
missile.position.traveled.deltaY = file->NextLE<int32_t>();
|
|
|
|
|
missile._mimfnum = file->NextLE<int32_t>();
|
|
|
|
|
missile._mispllvl = file->NextLE<int32_t>();
|
|
|
|
|
missile._miDelFlag = file->NextBool32();
|
|
|
|
|
missile._miAnimType = file->NextLE<uint8_t>();
|
|
|
|
|
file->Skip(3); // Alignment
|
|
|
|
|
missile._miAnimFlags = static_cast<MissileDataFlags>(file->NextLE<int32_t>());
|
|
|
|
|
file->Skip(4); // Skip pointer _miAnimData
|
|
|
|
|
missile._miAnimDelay = file->NextLE<int32_t>();
|
|
|
|
|
missile._miAnimLen = file->NextLE<int32_t>();
|
|
|
|
|
missile._miAnimWidth = file->NextLE<int32_t>();
|
|
|
|
|
missile._miAnimWidth2 = file->NextLE<int32_t>();
|
|
|
|
|
missile._miAnimCnt = file->NextLE<int32_t>();
|
|
|
|
|
missile._miAnimAdd = file->NextLE<int32_t>();
|
|
|
|
|
missile._miAnimFrame = file->NextLE<int32_t>();
|
|
|
|
|
missile._miDrawFlag = file->NextBool32();
|
|
|
|
|
missile._miLightFlag = file->NextBool32();
|
|
|
|
|
missile._miPreFlag = file->NextBool32();
|
|
|
|
|
missile._miUniqTrans = file->NextLE<uint32_t>();
|
|
|
|
|
missile._mirange = file->NextLE<int32_t>();
|
|
|
|
|
missile._misource = file->NextLE<int32_t>();
|
|
|
|
|
missile._micaster = static_cast<mienemy_type>(file->NextLE<int32_t>());
|
|
|
|
|
missile._midam = file->NextLE<int32_t>();
|
|
|
|
|
missile._miHitFlag = file->NextBool32();
|
|
|
|
|
missile._midist = file->NextLE<int32_t>();
|
|
|
|
|
missile._mlid = file->NextLE<int32_t>();
|
|
|
|
|
missile._mirnd = file->NextLE<int32_t>();
|
|
|
|
|
missile.var1 = file->NextLE<int32_t>();
|
|
|
|
|
missile.var2 = file->NextLE<int32_t>();
|
|
|
|
|
missile.var3 = file->NextLE<int32_t>();
|
|
|
|
|
missile.var4 = file->NextLE<int32_t>();
|
|
|
|
|
missile.var5 = file->NextLE<int32_t>();
|
|
|
|
|
missile.var6 = file->NextLE<int32_t>();
|
|
|
|
|
missile.var7 = file->NextLE<int32_t>();
|
|
|
|
|
missile.limitReached = file->NextBool32();
|
|
|
|
|
missile.lastCollisionTargetHash = 0;
|
|
|
|
|
if (Missiles.size() < Missiles.max_size()) {
|
|
|
|
|
Missiles.push_back(missile);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadObject(LoadHelper &file, Object &object)
|
|
|
|
|
{
|
|
|
|
|
object._otype = static_cast<_object_id>(file.NextLE<int32_t>());
|
|
|
|
|
object.position.x = file.NextLE<int32_t>();
|
|
|
|
|
object.position.y = file.NextLE<int32_t>();
|
|
|
|
|
object._oLight = file.NextBool32();
|
|
|
|
|
object._oAnimFlag = file.NextLE<uint32_t>();
|
|
|
|
|
file.Skip(4); // Skip pointer _oAnimData
|
|
|
|
|
object._oAnimDelay = file.NextLE<int32_t>();
|
|
|
|
|
object._oAnimCnt = file.NextLE<int32_t>();
|
|
|
|
|
object._oAnimLen = file.NextLE<uint32_t>();
|
|
|
|
|
object._oAnimFrame = file.NextLE<uint32_t>();
|
|
|
|
|
object._oAnimWidth = static_cast<uint16_t>(file.NextLE<int32_t>());
|
|
|
|
|
file.Skip(4); // Skip _oAnimWidth2
|
|
|
|
|
object._oDelFlag = file.NextBool32();
|
|
|
|
|
object._oBreak = file.NextLE<int8_t>();
|
|
|
|
|
file.Skip(3); // Alignment
|
|
|
|
|
object._oSolidFlag = file.NextBool32();
|
|
|
|
|
object._oMissFlag = file.NextBool32();
|
|
|
|
|
|
|
|
|
|
object._oSelFlag = file.NextLE<int8_t>();
|
|
|
|
|
file.Skip(3); // Alignment
|
|
|
|
|
object._oPreFlag = file.NextBool32();
|
|
|
|
|
object._oTrapFlag = file.NextBool32();
|
|
|
|
|
object._oDoorFlag = file.NextBool32();
|
|
|
|
|
object._olid = file.NextLE<int32_t>();
|
|
|
|
|
object._oRndSeed = file.NextLE<uint32_t>();
|
|
|
|
|
object._oVar1 = file.NextLE<int32_t>();
|
|
|
|
|
object._oVar2 = file.NextLE<int32_t>();
|
|
|
|
|
object._oVar3 = file.NextLE<int32_t>();
|
|
|
|
|
object._oVar4 = file.NextLE<int32_t>();
|
|
|
|
|
object._oVar5 = file.NextLE<int32_t>();
|
|
|
|
|
object._oVar6 = file.NextLE<uint32_t>();
|
|
|
|
|
object.bookMessage = static_cast<_speech_id>(file.NextLE<int32_t>());
|
|
|
|
|
object._oVar8 = file.NextLE<int32_t>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadItem(LoadHelper &file, Item &item)
|
|
|
|
|
{
|
|
|
|
|
LoadItemData(file, item);
|
|
|
|
|
GetItemFrm(item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadPremium(LoadHelper &file, int i)
|
|
|
|
|
{
|
|
|
|
|
LoadItemData(file, premiumitems[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadQuest(LoadHelper *file, int i)
|
|
|
|
|
{
|
|
|
|
|
auto &quest = Quests[i];
|
|
|
|
|
|
|
|
|
|
quest._qlevel = file->NextLE<uint8_t>();
|
|
|
|
|
file->Skip<uint8_t>(); // _qtype, identical to _qidx
|
|
|
|
|
quest._qactive = static_cast<quest_state>(file->NextLE<uint8_t>());
|
|
|
|
|
quest._qlvltype = static_cast<dungeon_type>(file->NextLE<uint8_t>());
|
|
|
|
|
quest.position.x = file->NextLE<int32_t>();
|
|
|
|
|
quest.position.y = file->NextLE<int32_t>();
|
|
|
|
|
quest._qslvl = static_cast<_setlevels>(file->NextLE<uint8_t>());
|
|
|
|
|
quest._qidx = static_cast<quest_id>(file->NextLE<uint8_t>());
|
|
|
|
|
if (gbIsHellfireSaveGame) {
|
|
|
|
|
file->Skip(2); // Alignment
|
|
|
|
|
quest._qmsg = static_cast<_speech_id>(file->NextLE<int32_t>());
|
|
|
|
|
} else {
|
|
|
|
|
quest._qmsg = static_cast<_speech_id>(file->NextLE<uint8_t>());
|
|
|
|
|
}
|
|
|
|
|
quest._qvar1 = file->NextLE<uint8_t>();
|
|
|
|
|
quest._qvar2 = file->NextLE<uint8_t>();
|
|
|
|
|
file->Skip(2); // Alignment
|
|
|
|
|
if (!gbIsHellfireSaveGame)
|
|
|
|
|
file->Skip(1); // Alignment
|
|
|
|
|
quest._qlog = file->NextBool32();
|
|
|
|
|
|
|
|
|
|
ReturnLvlPosition.x = file->NextBE<int32_t>();
|
|
|
|
|
ReturnLvlPosition.y = file->NextBE<int32_t>();
|
|
|
|
|
ReturnLevel = file->NextBE<int32_t>();
|
|
|
|
|
ReturnLevelType = static_cast<dungeon_type>(file->NextBE<int32_t>());
|
|
|
|
|
file->Skip(sizeof(int32_t)); // Skip DoomQuestState
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadLighting(LoadHelper *file, Light *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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadPortal(LoadHelper *file, int i)
|
|
|
|
|
{
|
|
|
|
|
Portal *pPortal = &Portals[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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConvertLevels()
|
|
|
|
|
{
|
|
|
|
|
// Backup current level state
|
|
|
|
|
bool tmpSetlevel = setlevel;
|
|
|
|
|
_setlevels tmpSetlvlnum = setlvlnum;
|
|
|
|
|
int tmpCurrlevel = currlevel;
|
|
|
|
|
dungeon_type tmpLeveltype = leveltype;
|
|
|
|
|
|
|
|
|
|
gbSkipSync = true;
|
|
|
|
|
|
|
|
|
|
setlevel = false; // Convert regular levels
|
|
|
|
|
for (int i = 0; i < giNumberOfLevels; i++) {
|
|
|
|
|
currlevel = i;
|
|
|
|
|
if (!LevelFileExists())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
leveltype = gnLevelTypeTbl[i];
|
|
|
|
|
|
|
|
|
|
LoadLevel();
|
|
|
|
|
SaveLevel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setlevel = true; // Convert quest levels
|
|
|
|
|
for (auto &quest : Quests) {
|
|
|
|
|
if (quest._qactive == QUEST_NOTAVAIL) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leveltype = quest._qlvltype;
|
|
|
|
|
if (leveltype == DTYPE_NONE) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setlvlnum = quest._qslvl;
|
|
|
|
|
if (!LevelFileExists())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
LoadLevel();
|
|
|
|
|
SaveLevel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gbSkipSync = false;
|
|
|
|
|
|
|
|
|
|
// Restor current level state
|
|
|
|
|
setlevel = tmpSetlevel;
|
|
|
|
|
setlvlnum = tmpSetlvlnum;
|
|
|
|
|
currlevel = tmpCurrlevel;
|
|
|
|
|
leveltype = tmpLeveltype;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadMatchingItems(LoadHelper &file, const int n, Item *pItem)
|
|
|
|
|
{
|
|
|
|
|
Item 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Loads items on the current dungeon floor
|
|
|
|
|
* @param file interface to the save file
|
|
|
|
|
* @param savedItemCount how many items to read from the save file
|
|
|
|
|
*/
|
|
|
|
|
void LoadDroppedItems(LoadHelper &file, size_t savedItemCount)
|
|
|
|
|
{
|
|
|
|
|
// Skip loading ActiveItems and AvailableItems, the indices are initialised below based on the number of valid items
|
|
|
|
|
file.Skip<uint8_t>(MAXITEMS * 2);
|
|
|
|
|
|
|
|
|
|
// Reset ActiveItems, the Items array will be populated from the start
|
|
|
|
|
std::iota(ActiveItems, ActiveItems + MAXITEMS, 0);
|
|
|
|
|
ActiveItemCount = 0;
|
|
|
|
|
// Clear dItem so we can populate valid drop locations
|
|
|
|
|
memset(dItem, 0, sizeof(dItem));
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < savedItemCount; i++) {
|
|
|
|
|
Item &item = Items[ActiveItemCount];
|
|
|
|
|
LoadItem(file, item);
|
|
|
|
|
|
|
|
|
|
if (!item.isEmpty()) {
|
|
|
|
|
// Loaded a valid item
|
|
|
|
|
ActiveItemCount++;
|
|
|
|
|
// populate its location in the lookup table with the offset in the Items array + 1 (so 0 can be used for "no item")
|
|
|
|
|
dItem[item.position.x][item.position.y] = ActiveItemCount;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SaveItem(SaveHelper &file, const Item &item)
|
|
|
|
|
{
|
|
|
|
|
auto idx = item.IDidx;
|
|
|
|
|
if (!gbIsHellfire)
|
|
|
|
|
idx = RemapItemIdxToDiablo(idx);
|
|
|
|
|
if (gbIsSpawn)
|
|
|
|
|
idx = RemapItemIdxToSpawn(idx);
|
|
|
|
|
ItemType iType = item._itype;
|
|
|
|
|
if (idx == -1) {
|
|
|
|
|
idx = _item_indexes::IDI_GOLD;
|
|
|
|
|
iType = ItemType::None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file.WriteLE<int32_t>(item._iSeed);
|
|
|
|
|
file.WriteLE<int16_t>(item._iCreateInfo);
|
|
|
|
|
file.Skip(2); // Alignment
|
|
|
|
|
file.WriteLE<int32_t>(static_cast<int32_t>(iType));
|
|
|
|
|
file.WriteLE<int32_t>(item.position.x);
|
|
|
|
|
file.WriteLE<int32_t>(item.position.y);
|
|
|
|
|
file.WriteLE<uint32_t>(item._iAnimFlag ? 1 : 0);
|
|
|
|
|
file.Skip(4); // Skip pointer _iAnimData
|
|
|
|
|
file.WriteLE<int32_t>(item.AnimInfo.NumberOfFrames);
|
|
|
|
|
file.WriteLE<int32_t>(item.AnimInfo.CurrentFrame + 1);
|
|
|
|
|
// write _iAnimWidth for vanilla compatibility
|
|
|
|
|
file.WriteLE<int32_t>(ItemAnimWidth);
|
|
|
|
|
// write _iAnimWidth2 for vanilla compatibility
|
|
|
|
|
file.WriteLE<int32_t>(CalculateWidth2(ItemAnimWidth));
|
|
|
|
|
file.Skip<uint32_t>(); // _delFlag, unused since 1.02
|
|
|
|
|
file.WriteLE<uint8_t>(item._iSelFlag);
|
|
|
|
|
file.Skip(3); // Alignment
|
|
|
|
|
file.WriteLE<uint32_t>(item._iPostDraw ? 1 : 0);
|
|
|
|
|
file.WriteLE<uint32_t>(item._iIdentified ? 1 : 0);
|
|
|
|
|
file.WriteLE<int8_t>(item._iMagical);
|
|
|
|
|
file.WriteBytes(item._iName, 64);
|
|
|
|
|
file.WriteBytes(item._iIName, 64);
|
|
|
|
|
file.WriteLE<int8_t>(item._iLoc);
|
|
|
|
|
file.WriteLE<uint8_t>(item._iClass);
|
|
|
|
|
file.Skip(1); // Alignment
|
|
|
|
|
file.WriteLE<int32_t>(item._iCurs);
|
|
|
|
|
file.WriteLE<int32_t>(item._ivalue);
|
|
|
|
|
file.WriteLE<int32_t>(item._iIvalue);
|
|
|
|
|
file.WriteLE<int32_t>(item._iMinDam);
|
|
|
|
|
file.WriteLE<int32_t>(item._iMaxDam);
|
|
|
|
|
file.WriteLE<int32_t>(item._iAC);
|
|
|
|
|
file.WriteLE<uint32_t>(static_cast<uint32_t>(item._iFlags));
|
|
|
|
|
file.WriteLE<int32_t>(item._iMiscId);
|
|
|
|
|
file.WriteLE<int32_t>(item._iSpell);
|
|
|
|
|
file.WriteLE<int32_t>(item._iCharges);
|
|
|
|
|
file.WriteLE<int32_t>(item._iMaxCharges);
|
|
|
|
|
file.WriteLE<int32_t>(item._iDurability);
|
|
|
|
|
file.WriteLE<int32_t>(item._iMaxDur);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLDam);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLToHit);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLAC);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLStr);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLMag);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLDex);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLVit);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLFR);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLLR);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLMR);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLMana);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLHP);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLDamMod);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLGetHit);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLLight);
|
|
|
|
|
file.WriteLE<int8_t>(item._iSplLvlAdd);
|
|
|
|
|
file.WriteLE<int8_t>(item._iRequest ? 1 : 0);
|
|
|
|
|
file.Skip(2); // Alignment
|
|
|
|
|
file.WriteLE<int32_t>(item._iUid);
|
|
|
|
|
file.WriteLE<int32_t>(item._iFMinDam);
|
|
|
|
|
file.WriteLE<int32_t>(item._iFMaxDam);
|
|
|
|
|
file.WriteLE<int32_t>(item._iLMinDam);
|
|
|
|
|
file.WriteLE<int32_t>(item._iLMaxDam);
|
|
|
|
|
file.WriteLE<int32_t>(item._iPLEnAc);
|
|
|
|
|
file.WriteLE<int8_t>(item._iPrePower);
|
|
|
|
|
file.WriteLE<int8_t>(item._iSufPower);
|
|
|
|
|
file.Skip(2); // Alignment
|
|
|
|
|
file.WriteLE<int32_t>(item._iVAdd1);
|
|
|
|
|
file.WriteLE<int32_t>(item._iVMult1);
|
|
|
|
|
file.WriteLE<int32_t>(item._iVAdd2);
|
|
|
|
|
file.WriteLE<int32_t>(item._iVMult2);
|
|
|
|
|
file.WriteLE<int8_t>(item._iMinStr);
|
|
|
|
|
file.WriteLE<uint8_t>(item._iMinMag);
|
|
|
|
|
file.WriteLE<int8_t>(item._iMinDex);
|
|
|
|
|
file.Skip(1); // Alignment
|
|
|
|
|
file.WriteLE<uint32_t>(item._iStatFlag ? 1 : 0);
|
|
|
|
|
file.WriteLE<int32_t>(idx);
|
|
|
|
|
file.WriteLE<uint32_t>(item.dwBuff);
|
|
|
|
|
if (gbIsHellfire)
|
|
|
|
|
file.WriteLE<uint32_t>(static_cast<uint32_t>(item._iDamAcFlags));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SavePlayer(SaveHelper &file, const Player &player)
|
|
|
|
|
{
|
|
|
|
|
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>(static_cast<int32_t>(player.destParam3));
|
|
|
|
|
file.WriteLE<int32_t>(player.destParam4);
|
|
|
|
|
file.WriteLE<uint32_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>(static_cast<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 + 1);
|
|
|
|
|
// write _pAnimWidth for vanilla compatibility
|
|
|
|
|
int animWidth = player.AnimInfo.celSprite ? player.AnimInfo.celSprite->Width() : 96;
|
|
|
|
|
file.WriteLE<int32_t>(animWidth);
|
|
|
|
|
// write _pAnimWidth2 for vanilla compatibility
|
|
|
|
|
file.WriteLE<int32_t>(CalculateWidth2(animWidth));
|
|
|
|
|
file.Skip<uint32_t>(); // 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.Skip<int8_t>(); // Skip _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.Skip<int8_t>(); // Skip _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>(static_cast<uint8_t>(player._pSpellFlags));
|
|
|
|
|
file.Skip(3); // Alignment
|
|
|
|
|
|
|
|
|
|
// Extra hotkeys: to keep single player save compatibility, write only 4 hotkeys here, rely on SaveHotkeys for the rest
|
|
|
|
|
for (size_t i = 0; i < 4; i++) {
|
|
|
|
|
file.WriteLE<int32_t>(player._pSplHotKey[i]);
|
|
|
|
|
}
|
|
|
|
|
for (size_t i = 0; i < 4; i++) {
|
|
|
|
|
file.WriteLE<uint8_t>(player._pSplTHotKey[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file.WriteLE<int32_t>(player.UsesRangedWeapon() ? 1 : 0);
|
|
|
|
|
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<int32_t>(); // Skip _pHPPer
|
|
|
|
|
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<int32_t>(); // Skip _pManaPer
|
|
|
|
|
file.WriteLE<int8_t>(player._pLevel);
|
|
|
|
|
file.WriteLE<int8_t>(player._pMaxLvl);
|
|
|
|
|
file.Skip(2); // Alignment
|
|
|
|
|
file.WriteLE<uint32_t>(player._pExperience);
|
|
|
|
|
file.Skip<uint32_t>(); // Skip _pMaxExp
|
|
|
|
|
file.WriteLE<uint32_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>(static_cast<int32_t>(player.tempDirection));
|
|
|
|
|
file.WriteLE<int32_t>(player.spellLevel);
|
|
|
|
|
file.Skip<int32_t>(); // skip _pVar5, was used for storing position of a tile which should have its HorizontalMovingPlayer flag removed after walking
|
|
|
|
|
file.WriteLE<int32_t>(player.position.offset2.deltaX);
|
|
|
|
|
file.WriteLE<int32_t>(player.position.offset2.deltaY);
|
|
|
|
|
file.Skip<int32_t>(); // Skip _pVar8
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
file.Skip<int32_t>(); // Skip _pGFXLoad
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
for (const Item &item : player.InvBody)
|
|
|
|
|
SaveItem(file, item);
|
|
|
|
|
|
|
|
|
|
for (const Item &item : player.InvList)
|
|
|
|
|
SaveItem(file, item);
|
|
|
|
|
|
|
|
|
|
file.WriteLE<int32_t>(player._pNumInv);
|
|
|
|
|
|
|
|
|
|
for (int8_t cell : player.InvGrid)
|
|
|
|
|
file.WriteLE<int8_t>(cell);
|
|
|
|
|
|
|
|
|
|
for (const Item &item : player.SpdList)
|
|
|
|
|
SaveItem(file, item);
|
|
|
|
|
|
|
|
|
|
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>(static_cast<int32_t>(player._pIFlags));
|
|
|
|
|
file.WriteLE<int32_t>(player._pIGetHit);
|
|
|
|
|
|
|
|
|
|
file.WriteLE<int8_t>(player._pISplLvlAdd);
|
|
|
|
|
file.Skip<uint8_t>(); // Skip _pISplCost
|
|
|
|
|
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>(static_cast<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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SaveMonster(SaveHelper *file, Monster &monster)
|
|
|
|
|
{
|
|
|
|
|
file->WriteLE<int32_t>(monster._mMTidx);
|
|
|
|
|
file->WriteLE<int32_t>(static_cast<int>(monster._mmode));
|
|
|
|
|
file->WriteLE<uint8_t>(monster._mgoal);
|
|
|
|
|
file->Skip(3); // Alignment
|
|
|
|
|
file->WriteLE<int32_t>(monster._mgoalvar1);
|
|
|
|
|
file->WriteLE<int32_t>(monster._mgoalvar2);
|
|
|
|
|
file->WriteLE<int32_t>(monster._mgoalvar3);
|
|
|
|
|
file->Skip(4); // Unused
|
|
|
|
|
file->WriteLE<uint8_t>(monster._pathcount);
|
|
|
|
|
file->Skip(3); // Alignment
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.tile.x);
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.tile.y);
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.future.x);
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.future.y);
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.old.x);
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.old.y);
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.offset.deltaX);
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.offset.deltaY);
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.velocity.deltaX);
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.velocity.deltaY);
|
|
|
|
|
file->WriteLE<int32_t>(static_cast<int32_t>(monster._mdir));
|
|
|
|
|
file->WriteLE<int32_t>(monster._menemy);
|
|
|
|
|
file->WriteLE<uint8_t>(monster.enemyPosition.x);
|
|
|
|
|
file->WriteLE<uint8_t>(monster.enemyPosition.y);
|
|
|
|
|
file->Skip(2); // Unused
|
|
|
|
|
|
|
|
|
|
file->Skip(4); // Skip pointer _mAnimData
|
|
|
|
|
file->WriteLE<int32_t>(monster.AnimInfo.TicksPerFrame);
|
|
|
|
|
file->WriteLE<int32_t>(monster.AnimInfo.TickCounterOfCurrentFrame);
|
|
|
|
|
file->WriteLE<int32_t>(monster.AnimInfo.NumberOfFrames);
|
|
|
|
|
file->WriteLE<int32_t>(monster.AnimInfo.CurrentFrame + 1);
|
|
|
|
|
file->Skip<uint32_t>(); // Skip _meflag
|
|
|
|
|
file->WriteLE<uint32_t>(monster._mDelFlag ? 1 : 0);
|
|
|
|
|
file->WriteLE<int32_t>(monster._mVar1);
|
|
|
|
|
file->WriteLE<int32_t>(monster._mVar2);
|
|
|
|
|
file->WriteLE<int32_t>(monster._mVar3);
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.temp.x);
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.temp.y);
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.offset2.deltaX);
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.offset2.deltaY);
|
|
|
|
|
file->Skip<int32_t>(); // Skip _mVar8
|
|
|
|
|
file->WriteLE<int32_t>(monster._mmaxhp);
|
|
|
|
|
file->WriteLE<int32_t>(monster._mhitpoints);
|
|
|
|
|
|
|
|
|
|
file->WriteLE<uint8_t>(monster._mAi);
|
|
|
|
|
file->WriteLE<uint8_t>(monster._mint);
|
|
|
|
|
file->Skip(2); // Alignment
|
|
|
|
|
file->WriteLE<uint32_t>(monster._mFlags);
|
|
|
|
|
file->WriteLE<uint8_t>(monster._msquelch);
|
|
|
|
|
file->Skip(3); // Alignment
|
|
|
|
|
file->Skip(4); // Unused
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.last.x);
|
|
|
|
|
file->WriteLE<int32_t>(monster.position.last.y);
|
|
|
|
|
file->WriteLE<uint32_t>(monster._mRndSeed);
|
|
|
|
|
file->WriteLE<uint32_t>(monster._mAISeed);
|
|
|
|
|
file->Skip(4); // Unused
|
|
|
|
|
|
|
|
|
|
file->WriteLE<uint8_t>(monster._uniqtype);
|
|
|
|
|
file->WriteLE<uint8_t>(monster._uniqtrans);
|
|
|
|
|
file->WriteLE<int8_t>(monster._udeadval);
|
|
|
|
|
|
|
|
|
|
file->WriteLE<int8_t>(monster.mWhoHit);
|
|
|
|
|
file->WriteLE<int8_t>(monster.mLevel);
|
|
|
|
|
file->Skip(1); // Alignment
|
|
|
|
|
file->WriteLE<uint16_t>(monster.mExp);
|
|
|
|
|
|
|
|
|
|
file->WriteLE<uint8_t>(static_cast<uint8_t>(std::min<uint16_t>(monster.mHit, std::numeric_limits<uint8_t>::max()))); // For backwards compatibility
|
|
|
|
|
file->WriteLE<uint8_t>(monster.mMinDamage);
|
|
|
|
|
file->WriteLE<uint8_t>(monster.mMaxDamage);
|
|
|
|
|
file->WriteLE<uint8_t>(static_cast<uint8_t>(std::min<uint16_t>(monster.mHit2, std::numeric_limits<uint8_t>::max()))); // For backwards compatibility
|
|
|
|
|
file->WriteLE<uint8_t>(monster.mMinDamage2);
|
|
|
|
|
file->WriteLE<uint8_t>(monster.mMaxDamage2);
|
|
|
|
|
file->WriteLE<uint8_t>(monster.mArmorClass);
|
|
|
|
|
file->Skip(1); // Alignment
|
|
|
|
|
file->WriteLE<uint16_t>(monster.mMagicRes);
|
|
|
|
|
file->Skip(2); // Alignment
|
|
|
|
|
|
|
|
|
|
file->WriteLE<int32_t>(monster.mtalkmsg == TEXT_NONE ? 0 : monster.mtalkmsg); // Replicate original bad mapping of none for monsters
|
|
|
|
|
file->WriteLE<uint8_t>(monster.leader);
|
|
|
|
|
file->WriteLE<uint8_t>(static_cast<std::uint8_t>(monster.leaderRelation));
|
|
|
|
|
file->WriteLE<uint8_t>(monster.packsize);
|
|
|
|
|
// vanilla compatibility
|
|
|
|
|
if (monster.mlid == NO_LIGHT)
|
|
|
|
|
file->WriteLE<int8_t>(0);
|
|
|
|
|
else
|
|
|
|
|
file->WriteLE<int8_t>(monster.mlid);
|
|
|
|
|
|
|
|
|
|
// Omit pointer mName;
|
|
|
|
|
// Omit pointer MType;
|
|
|
|
|
// Omit pointer MData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SaveMissile(SaveHelper *file, const Missile &missile)
|
|
|
|
|
{
|
|
|
|
|
file->WriteLE<int32_t>(missile._mitype);
|
|
|
|
|
file->WriteLE<int32_t>(missile.position.tile.x);
|
|
|
|
|
file->WriteLE<int32_t>(missile.position.tile.y);
|
|
|
|
|
file->WriteLE<int32_t>(missile.position.offset.deltaX);
|
|
|
|
|
file->WriteLE<int32_t>(missile.position.offset.deltaY);
|
|
|
|
|
file->WriteLE<int32_t>(missile.position.velocity.deltaX);
|
|
|
|
|
file->WriteLE<int32_t>(missile.position.velocity.deltaY);
|
|
|
|
|
file->WriteLE<int32_t>(missile.position.start.x);
|
|
|
|
|
file->WriteLE<int32_t>(missile.position.start.y);
|
|
|
|
|
file->WriteLE<int32_t>(missile.position.traveled.deltaX);
|
|
|
|
|
file->WriteLE<int32_t>(missile.position.traveled.deltaY);
|
|
|
|
|
file->WriteLE<int32_t>(missile._mimfnum);
|
|
|
|
|
file->WriteLE<int32_t>(missile._mispllvl);
|
|
|
|
|
file->WriteLE<uint32_t>(missile._miDelFlag ? 1 : 0);
|
|
|
|
|
file->WriteLE<uint8_t>(missile._miAnimType);
|
|
|
|
|
file->Skip(3); // Alignment
|
|
|
|
|
file->WriteLE<int32_t>(static_cast<int32_t>(missile._miAnimFlags));
|
|
|
|
|
file->Skip(4); // Skip pointer _miAnimData
|
|
|
|
|
file->WriteLE<int32_t>(missile._miAnimDelay);
|
|
|
|
|
file->WriteLE<int32_t>(missile._miAnimLen);
|
|
|
|
|
file->WriteLE<int32_t>(missile._miAnimWidth);
|
|
|
|
|
file->WriteLE<int32_t>(missile._miAnimWidth2);
|
|
|
|
|
file->WriteLE<int32_t>(missile._miAnimCnt);
|
|
|
|
|
file->WriteLE<int32_t>(missile._miAnimAdd);
|
|
|
|
|
file->WriteLE<int32_t>(missile._miAnimFrame);
|
|
|
|
|
file->WriteLE<uint32_t>(missile._miDrawFlag ? 1 : 0);
|
|
|
|
|
file->WriteLE<uint32_t>(missile._miLightFlag ? 1 : 0);
|
|
|
|
|
file->WriteLE<uint32_t>(missile._miPreFlag ? 1 : 0);
|
|
|
|
|
file->WriteLE<uint32_t>(missile._miUniqTrans);
|
|
|
|
|
file->WriteLE<int32_t>(missile._mirange);
|
|
|
|
|
file->WriteLE<int32_t>(missile._misource);
|
|
|
|
|
file->WriteLE<int32_t>(missile._micaster);
|
|
|
|
|
file->WriteLE<int32_t>(missile._midam);
|
|
|
|
|
file->WriteLE<uint32_t>(missile._miHitFlag ? 1 : 0);
|
|
|
|
|
file->WriteLE<int32_t>(missile._midist);
|
|
|
|
|
file->WriteLE<int32_t>(missile._mlid);
|
|
|
|
|
file->WriteLE<int32_t>(missile._mirnd);
|
|
|
|
|
file->WriteLE<int32_t>(missile.var1);
|
|
|
|
|
file->WriteLE<int32_t>(missile.var2);
|
|
|
|
|
file->WriteLE<int32_t>(missile.var3);
|
|
|
|
|
file->WriteLE<int32_t>(missile.var4);
|
|
|
|
|
file->WriteLE<int32_t>(missile.var5);
|
|
|
|
|
file->WriteLE<int32_t>(missile.var6);
|
|
|
|
|
file->WriteLE<int32_t>(missile.var7);
|
|
|
|
|
file->WriteLE<uint32_t>(missile.limitReached ? 1 : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SaveObject(SaveHelper &file, const Object &object)
|
|
|
|
|
{
|
|
|
|
|
file.WriteLE<int32_t>(object._otype);
|
|
|
|
|
file.WriteLE<int32_t>(object.position.x);
|
|
|
|
|
file.WriteLE<int32_t>(object.position.y);
|
|
|
|
|
file.WriteLE<uint32_t>(object._oLight ? 1 : 0);
|
|
|
|
|
file.WriteLE<uint32_t>(object._oAnimFlag);
|
|
|
|
|
file.Skip(4); // Skip pointer _oAnimData
|
|
|
|
|
file.WriteLE<int32_t>(object._oAnimDelay);
|
|
|
|
|
file.WriteLE<int32_t>(object._oAnimCnt);
|
|
|
|
|
file.WriteLE<uint32_t>(object._oAnimLen);
|
|
|
|
|
file.WriteLE<uint32_t>(object._oAnimFrame);
|
|
|
|
|
file.WriteLE<int32_t>(object._oAnimWidth);
|
|
|
|
|
file.WriteLE<int32_t>(CalculateWidth2(static_cast<int>(object._oAnimWidth))); // Write _oAnimWidth2 for vanilla compatibility
|
|
|
|
|
file.WriteLE<uint32_t>(object._oDelFlag ? 1 : 0);
|
|
|
|
|
file.WriteLE<int8_t>(object._oBreak);
|
|
|
|
|
file.Skip(3); // Alignment
|
|
|
|
|
file.WriteLE<uint32_t>(object._oSolidFlag ? 1 : 0);
|
|
|
|
|
file.WriteLE<uint32_t>(object._oMissFlag ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
file.WriteLE<int8_t>(object._oSelFlag);
|
|
|
|
|
file.Skip(3); // Alignment
|
|
|
|
|
file.WriteLE<uint32_t>(object._oPreFlag ? 1 : 0);
|
|
|
|
|
file.WriteLE<uint32_t>(object._oTrapFlag ? 1 : 0);
|
|
|
|
|
file.WriteLE<uint32_t>(object._oDoorFlag ? 1 : 0);
|
|
|
|
|
file.WriteLE<int32_t>(object._olid);
|
|
|
|
|
file.WriteLE<uint32_t>(object._oRndSeed);
|
|
|
|
|
file.WriteLE<int32_t>(object._oVar1);
|
|
|
|
|
file.WriteLE<int32_t>(object._oVar2);
|
|
|
|
|
file.WriteLE<int32_t>(object._oVar3);
|
|
|
|
|
file.WriteLE<int32_t>(object._oVar4);
|
|
|
|
|
file.WriteLE<int32_t>(object._oVar5);
|
|
|
|
|
file.WriteLE<uint32_t>(object._oVar6);
|
|
|
|
|
file.WriteLE<int32_t>(object.bookMessage);
|
|
|
|
|
file.WriteLE<int32_t>(object._oVar8);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SaveQuest(SaveHelper *file, int i)
|
|
|
|
|
{
|
|
|
|
|
auto &quest = Quests[i];
|
|
|
|
|
|
|
|
|
|
file->WriteLE<uint8_t>(quest._qlevel);
|
|
|
|
|
file->WriteLE<uint8_t>(quest._qidx); // _qtype for compatability, used in DRLG_CheckQuests
|
|
|
|
|
file->WriteLE<uint8_t>(quest._qactive);
|
|
|
|
|
file->WriteLE<uint8_t>(quest._qlvltype);
|
|
|
|
|
file->WriteLE<int32_t>(quest.position.x);
|
|
|
|
|
file->WriteLE<int32_t>(quest.position.y);
|
|
|
|
|
file->WriteLE<uint8_t>(quest._qslvl);
|
|
|
|
|
file->WriteLE<uint8_t>(quest._qidx);
|
|
|
|
|
if (gbIsHellfire) {
|
|
|
|
|
file->Skip(2); // Alignment
|
|
|
|
|
file->WriteLE<int32_t>(quest._qmsg);
|
|
|
|
|
} else {
|
|
|
|
|
file->WriteLE<uint8_t>(quest._qmsg);
|
|
|
|
|
}
|
|
|
|
|
file->WriteLE<uint8_t>(quest._qvar1);
|
|
|
|
|
file->WriteLE<uint8_t>(quest._qvar2);
|
|
|
|
|
file->Skip(2); // Alignment
|
|
|
|
|
if (!gbIsHellfire)
|
|
|
|
|
file->Skip(1); // Alignment
|
|
|
|
|
file->WriteLE<uint32_t>(quest._qlog ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
file->WriteBE<int32_t>(ReturnLvlPosition.x);
|
|
|
|
|
file->WriteBE<int32_t>(ReturnLvlPosition.y);
|
|
|
|
|
file->WriteBE<int32_t>(ReturnLevel);
|
|
|
|
|
file->WriteBE<int32_t>(ReturnLevelType);
|
|
|
|
|
file->Skip(sizeof(int32_t)); // Skip DoomQuestState
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SaveLighting(SaveHelper *file, Light *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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SavePortal(SaveHelper *file, int i)
|
|
|
|
|
{
|
|
|
|
|
Portal *pPortal = &Portals[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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Saves items on the current dungeon floor
|
|
|
|
|
* @param file interface to the save file
|
|
|
|
|
* @return a map converting from runtime item indexes to the relative position in the save file, used by SaveDroppedItemLocations
|
|
|
|
|
* @see SaveDroppedItemLocations
|
|
|
|
|
*/
|
|
|
|
|
std::unordered_map<uint8_t, uint8_t> SaveDroppedItems(SaveHelper &file)
|
|
|
|
|
{
|
|
|
|
|
// Vanilla Diablo/Hellfire initialise the ActiveItems and AvailableItems arrays based on saved data, so write valid values for compatibility
|
|
|
|
|
for (uint8_t i = 0; i < MAXITEMS; i++)
|
|
|
|
|
file.WriteLE<uint8_t>(i); // Strictly speaking everything from ActiveItemCount onwards is unused but no harm writing non-zero values here.
|
|
|
|
|
for (uint8_t i = 0; i < MAXITEMS; i++)
|
|
|
|
|
file.WriteLE<uint8_t>((i + ActiveItemCount) % MAXITEMS);
|
|
|
|
|
|
|
|
|
|
std::unordered_map<uint8_t, uint8_t> itemIndexes = { { 0, 0 } };
|
|
|
|
|
for (uint8_t i = 0; i < ActiveItemCount; i++) {
|
|
|
|
|
itemIndexes[ActiveItems[i] + 1] = i + 1;
|
|
|
|
|
SaveItem(file, Items[ActiveItems[i]]);
|
|
|
|
|
}
|
|
|
|
|
return itemIndexes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Saves the position of dropped items (in dItem)
|
|
|
|
|
* @param file interface to the save file
|
|
|
|
|
* @param indexMap a map converting from runtime item indexes to the relative position in the save file
|
|
|
|
|
*/
|
|
|
|
|
void SaveDroppedItemLocations(SaveHelper &file, const std::unordered_map<uint8_t, uint8_t> &itemIndexes)
|
|
|
|
|
{
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.WriteLE<uint8_t>(itemIndexes.at(dItem[i][j]));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr uint32_t VersionAdditionalMissiles = 0;
|
|
|
|
|
|
|
|
|
|
void SaveAdditionalMissiles()
|
|
|
|
|
{
|
|
|
|
|
constexpr size_t BytesWrittenBySaveMissile = 180;
|
|
|
|
|
uint32_t missileCountAdditional = (Missiles.size() > MaxMissilesForSaveGame) ? static_cast<uint32_t>(Missiles.size() - MaxMissilesForSaveGame) : 0;
|
|
|
|
|
SaveHelper file(CurrentSaveArchive(), "additionalMissiles", sizeof(uint32_t) + sizeof(uint32_t) + (missileCountAdditional * BytesWrittenBySaveMissile));
|
|
|
|
|
|
|
|
|
|
file.WriteLE<uint32_t>(VersionAdditionalMissiles);
|
|
|
|
|
file.WriteLE<uint32_t>(missileCountAdditional);
|
|
|
|
|
|
|
|
|
|
if (missileCountAdditional > 0) {
|
|
|
|
|
auto it = Missiles.cbegin();
|
|
|
|
|
// std::list::const_iterator doesn't provide operator+() :/ using std::advance to get past the missiles we've already saved
|
|
|
|
|
std::advance(it, MaxMissilesForSaveGame);
|
|
|
|
|
for (; it != Missiles.cend(); it++) {
|
|
|
|
|
SaveMissile(&file, *it);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadAdditionalMissiles()
|
|
|
|
|
{
|
|
|
|
|
LoadHelper file(OpenSaveArchive(gSaveNumber), "additionalMissiles");
|
|
|
|
|
|
|
|
|
|
if (!file.IsValid()) {
|
|
|
|
|
// no addtional Missiles saved
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto loadedVersion = file.NextLE<uint32_t>();
|
|
|
|
|
if (loadedVersion > VersionAdditionalMissiles) {
|
|
|
|
|
// unknown version
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
auto missileCountAdditional = file.NextLE<uint32_t>();
|
|
|
|
|
for (uint32_t i = 0U; i < missileCountAdditional; i++) {
|
|
|
|
|
LoadMissile(&file);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int DiabloItemSaveSize = 368;
|
|
|
|
|
const int HellfireItemSaveSize = 372;
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
void RemoveInvalidItem(Item &item)
|
|
|
|
|
{
|
|
|
|
|
bool isInvalid = !IsItemAvailable(item.IDidx) || !IsUniqueAvailable(item._iUid);
|
|
|
|
|
|
|
|
|
|
if (!gbIsHellfire) {
|
|
|
|
|
isInvalid = isInvalid || (item._itype == ItemType::Staff && GetSpellStaffLevel(item._iSpell) == -1);
|
|
|
|
|
isInvalid = isInvalid || (item._iMiscId == IMISC_BOOK && GetSpellBookLevel(item._iSpell) == -1);
|
|
|
|
|
isInvalid = isInvalid || item._iDamAcFlags != ItemSpecialEffectHf::None;
|
|
|
|
|
isInvalid = isInvalid || item._iPrePower > IPL_LASTDIABLO;
|
|
|
|
|
isInvalid = isInvalid || item._iSufPower > IPL_LASTDIABLO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isInvalid) {
|
|
|
|
|
item.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_item_indexes RemapItemIdxFromDiablo(_item_indexes i)
|
|
|
|
|
{
|
|
|
|
|
constexpr auto GetItemIdValue = [](int i) -> int {
|
|
|
|
|
if (i == IDI_SORCERER) {
|
|
|
|
|
return IDI_SORCERER_DIABLO;
|
|
|
|
|
}
|
|
|
|
|
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 == IDI_SORCERER_DIABLO) {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns the size of the hotkeys file with the number of hotkeys passed and if a header with the number of hotkeys is present in the file
|
|
|
|
|
size_t HotkeysSize(size_t nHotkeys = NumHotkeys)
|
|
|
|
|
{
|
|
|
|
|
// header spells spell types active spell active spell type
|
|
|
|
|
return sizeof(uint8_t) + (nHotkeys * sizeof(int32_t)) + (nHotkeys * sizeof(uint8_t)) + sizeof(int32_t) + sizeof(uint8_t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadHotkeys()
|
|
|
|
|
{
|
|
|
|
|
LoadHelper file(OpenSaveArchive(gSaveNumber), "hotkeys");
|
|
|
|
|
if (!file.IsValid())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto &myPlayer = Players[MyPlayerId];
|
|
|
|
|
size_t nHotkeys = 4; // Defaults to old save format number
|
|
|
|
|
|
|
|
|
|
// Refill the spell arrays with no selection
|
|
|
|
|
std::fill(myPlayer._pSplHotKey, myPlayer._pSplHotKey + NumHotkeys, SPL_INVALID);
|
|
|
|
|
std::fill(myPlayer._pSplTHotKey, myPlayer._pSplTHotKey + NumHotkeys, RSPLTYPE_INVALID);
|
|
|
|
|
|
|
|
|
|
// Checking if the save file has the old format with only 4 hotkeys and no header
|
|
|
|
|
if (file.IsValid(HotkeysSize(nHotkeys))) {
|
|
|
|
|
// The file contains a header byte and at least 4 entries, so we can assume it's a new format save
|
|
|
|
|
nHotkeys = file.NextLE<uint8_t>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read all hotkeys in the file
|
|
|
|
|
for (size_t i = 0; i < nHotkeys; i++) {
|
|
|
|
|
// Do not load hotkeys past the size of the spell types array, discard the rest
|
|
|
|
|
if (i < NumHotkeys) {
|
|
|
|
|
myPlayer._pSplHotKey[i] = static_cast<spell_id>(file.NextLE<int32_t>());
|
|
|
|
|
} else {
|
|
|
|
|
file.Skip<int32_t>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (size_t i = 0; i < nHotkeys; i++) {
|
|
|
|
|
// Do not load hotkeys past the size of the spells array, discard the rest
|
|
|
|
|
if (i < NumHotkeys) {
|
|
|
|
|
myPlayer._pSplTHotKey[i] = static_cast<spell_type>(file.NextLE<uint8_t>());
|
|
|
|
|
} else {
|
|
|
|
|
file.Skip<uint8_t>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load the selected spell last
|
|
|
|
|
myPlayer._pRSpell = static_cast<spell_id>(file.NextLE<int32_t>());
|
|
|
|
|
myPlayer._pRSplType = static_cast<spell_type>(file.NextLE<uint8_t>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SaveHotkeys()
|
|
|
|
|
{
|
|
|
|
|
auto &myPlayer = Players[MyPlayerId];
|
|
|
|
|
|
|
|
|
|
SaveHelper file(CurrentSaveArchive(), "hotkeys", HotkeysSize());
|
|
|
|
|
|
|
|
|
|
// Write the number of spell hotkeys
|
|
|
|
|
file.WriteLE<uint8_t>(static_cast<uint8_t>(NumHotkeys));
|
|
|
|
|
|
|
|
|
|
// Write the spell hotkeys
|
|
|
|
|
for (auto &spellId : myPlayer._pSplHotKey) {
|
|
|
|
|
file.WriteLE<int32_t>(spellId);
|
|
|
|
|
}
|
|
|
|
|
for (auto &spellType : myPlayer._pSplTHotKey) {
|
|
|
|
|
file.WriteLE<uint8_t>(spellType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write the selected spell last
|
|
|
|
|
file.WriteLE<int32_t>(myPlayer._pRSpell);
|
|
|
|
|
file.WriteLE<uint8_t>(myPlayer._pRSplType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadHeroItems(Player &player)
|
|
|
|
|
{
|
|
|
|
|
LoadHelper file(OpenSaveArchive(gSaveNumber), "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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr uint8_t StashVersion = 0;
|
|
|
|
|
|
|
|
|
|
void LoadStash()
|
|
|
|
|
{
|
|
|
|
|
const char *filename;
|
|
|
|
|
if (!gbIsMultiplayer)
|
|
|
|
|
filename = "spstashitems";
|
|
|
|
|
else
|
|
|
|
|
filename = "mpstashitems";
|
|
|
|
|
|
|
|
|
|
Stash = {};
|
|
|
|
|
|
|
|
|
|
LoadHelper file(OpenStashArchive(), filename);
|
|
|
|
|
if (!file.IsValid())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto version = file.NextLE<uint8_t>();
|
|
|
|
|
if (version > StashVersion)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Stash.gold = file.NextLE<uint32_t>();
|
|
|
|
|
|
|
|
|
|
auto pages = file.NextLE<uint32_t>();
|
|
|
|
|
for (unsigned i = 0; i < pages; i++) {
|
|
|
|
|
auto page = file.NextLE<uint32_t>();
|
|
|
|
|
for (auto &row : Stash.stashGrids[page]) {
|
|
|
|
|
for (uint16_t &cell : row) {
|
|
|
|
|
cell = file.NextLE<uint16_t>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto itemCount = file.NextLE<uint32_t>();
|
|
|
|
|
Stash.stashList.resize(itemCount);
|
|
|
|
|
for (unsigned i = 0; i < itemCount; i++) {
|
|
|
|
|
LoadItemData(file, Stash.stashList[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Stash.SetPage(file.NextLE<uint32_t>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RemoveEmptyInventory(Player &player)
|
|
|
|
|
{
|
|
|
|
|
for (int i = NUM_INV_GRID_ELEM; i > 0; i--) {
|
|
|
|
|
int8_t idx = player.InvGrid[i - 1];
|
|
|
|
|
if (idx > 0 && player.InvList[idx - 1].isEmpty()) {
|
|
|
|
|
player.RemoveInvItem(idx - 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadGame(bool firstflag)
|
|
|
|
|
{
|
|
|
|
|
FreeGameMem();
|
|
|
|
|
|
|
|
|
|
LoadHelper file(OpenSaveArchive(gSaveNumber), "game");
|
|
|
|
|
if (!file.IsValid())
|
|
|
|
|
app_fatal("%s", _("Unable to open save file archive").c_str());
|
|
|
|
|
|
|
|
|
|
if (!IsHeaderValid(file.NextLE<uint32_t>()))
|
|
|
|
|
app_fatal("%s", _("Invalid save file").c_str());
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pfile_remove_temp_files();
|
|
|
|
|
|
|
|
|
|
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>();
|
|
|
|
|
auto savedItemCount = file.NextBE<uint32_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").c_str());
|
|
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < giNumberOfLevels; i++) {
|
|
|
|
|
glSeedTbl[i] = file.NextBE<uint32_t>();
|
|
|
|
|
file.Skip(4); // Skip loading gnLevelTypeTbl
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto &myPlayer = Players[MyPlayerId];
|
|
|
|
|
|
|
|
|
|
LoadPlayer(file, myPlayer);
|
|
|
|
|
|
|
|
|
|
sgGameInitInfo.nDifficulty = myPlayer.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(myPlayer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LoadGameLevel(firstflag, ENTRY_LOAD);
|
|
|
|
|
SyncInitPlr(MyPlayerId);
|
|
|
|
|
SyncPlrAnim(MyPlayerId);
|
|
|
|
|
|
|
|
|
|
ViewPosition = { viewX, viewY };
|
|
|
|
|
ActiveMonsterCount = tmpNummonsters;
|
|
|
|
|
ActiveObjectCount = tmpNobjects;
|
|
|
|
|
|
|
|
|
|
for (int &monstkill : MonsterKillCounts)
|
|
|
|
|
monstkill = file.NextBE<int32_t>();
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
for (int &monsterId : ActiveMonsters)
|
|
|
|
|
monsterId = file.NextBE<int32_t>();
|
|
|
|
|
for (int i = 0; i < ActiveMonsterCount; i++)
|
|
|
|
|
LoadMonster(&file, Monsters[ActiveMonsters[i]]);
|
|
|
|
|
for (int i = 0; i < ActiveMonsterCount; i++)
|
|
|
|
|
SyncPackSize(Monsters[ActiveMonsters[i]]);
|
|
|
|
|
// Skip ActiveMissiles
|
|
|
|
|
file.Skip<int8_t>(MaxMissilesForSaveGame);
|
|
|
|
|
// Skip AvailableMissiles
|
|
|
|
|
file.Skip<int8_t>(MaxMissilesForSaveGame);
|
|
|
|
|
for (int i = 0; i < tmpNummissiles; i++)
|
|
|
|
|
LoadMissile(&file);
|
|
|
|
|
for (int &objectId : ActiveObjects)
|
|
|
|
|
objectId = file.NextLE<int8_t>();
|
|
|
|
|
for (int &objectId : AvailableObjects)
|
|
|
|
|
objectId = file.NextLE<int8_t>();
|
|
|
|
|
for (int i = 0; i < ActiveObjectCount; i++)
|
|
|
|
|
LoadObject(file, Objects[ActiveObjects[i]]);
|
|
|
|
|
for (int i = 0; i < ActiveObjectCount; i++)
|
|
|
|
|
SyncObjectAnim(Objects[ActiveObjects[i]]);
|
|
|
|
|
|
|
|
|
|
ActiveLightCount = file.NextBE<int32_t>();
|
|
|
|
|
|
|
|
|
|
for (uint8_t &lightId : ActiveLights)
|
|
|
|
|
lightId = file.NextLE<uint8_t>();
|
|
|
|
|
for (int i = 0; i < ActiveLightCount; i++)
|
|
|
|
|
LoadLighting(&file, &Lights[ActiveLights[i]]);
|
|
|
|
|
|
|
|
|
|
VisionId = file.NextBE<int32_t>();
|
|
|
|
|
VisionCount = file.NextBE<int32_t>();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < VisionCount; i++)
|
|
|
|
|
LoadLighting(&file, &VisionList[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LoadDroppedItems(file, savedItemCount);
|
|
|
|
|
|
|
|
|
|
LoadAdditionalMissiles();
|
|
|
|
|
|
|
|
|
|
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] = static_cast<DungeonFlag>(file.NextLE<uint8_t>()) & DungeonFlag::LoadedFlags;
|
|
|
|
|
}
|
|
|
|
|
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>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// skip dItem indexes, this gets populated in LoadDroppedItems
|
|
|
|
|
file.Skip<uint8_t>(MAXDUNX * MAXDUNY);
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
dCorpse[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.NextLE<uint8_t>();
|
|
|
|
|
}
|
|
|
|
|
file.Skip(MAXDUNX * MAXDUNY); // dMissile
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
numpremium = file.NextBE<int32_t>();
|
|
|
|
|
premiumlevel = file.NextBE<int32_t>();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < giNumberOfSmithPremiumItems; i++)
|
|
|
|
|
LoadPremium(file, i);
|
|
|
|
|
if (gbIsHellfire && !gbIsHellfireSaveGame)
|
|
|
|
|
SpawnPremium(MyPlayerId);
|
|
|
|
|
|
|
|
|
|
AutomapActive = file.NextBool8();
|
|
|
|
|
AutoMapScale = file.NextBE<int32_t>();
|
|
|
|
|
AutomapZoomReset();
|
|
|
|
|
ResyncQuests();
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN)
|
|
|
|
|
ProcessLightList();
|
|
|
|
|
|
|
|
|
|
RedoPlayerVision();
|
|
|
|
|
ProcessVisionList();
|
|
|
|
|
// convert stray manashield missiles into pManaShield flag
|
|
|
|
|
for (auto &missile : Missiles) {
|
|
|
|
|
if (missile._mitype == MIS_MANASHIELD && !missile._miDelFlag) {
|
|
|
|
|
Players[missile._misource].pManaShield = true;
|
|
|
|
|
missile._miDelFlag = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
missiles_process_charge();
|
|
|
|
|
RedoMissileFlags();
|
|
|
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
|
gbProcessPlayers = true;
|
|
|
|
|
|
|
|
|
|
if (gbIsHellfireSaveGame != gbIsHellfire) {
|
|
|
|
|
SaveGame();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gbIsHellfireSaveGame = gbIsHellfire;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SaveHeroItems(Player &player)
|
|
|
|
|
{
|
|
|
|
|
size_t itemCount = NUM_INVLOC + NUM_INV_GRID_ELEM + MAXBELTITEMS;
|
|
|
|
|
SaveHelper file(CurrentSaveArchive(), "heroitems", itemCount * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t));
|
|
|
|
|
|
|
|
|
|
file.WriteLE<uint8_t>(gbIsHellfire ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
for (const Item &item : player.InvBody)
|
|
|
|
|
SaveItem(file, item);
|
|
|
|
|
for (const Item &item : player.InvList)
|
|
|
|
|
SaveItem(file, item);
|
|
|
|
|
for (const Item &item : player.SpdList)
|
|
|
|
|
SaveItem(file, item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SaveStash()
|
|
|
|
|
{
|
|
|
|
|
const char *filename;
|
|
|
|
|
if (!gbIsMultiplayer)
|
|
|
|
|
filename = "spstashitems";
|
|
|
|
|
else
|
|
|
|
|
filename = "mpstashitems";
|
|
|
|
|
|
|
|
|
|
const int itemSize = (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize);
|
|
|
|
|
|
|
|
|
|
SaveHelper file(
|
|
|
|
|
StashArchive(),
|
|
|
|
|
filename,
|
|
|
|
|
sizeof(uint8_t)
|
|
|
|
|
+ sizeof(uint32_t)
|
|
|
|
|
+ sizeof(uint32_t)
|
|
|
|
|
+ (sizeof(uint32_t) + 10 * 10 * sizeof(uint16_t)) * Stash.stashGrids.size()
|
|
|
|
|
+ sizeof(uint32_t)
|
|
|
|
|
+ itemSize * Stash.stashList.size()
|
|
|
|
|
+ sizeof(uint32_t));
|
|
|
|
|
|
|
|
|
|
file.WriteLE<uint8_t>(StashVersion);
|
|
|
|
|
|
|
|
|
|
file.WriteLE<uint32_t>(Stash.gold);
|
|
|
|
|
|
|
|
|
|
std::vector<unsigned> pagesToSave;
|
|
|
|
|
for (const auto &stashPage : Stash.stashGrids) {
|
|
|
|
|
if (std::any_of(stashPage.second.cbegin(), stashPage.second.cend(), [](const auto &row) {
|
|
|
|
|
return std::any_of(row.cbegin(), row.cend(), [](auto cell) {
|
|
|
|
|
return cell > 0;
|
|
|
|
|
});
|
|
|
|
|
})) {
|
|
|
|
|
// found a page that contains at least one item
|
|
|
|
|
pagesToSave.push_back(stashPage.first);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Current stash size is 100 pages. Will definitely fit in a 32 bit value.
|
|
|
|
|
file.WriteLE<uint32_t>(static_cast<uint32_t>(pagesToSave.size()));
|
|
|
|
|
for (const auto &page : pagesToSave) {
|
|
|
|
|
file.WriteLE<uint32_t>(page);
|
|
|
|
|
for (const auto &row : Stash.stashGrids[page]) {
|
|
|
|
|
for (uint16_t cell : row) {
|
|
|
|
|
file.WriteLE<uint16_t>(cell);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 100 pages of 100 items is still only 10 000, as with the page count will definitely fit in 32 bits even in the worst case.
|
|
|
|
|
file.WriteLE<uint32_t>(static_cast<uint32_t>(Stash.stashList.size()));
|
|
|
|
|
for (const Item &item : Stash.stashList) {
|
|
|
|
|
SaveItem(file, item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file.WriteLE<uint32_t>(static_cast<uint32_t>(Stash.GetPage()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SaveGameData()
|
|
|
|
|
{
|
|
|
|
|
SaveHelper file(CurrentSaveArchive(), "game", 320 * 1024);
|
|
|
|
|
|
|
|
|
|
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").c_str());
|
|
|
|
|
|
|
|
|
|
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>(ViewPosition.x);
|
|
|
|
|
file.WriteBE<int32_t>(ViewPosition.y);
|
|
|
|
|
file.WriteLE<uint8_t>(invflag ? 1 : 0);
|
|
|
|
|
file.WriteLE<uint8_t>(chrflag ? 1 : 0);
|
|
|
|
|
file.WriteBE<int32_t>(ActiveMonsterCount);
|
|
|
|
|
file.WriteBE<int32_t>(ActiveItemCount);
|
|
|
|
|
// ActiveMissileCount will be a value from 0-125 (for vanilla compatibility). Writing an unsigned value here to avoid
|
|
|
|
|
// warnings about casting from unsigned to signed, but there's no sign extension issues when reading this as a signed
|
|
|
|
|
// value later so it doesn't have to match in LoadGameData().
|
|
|
|
|
file.WriteBE<uint32_t>(static_cast<uint32_t>(std::min(Missiles.size(), MaxMissilesForSaveGame)));
|
|
|
|
|
file.WriteBE<int32_t>(ActiveObjectCount);
|
|
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < giNumberOfLevels; i++) {
|
|
|
|
|
file.WriteBE<uint32_t>(glSeedTbl[i]);
|
|
|
|
|
file.WriteBE<int32_t>(gnLevelTypeTbl[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto &myPlayer = Players[MyPlayerId];
|
|
|
|
|
myPlayer.pDifficulty = sgGameInitInfo.nDifficulty;
|
|
|
|
|
SavePlayer(file, myPlayer);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < giNumberQuests; i++)
|
|
|
|
|
SaveQuest(&file, i);
|
|
|
|
|
for (int i = 0; i < MAXPORTAL; i++)
|
|
|
|
|
SavePortal(&file, i);
|
|
|
|
|
for (int monstkill : MonsterKillCounts)
|
|
|
|
|
file.WriteBE<int32_t>(monstkill);
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
for (int monsterId : ActiveMonsters)
|
|
|
|
|
file.WriteBE<int32_t>(monsterId);
|
|
|
|
|
for (int i = 0; i < ActiveMonsterCount; i++)
|
|
|
|
|
SaveMonster(&file, Monsters[ActiveMonsters[i]]);
|
|
|
|
|
// Write ActiveMissiles
|
|
|
|
|
for (uint8_t activeMissile = 0; activeMissile < MaxMissilesForSaveGame; activeMissile++)
|
|
|
|
|
file.WriteLE<uint8_t>(activeMissile);
|
|
|
|
|
// Write AvailableMissiles
|
|
|
|
|
for (size_t availableMissiles = Missiles.size(); availableMissiles < MaxMissilesForSaveGame; availableMissiles++)
|
|
|
|
|
file.WriteLE(static_cast<uint8_t>(availableMissiles));
|
|
|
|
|
const size_t savedMissiles = std::min(Missiles.size(), MaxMissilesForSaveGame);
|
|
|
|
|
file.Skip<uint8_t>(savedMissiles);
|
|
|
|
|
// Write Missile Data
|
|
|
|
|
{
|
|
|
|
|
auto missilesEnd = Missiles.cbegin();
|
|
|
|
|
std::advance(missilesEnd, savedMissiles);
|
|
|
|
|
for (auto it = Missiles.cbegin(); it != missilesEnd; it++) {
|
|
|
|
|
SaveMissile(&file, *it);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (int objectId : ActiveObjects)
|
|
|
|
|
file.WriteLE(static_cast<int8_t>(objectId));
|
|
|
|
|
for (int objectId : AvailableObjects)
|
|
|
|
|
file.WriteLE(static_cast<int8_t>(objectId));
|
|
|
|
|
for (int i = 0; i < ActiveObjectCount; i++)
|
|
|
|
|
SaveObject(file, Objects[ActiveObjects[i]]);
|
|
|
|
|
|
|
|
|
|
file.WriteBE<int32_t>(ActiveLightCount);
|
|
|
|
|
|
|
|
|
|
for (uint8_t lightId : ActiveLights)
|
|
|
|
|
file.WriteLE<uint8_t>(lightId);
|
|
|
|
|
for (int i = 0; i < ActiveLightCount; i++)
|
|
|
|
|
SaveLighting(&file, &Lights[ActiveLights[i]]);
|
|
|
|
|
|
|
|
|
|
file.WriteBE<int32_t>(VisionId);
|
|
|
|
|
file.WriteBE<int32_t>(VisionCount);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < VisionCount; i++)
|
|
|
|
|
SaveLighting(&file, &VisionList[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto itemIndexes = SaveDroppedItems(file);
|
|
|
|
|
|
|
|
|
|
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<uint8_t>(static_cast<uint8_t>(dFlags[i][j] & DungeonFlag::SavedFlags));
|
|
|
|
|
}
|
|
|
|
|
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]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SaveDroppedItemLocations(file, itemIndexes);
|
|
|
|
|
|
|
|
|
|
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>(dCorpse[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]);
|
|
|
|
|
}
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.WriteLE<int8_t>(TileContainsMissile({ i, j }) ? -1 : 0); // For backwards compatability
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file.WriteBE<int32_t>(numpremium);
|
|
|
|
|
file.WriteBE<int32_t>(premiumlevel);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < giNumberOfSmithPremiumItems; i++)
|
|
|
|
|
SaveItem(file, premiumitems[i]);
|
|
|
|
|
|
|
|
|
|
file.WriteLE<uint8_t>(AutomapActive ? 1 : 0);
|
|
|
|
|
file.WriteBE<int32_t>(AutoMapScale);
|
|
|
|
|
|
|
|
|
|
SaveAdditionalMissiles();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SaveGame()
|
|
|
|
|
{
|
|
|
|
|
gbValidSaveFile = true;
|
|
|
|
|
pfile_write_hero(/*writeGameData=*/true);
|
|
|
|
|
sfile_write_stash();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SaveLevel()
|
|
|
|
|
{
|
|
|
|
|
PFileScopedArchiveWriter scopedWriter;
|
|
|
|
|
|
|
|
|
|
auto &myPlayer = Players[MyPlayerId];
|
|
|
|
|
|
|
|
|
|
DoUnVision(myPlayer.position.tile, myPlayer._pLightRad); // fix for vision staying on the level
|
|
|
|
|
|
|
|
|
|
if (currlevel == 0)
|
|
|
|
|
glSeedTbl[0] = AdvanceRndSeed();
|
|
|
|
|
|
|
|
|
|
char szName[MAX_PATH];
|
|
|
|
|
GetTempLevelNames(szName);
|
|
|
|
|
SaveHelper file(CurrentSaveArchive(), szName, 256 * 1024);
|
|
|
|
|
|
|
|
|
|
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>(dCorpse[i][j]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file.WriteBE<int32_t>(ActiveMonsterCount);
|
|
|
|
|
file.WriteBE<int32_t>(ActiveItemCount);
|
|
|
|
|
file.WriteBE<int32_t>(ActiveObjectCount);
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
for (int monsterId : ActiveMonsters)
|
|
|
|
|
file.WriteBE<int32_t>(monsterId);
|
|
|
|
|
for (int i = 0; i < ActiveMonsterCount; i++)
|
|
|
|
|
SaveMonster(&file, Monsters[ActiveMonsters[i]]);
|
|
|
|
|
for (int objectId : ActiveObjects)
|
|
|
|
|
file.WriteLE<int8_t>(objectId);
|
|
|
|
|
for (int objectId : AvailableObjects)
|
|
|
|
|
file.WriteLE<int8_t>(objectId);
|
|
|
|
|
for (int i = 0; i < ActiveObjectCount; i++)
|
|
|
|
|
SaveObject(file, Objects[ActiveObjects[i]]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto itemIndexes = SaveDroppedItems(file);
|
|
|
|
|
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
file.WriteLE<uint8_t>(static_cast<uint8_t>(dFlags[i][j] & DungeonFlag::SavedFlags));
|
|
|
|
|
}
|
|
|
|
|
SaveDroppedItemLocations(file, itemIndexes);
|
|
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!setlevel)
|
|
|
|
|
myPlayer._pLvlVisited[currlevel] = true;
|
|
|
|
|
else
|
|
|
|
|
myPlayer._pSLvlVisited[setlvlnum] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadLevel()
|
|
|
|
|
{
|
|
|
|
|
char szName[MAX_PATH];
|
|
|
|
|
GetPermLevelNames(szName);
|
|
|
|
|
LoadHelper file(OpenSaveArchive(gSaveNumber), szName);
|
|
|
|
|
if (!file.IsValid())
|
|
|
|
|
app_fatal("%s", _("Unable to open save file archive").c_str());
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dCorpse[i][j] = file.NextLE<int8_t>();
|
|
|
|
|
}
|
|
|
|
|
SyncUniqDead();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ActiveMonsterCount = file.NextBE<int32_t>();
|
|
|
|
|
auto savedItemCount = file.NextBE<uint32_t>();
|
|
|
|
|
ActiveObjectCount = file.NextBE<int32_t>();
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
for (int &monsterId : ActiveMonsters)
|
|
|
|
|
monsterId = file.NextBE<int32_t>();
|
|
|
|
|
for (int i = 0; i < ActiveMonsterCount; i++)
|
|
|
|
|
LoadMonster(&file, Monsters[ActiveMonsters[i]]);
|
|
|
|
|
for (int &objectId : ActiveObjects)
|
|
|
|
|
objectId = file.NextLE<int8_t>();
|
|
|
|
|
for (int &objectId : AvailableObjects)
|
|
|
|
|
objectId = file.NextLE<int8_t>();
|
|
|
|
|
for (int i = 0; i < ActiveObjectCount; i++)
|
|
|
|
|
LoadObject(file, Objects[ActiveObjects[i]]);
|
|
|
|
|
if (!gbSkipSync) {
|
|
|
|
|
for (int i = 0; i < ActiveObjectCount; i++)
|
|
|
|
|
SyncObjectAnim(Objects[ActiveObjects[i]]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LoadDroppedItems(file, savedItemCount);
|
|
|
|
|
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert)
|
|
|
|
|
dFlags[i][j] = static_cast<DungeonFlag>(file.NextLE<uint8_t>()) & DungeonFlag::LoadedFlags;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// skip dItem indexes, this gets populated in LoadDroppedItems
|
|
|
|
|
file.Skip<uint8_t>(MAXDUNX * MAXDUNY);
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
const auto automapView = static_cast<MapExplorationType>(file.NextLE<uint8_t>());
|
|
|
|
|
AutomapView[i][j] = automapView == MAP_EXP_OLD ? MAP_EXP_SELF : automapView;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!gbSkipSync) {
|
|
|
|
|
AutomapZoomReset();
|
|
|
|
|
ResyncQuests();
|
|
|
|
|
RedoMissileFlags();
|
|
|
|
|
UpdateLighting = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto &player : Players) {
|
|
|
|
|
if (player.plractive && currlevel == player.plrlevel)
|
|
|
|
|
Lights[player._plid]._lunflag = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace devilution
|