mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
651 lines
17 KiB
C++
651 lines
17 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
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "titanic/core/dont_save_file_item.h"
|
|
#include "titanic/core/node_item.h"
|
|
#include "titanic/core/project_item.h"
|
|
#include "titanic/core/view_item.h"
|
|
#include "titanic/events.h"
|
|
#include "titanic/game_manager.h"
|
|
#include "titanic/pet_control/pet_control.h"
|
|
#include "titanic/titanic.h"
|
|
#include "common/file.h"
|
|
#include "common/savefile.h"
|
|
#include "graphics/scaler.h"
|
|
#include "graphics/thumbnail.h"
|
|
|
|
namespace Titanic {
|
|
|
|
#define CURRENT_SAVEGAME_VERSION 1
|
|
#define MINIMUM_SAVEGAME_VERSION 1
|
|
|
|
static const char *const SAVEGAME_STR = "TNIC";
|
|
#define SAVEGAME_STR_SIZE 4
|
|
|
|
EMPTY_MESSAGE_MAP(CProjectItem, CFileItem);
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
void TitanicSavegameHeader::clear() {
|
|
_version = 0;
|
|
_saveName = "";
|
|
_thumbnail = nullptr;
|
|
_year = _month = _day = 0;
|
|
_hour = _minute = 0;
|
|
_totalFrames = 0;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
void CFileListItem::save(SimpleFile *file, int indent) {
|
|
file->writeNumberLine(0, indent);
|
|
file->writeQuotedLine(_name, indent);
|
|
|
|
ListItem::save(file, indent);
|
|
}
|
|
|
|
void CFileListItem::load(SimpleFile *file) {
|
|
file->readNumber();
|
|
_name = file->readString();
|
|
|
|
ListItem::load(file);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
CProjectItem::CProjectItem() : _nextRoomNumber(0), _nextMessageNumber(0),
|
|
_nextObjectNumber(0), _gameManager(nullptr) {
|
|
}
|
|
|
|
void CProjectItem::save(SimpleFile *file, int indent) {
|
|
file->writeNumberLine(6, indent);
|
|
file->writeQuotedLine("Next Avail. Object Number", indent);
|
|
file->writeNumberLine(_nextObjectNumber, indent);
|
|
file->writeQuotedLine("Next Avail. Message Number", indent);
|
|
file->writeNumberLine(_nextMessageNumber, indent);
|
|
|
|
file->writeQuotedLine("", indent);
|
|
_files.save(file, indent);
|
|
|
|
file->writeQuotedLine("Next Avail. Room Number", indent);
|
|
file->writeNumberLine(_nextRoomNumber, indent);
|
|
|
|
CTreeItem::save(file, indent);
|
|
}
|
|
|
|
void CProjectItem::buildFilesList() {
|
|
_files.destroyContents();
|
|
|
|
CTreeItem *treeItem = getFirstChild();
|
|
while (treeItem) {
|
|
if (treeItem->isFileItem()) {
|
|
CString name = static_cast<CFileItem *>(treeItem)->getFilename();
|
|
_files.add()->_name = name;
|
|
}
|
|
|
|
treeItem = getNextSibling();
|
|
}
|
|
}
|
|
|
|
void CProjectItem::load(SimpleFile *file) {
|
|
int val = file->readNumber();
|
|
_files.destroyContents();
|
|
int count;
|
|
|
|
switch (val) {
|
|
case 1:
|
|
file->readBuffer();
|
|
_nextRoomNumber = file->readNumber();
|
|
// Intentional fall-through
|
|
|
|
case 0:
|
|
// Load the list of files
|
|
count = file->readNumber();
|
|
for (int idx = 0; idx < count; ++idx) {
|
|
CString name = file->readString();
|
|
_files.add()->_name = name;
|
|
}
|
|
break;
|
|
|
|
case 6:
|
|
file->readBuffer();
|
|
_nextObjectNumber = file->readNumber();
|
|
// Intentional fall-through
|
|
|
|
case 5:
|
|
file->readBuffer();
|
|
_nextMessageNumber = file->readNumber();
|
|
// Intentional fall-through
|
|
|
|
case 4:
|
|
file->readBuffer();
|
|
// Intentional fall-through
|
|
|
|
case 2:
|
|
case 3:
|
|
_files.load(file);
|
|
file->readBuffer();
|
|
_nextRoomNumber = file->readNumber();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
CTreeItem::load(file);
|
|
}
|
|
|
|
CGameManager *CProjectItem::getGameManager() const {
|
|
return _gameManager;
|
|
}
|
|
|
|
void CProjectItem::setGameManager(CGameManager *gameManager) {
|
|
if (!_gameManager)
|
|
_gameManager = gameManager;
|
|
}
|
|
|
|
void CProjectItem::resetGameManager() {
|
|
_gameManager = nullptr;
|
|
}
|
|
|
|
void CProjectItem::loadGame(int slotId) {
|
|
CompressedFile file;
|
|
|
|
// Clear any existing project contents and call preload code
|
|
preLoad();
|
|
clear();
|
|
g_vm->_loadSaveSlot = -1;
|
|
|
|
// Open either an existing savegame slot or the new game template
|
|
if (slotId >= 0) {
|
|
Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(
|
|
g_vm->getSaveStateName(slotId));
|
|
file.open(saveFile);
|
|
} else {
|
|
Common::File *newFile = new Common::File();
|
|
if (!newFile->open("newgame.st"))
|
|
error("Could not open newgame.st");
|
|
file.open(newFile);
|
|
}
|
|
|
|
// Load the savegame header in
|
|
TitanicSavegameHeader header;
|
|
if (!readSavegameHeader(&file, header))
|
|
error("Failed to read save game header");
|
|
|
|
g_vm->_events->setTotalPlayTicks(header._totalFrames);
|
|
|
|
// Load the contents in
|
|
CProjectItem *newProject = loadData(&file);
|
|
file.isClassStart();
|
|
getGameManager()->load(&file);
|
|
|
|
file.close();
|
|
|
|
// Clear existing project
|
|
clear();
|
|
|
|
// Detach each item under the loaded project, and re-attach them
|
|
// to the existing project instance (this)
|
|
CTreeItem *item;
|
|
while ((item = newProject->getFirstChild()) != nullptr) {
|
|
item->detach();
|
|
item->addUnder(this);
|
|
}
|
|
|
|
// Loaded project instance is no longer needed
|
|
newProject->destroyAll();
|
|
|
|
// Post-load processing
|
|
postLoad();
|
|
}
|
|
|
|
void CProjectItem::saveGame(int slotId, const CString &desc) {
|
|
CompressedFile file;
|
|
Common::OutSaveFile *saveFile = g_system->getSavefileManager()->openForSaving(
|
|
g_vm->getSaveStateName(slotId), false);
|
|
file.open(saveFile);
|
|
|
|
// Signal the game is being saved
|
|
preSave();
|
|
|
|
// Write out the savegame header
|
|
TitanicSavegameHeader header;
|
|
header._saveName = desc;
|
|
writeSavegameHeader(&file, header);
|
|
|
|
// Save the contents out
|
|
saveData(&file, this);
|
|
|
|
// Save the game manager data
|
|
_gameManager->save(&file);
|
|
|
|
// Close the file and signal that the saving has finished
|
|
file.close();
|
|
postSave();
|
|
}
|
|
|
|
void CProjectItem::clear() {
|
|
CTreeItem *item;
|
|
while ((item = getFirstChild()) != nullptr)
|
|
item->destroyAll();
|
|
}
|
|
|
|
CProjectItem *CProjectItem::loadData(SimpleFile *file) {
|
|
if (!file->isClassStart())
|
|
return nullptr;
|
|
|
|
CProjectItem *root = nullptr;
|
|
CTreeItem *parent = nullptr;
|
|
CTreeItem *item = nullptr;
|
|
|
|
do {
|
|
CString entryString = file->readString();
|
|
|
|
if (entryString == "ALONG") {
|
|
// Move along, nothing needed
|
|
} else if (entryString == "UP") {
|
|
// Move up
|
|
if (parent == nullptr ||
|
|
(parent = parent->getParent()) == nullptr)
|
|
break;
|
|
} else if (entryString == "DOWN") {
|
|
// Move down
|
|
if (parent == nullptr)
|
|
parent = item;
|
|
else
|
|
parent = parent->getLastChild();
|
|
} else {
|
|
// Create new class instance
|
|
item = dynamic_cast<CTreeItem *>(CSaveableObject::createInstance(entryString));
|
|
assert(item);
|
|
|
|
if (root) {
|
|
// Already created root project
|
|
item->addUnder(parent);
|
|
} else {
|
|
root = dynamic_cast<CProjectItem *>(item);
|
|
assert(root);
|
|
root->_filename = _filename;
|
|
}
|
|
|
|
// Load the data for the item
|
|
item->load(file);
|
|
}
|
|
|
|
file->isClassStart();
|
|
} while (file->isClassStart());
|
|
|
|
return root;
|
|
}
|
|
|
|
void CProjectItem::saveData(SimpleFile *file, CTreeItem *item) const {
|
|
while (item) {
|
|
item->saveHeader(file, 0);
|
|
item->save(file, 1);
|
|
item->saveFooter(file, 0);
|
|
|
|
CTreeItem *child = item->getFirstChild();
|
|
if (child) {
|
|
file->write("\n{\n", 3);
|
|
file->writeQuotedString("DOWN");
|
|
file->write("\n}\n", 3);
|
|
saveData(file, child);
|
|
file->write("\n{\n", 3);
|
|
file->writeQuotedString("UP");
|
|
} else {
|
|
file->write("\n{\n", 3);
|
|
file->writeQuotedString("ALONG");
|
|
}
|
|
|
|
file->write("\n}\n", 3);
|
|
item = item->getNextSibling();
|
|
}
|
|
}
|
|
|
|
void CProjectItem::preLoad() {
|
|
if (_gameManager)
|
|
_gameManager->preLoad();
|
|
|
|
CScreenManager *scrManager = CScreenManager::_currentScreenManagerPtr;
|
|
if (scrManager)
|
|
scrManager->preLoad();
|
|
}
|
|
|
|
void CProjectItem::postLoad() {
|
|
CGameManager *gameManager = getGameManager();
|
|
if (gameManager)
|
|
gameManager->postLoad(this);
|
|
|
|
CPetControl *petControl = getPetControl();
|
|
if (petControl)
|
|
petControl->postLoad();
|
|
}
|
|
|
|
void CProjectItem::preSave() {
|
|
if (_gameManager)
|
|
_gameManager->preSave(this);
|
|
}
|
|
|
|
void CProjectItem::postSave() {
|
|
if (_gameManager)
|
|
_gameManager->postSave();
|
|
}
|
|
|
|
CPetControl *CProjectItem::getPetControl() const {
|
|
CDontSaveFileItem *fileItem = getDontSaveFileItem();
|
|
CTreeItem *treeItem;
|
|
|
|
if (!fileItem || (treeItem = fileItem->getLastChild()) == nullptr)
|
|
return nullptr;
|
|
|
|
while (treeItem) {
|
|
CPetControl *petControl = dynamic_cast<CPetControl *>(treeItem);
|
|
if (petControl)
|
|
return petControl;
|
|
|
|
treeItem = treeItem->getPriorSibling();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
CRoomItem *CProjectItem::findFirstRoom() const {
|
|
return dynamic_cast<CRoomItem *>(findChildInstance(CRoomItem::_type));
|
|
}
|
|
|
|
CTreeItem *CProjectItem::findChildInstance(ClassDef *classDef) const {
|
|
CTreeItem *treeItem = getFirstChild();
|
|
if (treeItem == nullptr)
|
|
return nullptr;
|
|
|
|
do {
|
|
CTreeItem *childItem = treeItem->getFirstChild();
|
|
if (childItem) {
|
|
do {
|
|
if (childItem->isInstanceOf(classDef))
|
|
return childItem;
|
|
} while ((childItem = childItem->getNextSibling()) != nullptr);
|
|
}
|
|
} while ((treeItem = treeItem->getNextSibling()) != nullptr);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
CRoomItem *CProjectItem::findNextRoom(CRoomItem *priorRoom) const {
|
|
return dynamic_cast<CRoomItem *>(findSiblingChildInstanceOf(CRoomItem::_type, priorRoom));
|
|
}
|
|
|
|
CTreeItem *CProjectItem::findSiblingChildInstanceOf(ClassDef *classDef, CTreeItem *startItem) const {
|
|
for (CTreeItem *treeItem = startItem->getParent()->getNextSibling();
|
|
treeItem; treeItem = treeItem->getNextSibling()) {
|
|
for (CTreeItem *childItem = treeItem->getFirstChild();
|
|
childItem; childItem = childItem->getNextSibling()) {
|
|
if (childItem->isInstanceOf(classDef))
|
|
return childItem;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
CDontSaveFileItem *CProjectItem::getDontSaveFileItem() const {
|
|
for (CTreeItem *treeItem = getFirstChild(); treeItem; treeItem = treeItem->getNextSibling()) {
|
|
if (treeItem->isInstanceOf(CDontSaveFileItem::_type))
|
|
return dynamic_cast<CDontSaveFileItem *>(treeItem);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
CRoomItem *CProjectItem::findHiddenRoom() {
|
|
return dynamic_cast<CRoomItem *>(findByName("HiddenRoom"));
|
|
}
|
|
|
|
CViewItem *CProjectItem::findView(int roomNumber, int nodeNumber, int viewNumber) {
|
|
CTreeItem *treeItem = getFirstChild();
|
|
CRoomItem *roomItem = nullptr;
|
|
|
|
// Scan for the specified room
|
|
if (treeItem) {
|
|
do {
|
|
CTreeItem *childItem = treeItem->getFirstChild();
|
|
CRoomItem *rItem = dynamic_cast<CRoomItem *>(childItem);
|
|
if (rItem && rItem->_roomNumber == roomNumber) {
|
|
roomItem = rItem;
|
|
break;
|
|
}
|
|
} while ((treeItem = treeItem->getNextSibling()) != nullptr);
|
|
}
|
|
if (!roomItem)
|
|
return nullptr;
|
|
|
|
// Scan for the specified node within the room
|
|
CNodeItem *nodeItem = nullptr;
|
|
|
|
CNodeItem *nItem = dynamic_cast<CNodeItem *>(
|
|
roomItem->findChildInstanceOf(CNodeItem::_type));
|
|
for (; nItem && !nodeItem; nItem = dynamic_cast<CNodeItem *>(
|
|
findNextInstanceOf(CNodeItem::_type, nItem))) {
|
|
if (nItem->_nodeNumber == nodeNumber)
|
|
nodeItem = nItem;
|
|
}
|
|
if (!nodeItem)
|
|
return nullptr;
|
|
|
|
// Scan for the specified view within the node
|
|
CViewItem *viewItem = dynamic_cast<CViewItem *>(
|
|
nodeItem->findChildInstanceOf(CViewItem::_type));
|
|
for (; viewItem; viewItem = dynamic_cast<CViewItem *>(
|
|
findNextInstanceOf(CViewItem::_type, viewItem))) {
|
|
if (viewItem->_viewNumber == viewNumber)
|
|
return viewItem;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SaveStateList CProjectItem::getSavegameList(const MetaEngine *metaEngine, const Common::String &target) {
|
|
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
|
|
Common::StringArray filenames;
|
|
Common::String saveDesc;
|
|
Common::String pattern = Common::String::format("%s.0??", target.c_str());
|
|
TitanicSavegameHeader header;
|
|
|
|
filenames = saveFileMan->listSavefiles(pattern);
|
|
sort(filenames.begin(), filenames.end()); // Sort to get the files in numerical order
|
|
|
|
SaveStateList saveList;
|
|
for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
|
|
const char *ext = strrchr(file->c_str(), '.');
|
|
int slot = ext ? atoi(ext + 1) : -1;
|
|
|
|
if (slot >= 0 && slot <= MAX_SAVES) {
|
|
Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file);
|
|
|
|
if (in) {
|
|
SimpleFile f;
|
|
f.open(in);
|
|
if (readSavegameHeader(&f, header))
|
|
saveList.push_back(SaveStateDescriptor(metaEngine, slot, header._saveName));
|
|
|
|
delete in;
|
|
}
|
|
}
|
|
}
|
|
|
|
return saveList;
|
|
}
|
|
|
|
WARN_UNUSED_RESULT bool CProjectItem::readSavegameHeader(SimpleFile *file, TitanicSavegameHeader &header, bool skipThumbnail) {
|
|
char saveIdentBuffer[SAVEGAME_STR_SIZE + 1];
|
|
header._thumbnail = nullptr;
|
|
header._totalFrames = 0;
|
|
|
|
// Validate the header Id
|
|
file->unsafeRead(saveIdentBuffer, SAVEGAME_STR_SIZE + 1);
|
|
if (strncmp(saveIdentBuffer, SAVEGAME_STR, SAVEGAME_STR_SIZE)) {
|
|
file->seek(-SAVEGAME_STR_SIZE, SEEK_CUR);
|
|
header._saveName = "Unnamed";
|
|
return true;
|
|
}
|
|
|
|
header._version = file->readByte();
|
|
if (header._version < MINIMUM_SAVEGAME_VERSION || header._version > CURRENT_SAVEGAME_VERSION)
|
|
return false;
|
|
|
|
// Read in the string
|
|
header._saveName.clear();
|
|
char ch;
|
|
while ((ch = (char)file->readByte()) != '\0') header._saveName += ch;
|
|
|
|
// Get the thumbnail
|
|
if (!Graphics::loadThumbnail(*file, header._thumbnail, skipThumbnail))
|
|
return false;
|
|
|
|
// Read in save date/time
|
|
header._year = file->readUint16LE();
|
|
header._month = file->readUint16LE();
|
|
header._day = file->readUint16LE();
|
|
header._hour = file->readUint16LE();
|
|
header._minute = file->readUint16LE();
|
|
header._totalFrames = file->readUint32LE();
|
|
|
|
return true;
|
|
}
|
|
|
|
void CProjectItem::writeSavegameHeader(SimpleFile *file, TitanicSavegameHeader &header) {
|
|
// Write out a savegame header
|
|
file->write(SAVEGAME_STR, SAVEGAME_STR_SIZE + 1);
|
|
|
|
file->writeByte(CURRENT_SAVEGAME_VERSION);
|
|
|
|
// Write savegame name
|
|
file->write(header._saveName.c_str(), header._saveName.size());
|
|
file->writeByte('\0');
|
|
|
|
// Create a thumbnail of the screen and save it out
|
|
Graphics::Surface *thumb = createThumbnail();
|
|
Graphics::saveThumbnail(*file, *thumb);
|
|
thumb->free();
|
|
delete thumb;
|
|
|
|
// Write out the save date/time
|
|
TimeDate td;
|
|
g_system->getTimeAndDate(td);
|
|
file->writeUint16LE(td.tm_year + 1900);
|
|
file->writeUint16LE(td.tm_mon + 1);
|
|
file->writeUint16LE(td.tm_mday);
|
|
file->writeUint16LE(td.tm_hour);
|
|
file->writeUint16LE(td.tm_min);
|
|
file->writeUint32LE(g_vm->_events->getTotalPlayTicks());
|
|
}
|
|
|
|
Graphics::Surface *CProjectItem::createThumbnail() {
|
|
Graphics::Surface *thumb = new Graphics::Surface();
|
|
|
|
::createThumbnailFromScreen(thumb);
|
|
return thumb;
|
|
}
|
|
|
|
CViewItem *CProjectItem::parseView(const CString &viewString) {
|
|
int firstIndex = viewString.indexOf('.');
|
|
int lastIndex = viewString.lastIndexOf('.');
|
|
CString roomName, nodeName, viewName;
|
|
|
|
if (firstIndex == -1) {
|
|
roomName = viewString;
|
|
}
|
|
else {
|
|
roomName = viewString.left(firstIndex);
|
|
|
|
if (lastIndex > firstIndex) {
|
|
nodeName = viewString.mid(firstIndex + 1, lastIndex - firstIndex - 1);
|
|
viewName = viewString.mid(lastIndex + 1);
|
|
}
|
|
else {
|
|
nodeName = viewString.mid(firstIndex + 1);
|
|
}
|
|
}
|
|
|
|
CGameManager *gameManager = getGameManager();
|
|
if (!gameManager)
|
|
return nullptr;
|
|
|
|
CRoomItem *room = gameManager->getRoom();
|
|
CProjectItem *project = room->getRoot();
|
|
|
|
// Ensure we have the specified room
|
|
if (project) {
|
|
if (room->getName().compareToIgnoreCase(roomName)) {
|
|
// Scan for the correct room
|
|
for (room = project->findFirstRoom();
|
|
room && room->getName().compareToIgnoreCase(roomName);
|
|
room = project->findNextRoom(room));
|
|
}
|
|
}
|
|
if (!room)
|
|
return nullptr;
|
|
|
|
// Find the designated node within the room
|
|
CNodeItem *node = dynamic_cast<CNodeItem *>(room->findChildInstanceOf(CNodeItem::_type));
|
|
while (node && node->getName().compareToIgnoreCase(nodeName))
|
|
node = dynamic_cast<CNodeItem *>(room->findNextInstanceOf(CNodeItem::_type, node));
|
|
if (!node)
|
|
return nullptr;
|
|
|
|
CViewItem *view = dynamic_cast<CViewItem *>(node->findChildInstanceOf(CViewItem::_type));
|
|
while (view && view->getName().compareToIgnoreCase(viewName))
|
|
view = dynamic_cast<CViewItem *>(node->findNextInstanceOf(CViewItem::_type, view));
|
|
if (!view)
|
|
return nullptr;
|
|
|
|
// Find the view, so return it
|
|
return view;
|
|
}
|
|
|
|
bool CProjectItem::changeView(const CString &viewName) {
|
|
return changeView(viewName, "");
|
|
}
|
|
|
|
bool CProjectItem::changeView(const CString &viewName, const CString &clipName) {
|
|
CViewItem *newView = parseView(viewName);
|
|
CGameManager *gameManager = getGameManager();
|
|
CViewItem *oldView = gameManager->getView();
|
|
|
|
if (!oldView || !newView)
|
|
return false;
|
|
|
|
CMovieClip *clip = nullptr;
|
|
if (!clipName.empty()) {
|
|
clip = oldView->findNode()->findRoom()->findClip(clipName);
|
|
} else {
|
|
CLinkItem *link = oldView->findLink(newView);
|
|
if (link)
|
|
clip = link->getClip();
|
|
}
|
|
|
|
gameManager->_gameState.changeView(newView, clip);
|
|
return true;
|
|
}
|
|
|
|
|
|
} // End of namespace Titanic
|