scummvm/engines/saga2/saveload.cpp
2021-12-26 18:48:43 +01:00

586 lines
14 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* aint32 with this program; if not, write to the Free Software
*
*
* Based on the original sources
* Faery Tale II -- The Halls of the Dead
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
*/
#include "saga2/saga2.h"
#include "saga2/saveload.h"
#include "saga2/objects.h"
#include "saga2/tile.h"
#include "saga2/script.h"
#include "saga2/motion.h"
#include "saga2/task.h"
#include "saga2/speech.h"
#include "saga2/timers.h"
#include "saga2/sensor.h"
#include "saga2/band.h"
#include "saga2/mission.h"
#include "saga2/tilemode.h"
#include "saga2/magic.h"
#include "saga2/intrface.h"
#include "saga2/vpal.h"
#include "saga2/palette.h"
#include "saga2/contain.h"
#include "saga2/imagcach.h"
namespace Saga2 {
void updateMainDisplay();
void fadeUp();
void fadeDown();
void enablePaletteChanges();
const ChunkID gameID = MKTAG('F', 'T', 'A', '2');
void SaveFileHeader::read(Common::InSaveFile *in) {
char fileName[SaveFileHeader::kSaveNameSize];
gameID = in->readUint32BE();;
in->read(fileName, SaveFileHeader::kSaveNameSize);
saveName = fileName;
}
void SaveFileHeader::write(Common::OutSaveFile *out) {
out->writeUint32BE(gameID);
out->write(saveName.c_str(), saveName.size());
int remainingBytes = SaveFileHeader::kHeaderSize - saveName.size() - 4;
for (int i = 0; i < remainingBytes; ++i)
out->writeByte(0);
debugC(1, kDebugSaveload, "Writing game header: gameID = %s, saveName = %s", tag2str(gameID), saveName.c_str());
}
/* ===================================================================== *
Functions
* ===================================================================== */
// The following resources are included in the save file
// GLOB - miscellaneous globals
// TIME - game timer
// CALE - game calender
// WRLD - worlds
// ACTR - actors
// OBJS - objects
// BAND - actor bands
// PLYR - player actors
// CNTR - center actor ID and view object ID
// TAGS - active item instance state arrays
// CYCL - tile cycling states
// SDTA - SAGA data segment
// SAGA - SAGA threads
// MOTN - motion tasks
// TSTK - actor task stacks
// TASK - actor tasks
// TACT - tile activity tasks
// SPCH - speech tasks
// AREG - active regions
// TIMR - actor timers
// SENS - actor sensors
// ACNT - temporary actor count array
// MISS - missions
// FACT - faction tallies
// TMST - TileMode state
// SPEL - Active Spell List
// AMAP - auto map data
// UIST - user interface state
// PALE - palette state
// CONT - container nodes
//----------------------------------------------------------------------
// Load initial game state
void initGameState() {
pauseTimer();
initGlobals();
initCalender();
initWorlds();
initActors();
initObjects();
initBands();
initPlayerActors();
initCenterActor();
initActiveItemStates();
initTileCyclingStates();
initSAGADataSeg();
initSAGAThreads();
initMotionTasks();
initTaskStacks();
initTasks();
initTileTasks();
initSpeechTasks();
initActiveRegions();
initSensors();
initTempActorCount();
initMissions();
initFactionTallies();
initTileModeState();
initSpellState();
initAutoMap();
initUIState();
initPaletteState();
initContainerNodes();
resumeTimer();
}
//----------------------------------------------------------------------
// Save the current game state
void saveGame(Common::OutSaveFile *out, Common::String saveName) {
debugC(1, kDebugSaveload, "Saving game");
SaveFileHeader header;
header.gameID = gameID;
header.saveName = saveName;
header.write(out);
saveGlobals(out);
saveTimer(out);
saveCalender(out);
saveWorlds(out);
saveActors(out);
saveObjects(out);
saveBands(out);
savePlayerActors(out);
saveCenterActor(out);
saveActiveItemStates(out);
saveTileCyclingStates(out);
saveSAGADataSeg(out);
saveSAGAThreads(out);
saveMotionTasks(out);
saveTaskStacks(out);
saveTasks(out);
saveTileTasks(out);
saveSpeechTasks(out);
saveActiveRegions(out);
saveTimers(out);
saveSensors(out);
saveTempActorCount(out);
saveMissions(out);
saveFactionTallies(out);
saveTileModeState(out);
saveSpellState(out);
saveAutoMap(out);
saveUIState(out);
savePaletteState(out);
saveContainerNodes(out);
}
bool firstChunk(Common::InSaveFile *in, ChunkID &chunk, int32 &size) {
if (!in->seek(SaveFileHeader::kHeaderSize, SEEK_SET))
error("Error seeking first save game chunk");
if (in->size() - in->pos() < 8) {
debugC(1, kDebugSaveload, "Reached EOF on first Chunk %s", tag2str(chunk));
return false;
}
chunk = in->readUint32BE();
size = in->readUint32LE();
debugC(1, kDebugSaveload, "First Chunk loaded: chunkID = %s, size = %d", tag2str(chunk), size);
return true;
}
bool nextChunk(Common::InSaveFile *in, ChunkID &chunk, int32 &size) {
if (in->size() - in->pos() < 8) {
debugC(1, kDebugSaveload, "Reached EOF at %s", tag2str(chunk));
return false;
}
chunk = in->readUint32BE();
size = in->readUint32LE();
debugC(1, kDebugSaveload, "Next Chunk loaded: chunkID = %s, size = %d", tag2str(chunk), size);
// Return success
return true;
}
//----------------------------------------------------------------------
// Load a previously saved game state
void loadSavedGameState(int16 saveNo) {
enum {
loadGlobalsFlag = (1 << 0),
loadTimerFlag = (1 << 1),
loadCalenderFlag = (1 << 2),
loadWorldsFlag = (1 << 3),
loadActorsFlag = (1 << 4),
loadObjectsFlag = (1 << 5),
loadBandsFlag = (1 << 6),
loadPlayerActorsFlag = (1 << 7),
loadCenterActorFlag = (1 << 8),
loadActiveItemStatesFlag = (1 << 9),
loadTileCyclingStatesFlag = (1 << 10),
loadSAGADataSegFlag = (1 << 11),
loadSAGAThreadsFlag = (1 << 12),
loadMotionTasksFlag = (1 << 13),
loadTaskStacksFlag = (1 << 14),
loadTasksFlag = (1 << 15),
loadTileTasksFlag = (1 << 16),
loadSpeechTasksFlag = (1 << 17),
loadActiveRegionsFlag = (1 << 18),
loadTimersFlag = (1 << 19),
loadSensorsFlag = (1 << 20),
loadTempActorCountFlag = (1 << 21),
loadMissionsFlag = (1 << 22),
loadFactionTalliesFlag = (1 << 23),
loadTileModeStateFlag = (1 << 24),
loadSpellStateFlag = (1 << 25),
loadAutoMapFlag = (1 << 26),
loadUIStateFlag = (1 << 27),
loadPaletteStateFlag = (1 << 28),
loadContainerNodesFlag = (1 << 29)
};
uint32 loadFlags = 0;
pauseTimer();
Common::InSaveFile *in = g_vm->getSaveFileManager()->openForLoading(g_vm->getSavegameFile(saveNo));
ChunkID id;
int32 chunkSize;
bool notEOF;
notEOF = firstChunk(in, id, chunkSize);
while (notEOF) {
switch (id) {
case MKTAG('G', 'L', 'O', 'B'):
loadGlobals(in);
loadFlags |= loadGlobalsFlag;
break;
case MKTAG('T', 'I', 'M', 'E'):
loadTimer(in);
loadFlags |= loadTimerFlag;
break;
case MKTAG('C', 'A', 'L', 'E'):
loadCalender(in);
loadFlags |= loadCalenderFlag;
break;
case MKTAG('W', 'R', 'L', 'D'):
loadWorlds(in);
loadFlags |= loadWorldsFlag;
break;
case MKTAG('A', 'C', 'T', 'R'):
loadActors(in);
loadFlags |= loadActorsFlag;
break;
case MKTAG('O', 'B', 'J', 'S'):
loadObjects(in);
loadFlags |= loadObjectsFlag;
break;
case MKTAG('B', 'A', 'N', 'D'):
if (loadFlags & loadActorsFlag) {
loadBands(in, chunkSize);
loadFlags |= loadBandsFlag;
} else
error("Bands loaded prematurely");
break;
case MKTAG('P', 'L', 'Y', 'R'):
if (loadFlags & loadBandsFlag) {
loadPlayerActors(in);
loadFlags |= loadPlayerActorsFlag;
} else
error("PlayerActors loaded prematurely");
break;
case MKTAG('C', 'N', 'T', 'R'):
loadCenterActor(in);
loadFlags |= loadCenterActorFlag;
break;
case MKTAG('T', 'A', 'G', 'S'):
loadActiveItemStates(in);
loadFlags |= loadActiveItemStatesFlag;
break;
case MKTAG('C', 'Y', 'C', 'L'):
loadTileCyclingStates(in);
loadFlags |= loadTileCyclingStatesFlag;
break;
case MKTAG('S', 'D', 'T', 'A'):
loadSAGADataSeg(in);
loadFlags |= loadSAGADataSegFlag;
break;
case MKTAG('S', 'A', 'G', 'A'):
loadSAGAThreads(in, chunkSize);
loadFlags |= loadSAGAThreadsFlag;
break;
case MKTAG('M', 'O', 'T', 'N'):
if (!(~loadFlags & (loadActorsFlag | loadObjectsFlag))) {
loadMotionTasks(in, chunkSize);
loadFlags |= loadMotionTasksFlag;
} else
error("MotionTasks loaded prematurely");
break;
case MKTAG('T', 'S', 'T', 'K'):
if (loadFlags & loadActorsFlag) {
loadTaskStacks(in, chunkSize);
loadFlags |= loadTaskStacksFlag;
} else
error("TaskStacks loaded prematurely");
break;
case MKTAG('T', 'A', 'S', 'K'):
if (loadFlags & loadTaskStacksFlag) {
loadTasks(in, chunkSize);
loadFlags |= loadTasksFlag;
} else
error("Tasks loaded prematurely");
break;
case MKTAG('T', 'A', 'C', 'T'):
if (loadFlags & loadWorldsFlag) {
loadTileTasks(in, chunkSize);
loadFlags |= loadTileTasksFlag;
} else
error("TileActivityTasks loaded prematurely");
break;
case MKTAG('S', 'P', 'C', 'H'):
if (!(~loadFlags & (loadActorsFlag | loadObjectsFlag))) {
loadSpeechTasks(in, chunkSize);
loadFlags |= loadSpeechTasksFlag;
} else
error("SpeechTasks loaded prematurely");
break;
case MKTAG('A', 'R', 'E', 'G'):
loadActiveRegions(in);
loadFlags |= loadActiveRegionsFlag;
break;
case MKTAG('T', 'I', 'M', 'R'):
if (loadFlags & loadActorsFlag) {
loadTimers(in);
loadFlags |= loadTimersFlag;
} else
error("Timers loaded prematurely");
break;
case MKTAG('S', 'E', 'N', 'S'):
if (loadFlags & loadActorsFlag) {
loadSensors(in);
loadFlags |= loadSensorsFlag;
} else
error("Sensors loaded prematurely");
break;
case MKTAG('A', 'C', 'N', 'T'):
loadTempActorCount(in, chunkSize);
loadFlags |= loadTempActorCountFlag;
break;
case MKTAG('M', 'I', 'S', 'S'):
loadMissions(in);
loadFlags |= loadMissionsFlag;
break;
case MKTAG('F', 'A', 'C', 'T'):
loadFactionTallies(in);
loadFlags |= loadFactionTalliesFlag;
break;
case MKTAG('T', 'M', 'S', 'T'):
if (loadFlags & loadActorsFlag) {
loadTileModeState(in);
loadFlags |= loadTileModeStateFlag;
} else
error("TileMode state loaded prematurely");
break;
case MKTAG('S', 'P', 'E', 'L'):
loadSpellState(in);
loadFlags |= loadSpellStateFlag;
break;
case MKTAG('A', 'M', 'A', 'P'):
if (loadFlags & loadWorldsFlag) {
loadAutoMap(in, chunkSize);
loadFlags |= loadAutoMapFlag;
} else
error("Auto map loaded prematurely");
break;
case MKTAG('U', 'I', 'S', 'T'):
if (loadFlags & loadPlayerActorsFlag) {
loadUIState(in);
loadFlags |= loadUIStateFlag;
} else
error("UI state loaded prematurely");
break;
case MKTAG('P', 'A', 'L', 'E'):
loadPaletteState(in);
loadFlags |= loadPaletteStateFlag;
break;
case MKTAG('C', 'O', 'N', 'T'):
if (loadFlags & loadObjectsFlag) {
loadContainerNodes(in);
loadFlags |= loadContainerNodesFlag;
} else
error("ContainerNodes loaded prematurely");
break;
}
if (loadFlags & loadContainerNodesFlag)
break;
notEOF = nextChunk(in, id, chunkSize);
}
if (!(loadFlags & loadGlobalsFlag))
error("Globals not loaded");
if (!(loadFlags & loadTimerFlag))
error("Timer not loaded");
if (!(loadFlags & loadCalenderFlag))
error("Game calender not loaded");
if (!(loadFlags & loadWorldsFlag))
error("Worlds not loaded");
if (!(loadFlags & loadObjectsFlag))
error("Objects not loaded");
if (!(loadFlags & loadActorsFlag))
error("Actors not loaded");
if (!(loadFlags & loadPlayerActorsFlag))
error("Player actors not loaded");
if (!(loadFlags & loadCenterActorFlag))
error("Center actor not loaded");
if (!(loadFlags & loadActiveItemStatesFlag))
error("Active item states not loaded");
if (!(loadFlags & loadTileCyclingStatesFlag))
error("Tile cycling states not loaded");
if (!(loadFlags & loadSAGADataSegFlag))
error("SAGA data segment not loaded");
if (!(loadFlags & loadSAGAThreadsFlag))
error("SAGA threads not loaded");
if (!(loadFlags & loadActiveRegionsFlag))
error("Active Regions not loaded");
ExtendedSavegameHeader header;
if (MetaEngine::readSavegameHeader(in, &header)) {
g_vm->setTotalPlayTime(header.playtime * 1000);
}
delete in;
resumeTimer();
}
void loadGame(int16 saveNo) {
disableUserControls();
cleanupGameState();
fadeDown();
loadSavedGameState(saveNo);
if (GameMode::newmodeFlag)
GameMode::update();
updateActiveRegions();
enableUserControls();
updateMainDisplay();
drawMainDisplay();
enablePaletteChanges();
updateAllUserControls();
fadeUp();
reDrawScreen();
}
//----------------------------------------------------------------------
// Cleanup the game state
void cleanupGameState() {
cleanupContainerNodes();
cleanupPaletteState();
cleanupUIState();
cleanupAutoMap();
cleanupSpellState();
cleanupTileModeState();
cleanupFactionTallies();
cleanupMissions();
cleanupTempActorCount();
cleanupSensors();
cleanupTimers();
cleanupActiveRegions();
cleanupSpeechTasks();
cleanupTileTasks();
cleanupTasks();
cleanupTaskStacks();
cleanupMotionTasks();
cleanupSAGAThreads();
cleanupSAGADataSeg();
cleanupTileCyclingStates();
cleanupActiveItemStates();
cleanupCenterActor();
cleanupPlayerActors();
cleanupBands();
cleanupObjects();
cleanupActors();
cleanupWorlds();
cleanupGlobals();
}
//#define DEBUG_FILETIME
#ifdef DEBUG_FILETIME
#include <time.h>
#endif
void checkRestartGame(const char *exeName) {
Common::String saveRestart = g_vm->getSavegameFile(999);
g_vm->saveGameState(999, saveRestart, true);
}
void loadRestartGame() {
loadSavedGameState(999);
}
} // end of namespace Saga2