ULTIMA4: Move saving code into Savegame class

This also merges the serparate save files the original had
for the party, creatures, and dungeon information.
This commit is contained in:
Paul Gilbert 2020-03-28 14:41:46 -07:00 committed by Paul Gilbert
parent 30fa38e336
commit 21355706b4
4 changed files with 138 additions and 164 deletions

View File

@ -22,12 +22,115 @@
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/filesys/io.h"
#include "ultima/ultima4/game/object.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/game/object.h"
#include "ultima/ultima4/map/location.h"
namespace Ultima {
namespace Ultima4 {
void SaveGame::save(Common::WriteStream *stream) {
Common::Serializer ser(nullptr, stream);
if (g_context->_location->_prev) {
_x = g_context->_location->_coords.x;
_y = g_context->_location->_coords.y;
_dngLevel = g_context->_location->_coords.z;
_dngX = g_context->_location->_prev->_coords.x;
_dngY = g_context->_location->_prev->_coords.y;
} else {
_x = g_context->_location->_coords.x;
_y = g_context->_location->_coords.y;
_dngLevel = g_context->_location->_coords.z;
}
_location = g_context->_location->_map->_id;
// _orientation = (Direction)(g_ultima->_saveGame->_orientation - DIR_WEST);
synchronize(ser);
/*
* Save monsters
*/
// fix creature animations. This was done for compatibility with u4dos,
// so may be redundant now
g_context->_location->_map->resetObjectAnimations();
g_context->_location->_map->fillMonsterTable();
SaveGameMonsterRecord::synchronize(g_context->_location->_map->_monsterTable, ser);
/**
* Write dungeon info
*/
if (g_context->_location->_context & CTX_DUNGEON) {
unsigned int x, y, z;
typedef Std::map<const Creature *, int, Std::PointerHash> DngCreatureIdMap;
static DngCreatureIdMap id_map;
/**
* Map creatures to u4dos dungeon creature Ids
*/
if (id_map.size() == 0) {
id_map[creatureMgr->getById(RAT_ID)] = 1;
id_map[creatureMgr->getById(BAT_ID)] = 2;
id_map[creatureMgr->getById(GIANT_SPIDER_ID)] = 3;
id_map[creatureMgr->getById(GHOST_ID)] = 4;
id_map[creatureMgr->getById(SLIME_ID)] = 5;
id_map[creatureMgr->getById(TROLL_ID)] = 6;
id_map[creatureMgr->getById(GREMLIN_ID)] = 7;
id_map[creatureMgr->getById(MIMIC_ID)] = 8;
id_map[creatureMgr->getById(REAPER_ID)] = 9;
id_map[creatureMgr->getById(INSECT_SWARM_ID)] = 10;
id_map[creatureMgr->getById(GAZER_ID)] = 11;
id_map[creatureMgr->getById(PHANTOM_ID)] = 12;
id_map[creatureMgr->getById(ORC_ID)] = 13;
id_map[creatureMgr->getById(SKELETON_ID)] = 14;
id_map[creatureMgr->getById(ROGUE_ID)] = 15;
}
for (z = 0; z < g_context->_location->_map->_levels; z++) {
for (y = 0; y < g_context->_location->_map->_height; y++) {
for (x = 0; x < g_context->_location->_map->_width; x++) {
unsigned char tile = g_context->_location->_map->translateToRawTileIndex(*g_context->_location->_map->getTileFromData(MapCoords(x, y, z)));
Object *obj = g_context->_location->_map->objectAt(MapCoords(x, y, z));
/**
* Add the creature to the tile
*/
if (obj && obj->getType() == Object::CREATURE) {
const Creature *m = dynamic_cast<Creature *>(obj);
DngCreatureIdMap::iterator m_id = id_map.find(m);
if (m_id != id_map.end())
tile |= m_id->_value;
}
// Write the tile
stream->writeByte(tile);
}
}
}
/**
* Write out monsters
*/
// fix creature animations so they are compatible with u4dos.
// This may be redundant now for ScummVM
g_context->_location->_prev->_map->resetObjectAnimations();
g_context->_location->_prev->_map->fillMonsterTable(); /* fill the monster table so we can save it */
SaveGameMonsterRecord::synchronize(g_context->_location->_prev->_map->_monsterTable, ser);
}
}
void SaveGame::load(Common::SeekableReadStream *stream) {
}
void SaveGame::synchronize(Common::Serializer &s) {
int i;
@ -180,6 +283,12 @@ void SaveGamePlayerRecord::init() {
void SaveGameMonsterRecord::synchronize(SaveGameMonsterRecord *monsterTable, Common::Serializer &s) {
int i;
const uint32 IDENT = MKTAG('M', 'O', 'N', 'S');
uint32 val = IDENT;
s.syncAsUint32BE(val);
if (s.isLoading() && val != IDENT)
error("Invalid savegame");
if (s.isSaving() && !monsterTable) {
int dataSize = MONSTERTABLE_SIZE * 8;

View File

@ -225,9 +225,26 @@ struct SaveGameMonsterRecord {
* Represents the on-disk contents of PARTY.SAV.
*/
struct SaveGame {
void synchronize(Common::Serializer &s);
/**
* Initialize a new savegame structure
*/
void init(const SaveGamePlayerRecord *avatarInfo);
/**
* Load an entire savegame, including monsters
*/
void save(Common::WriteStream *stream);
/**
* Save an entire savegame, including monsters
*/
void load(Common::SeekableReadStream *stream);
/**
* Synchronizes data for the savegame structure
*/
void synchronize(Common::Serializer &s);
unsigned int _unknown1;
unsigned int _moves;
SaveGamePlayerRecord _players[8];

View File

@ -85,7 +85,6 @@ void gameInnHandler(void);
void gameLostEighth(Virtue virtue);
void gamePartyStarving(void);
time_t gameTimeSinceLastCommand(void);
int gameSave(void);
/* spell functions */
void gameCastSpell(unsigned int spell, int caster, int param);
@ -333,142 +332,6 @@ void GameController::init() {
TRACE(gameDbg, "gameInit() completed successfully.");
}
/**
* Saves the game state into party.sav and creatures.sav.
*/
int gameSave() {
Common::OutSaveFile *saveGameFile, *monstersFile, *dngMapFile;
SaveGame save = *g_ultima->_saveGame;
/*************************************************/
/* Make sure the savegame struct is accurate now */
if (g_context->_location->_prev) {
save._x = g_context->_location->_coords.x;
save._y = g_context->_location->_coords.y;
save._dngLevel = g_context->_location->_coords.z;
save._dngX = g_context->_location->_prev->_coords.x;
save._dngY = g_context->_location->_prev->_coords.y;
} else {
save._x = g_context->_location->_coords.x;
save._y = g_context->_location->_coords.y;
save._dngLevel = g_context->_location->_coords.z;
save._dngX = g_ultima->_saveGame->_dngX;
save._dngY = g_ultima->_saveGame->_dngY;
}
save._location = g_context->_location->_map->_id;
save._orientation = (Direction)(g_ultima->_saveGame->_orientation - DIR_WEST);
/* Done making sure the savegame struct is accurate */
/****************************************************/
saveGameFile = g_system->getSavefileManager()->openForSaving(PARTY_SAV_BASE_FILENAME);
if (!saveGameFile) {
screenMessage("Error opening " PARTY_SAV_BASE_FILENAME "\n");
return 0;
}
Common::Serializer ser(nullptr, saveGameFile);
save.synchronize(ser);
delete saveGameFile;
monstersFile = g_system->getSavefileManager()->openForSaving(MONSTERS_SAV_BASE_FILENAME);
if (!monstersFile) {
screenMessage("Error opening %s\n", MONSTERS_SAV_BASE_FILENAME);
return 0;
}
/* fix creature animations so they are compatible with u4dos */
g_context->_location->_map->resetObjectAnimations();
g_context->_location->_map->fillMonsterTable(); /* fill the monster table so we can save it */
Common::Serializer ser2(nullptr, monstersFile);
SaveGameMonsterRecord::synchronize(g_context->_location->_map->_monsterTable, ser2);
delete monstersFile;
/**
* Write dungeon info
*/
if (g_context->_location->_context & CTX_DUNGEON) {
unsigned int x, y, z;
typedef Std::map<const Creature *, int, Std::PointerHash> DngCreatureIdMap;
static DngCreatureIdMap id_map;
/**
* Map creatures to u4dos dungeon creature Ids
*/
if (id_map.size() == 0) {
id_map[creatureMgr->getById(RAT_ID)] = 1;
id_map[creatureMgr->getById(BAT_ID)] = 2;
id_map[creatureMgr->getById(GIANT_SPIDER_ID)] = 3;
id_map[creatureMgr->getById(GHOST_ID)] = 4;
id_map[creatureMgr->getById(SLIME_ID)] = 5;
id_map[creatureMgr->getById(TROLL_ID)] = 6;
id_map[creatureMgr->getById(GREMLIN_ID)] = 7;
id_map[creatureMgr->getById(MIMIC_ID)] = 8;
id_map[creatureMgr->getById(REAPER_ID)] = 9;
id_map[creatureMgr->getById(INSECT_SWARM_ID)] = 10;
id_map[creatureMgr->getById(GAZER_ID)] = 11;
id_map[creatureMgr->getById(PHANTOM_ID)] = 12;
id_map[creatureMgr->getById(ORC_ID)] = 13;
id_map[creatureMgr->getById(SKELETON_ID)] = 14;
id_map[creatureMgr->getById(ROGUE_ID)] = 15;
}
dngMapFile = g_system->getSavefileManager()->openForSaving("dngmap.sav");
if (!dngMapFile) {
screenMessage("Error opening dngmap.sav\n");
return 0;
}
for (z = 0; z < g_context->_location->_map->_levels; z++) {
for (y = 0; y < g_context->_location->_map->_height; y++) {
for (x = 0; x < g_context->_location->_map->_width; x++) {
unsigned char tile = g_context->_location->_map->translateToRawTileIndex(*g_context->_location->_map->getTileFromData(MapCoords(x, y, z)));
Object *obj = g_context->_location->_map->objectAt(MapCoords(x, y, z));
/**
* Add the creature to the tile
*/
if (obj && obj->getType() == Object::CREATURE) {
const Creature *m = dynamic_cast<Creature *>(obj);
DngCreatureIdMap::iterator m_id = id_map.find(m);
if (m_id != id_map.end())
tile |= m_id->_value;
}
// Write the tile
dngMapFile->writeByte(tile);
}
}
}
delete dngMapFile;
/**
* Write out monsters
*/
monstersFile = g_system->getSavefileManager()->openForSaving(
OUTMONST_SAV_BASE_FILENAME);
if (!monstersFile) {
screenMessage("Error opening %s\n", OUTMONST_SAV_BASE_FILENAME);
return 0;
}
/* fix creature animations so they are compatible with u4dos */
g_context->_location->_prev->_map->resetObjectAnimations();
g_context->_location->_prev->_map->fillMonsterTable(); /* fill the monster table so we can save it */
Common::Serializer ser3(nullptr, monstersFile);
SaveGameMonsterRecord::synchronize(g_context->_location->_prev->_map->_monsterTable, ser3);
delete monstersFile;
}
return 1;
}
/**
* Sets the view mode.
*/
@ -1171,9 +1034,11 @@ bool GameController::keyPressed(int key) {
case 'q':
screenMessage("Quit & Save...\n%d moves\n", g_ultima->_saveGame->_moves);
if (g_context->_location->_context & CTX_CAN_SAVE_GAME) {
gameSave();
screenMessage("Press Alt-x to quit\n");
} else screenMessage("%cNot here!%c\n", FG_GREY, FG_WHITE);
if (g_ultima->saveGameDialog())
screenMessage("Press Alt-x to quit\n");
} else {
screenMessage("%cNot here!%c\n", FG_GREY, FG_WHITE);
}
break;

View File

@ -746,18 +746,13 @@ void IntroController::finishInitiateGame(const Common::String &nameBuffer, SexTy
// ask questions that determine character class
startQuestions();
// write out save game an segue into game
SaveGame saveGame;
// Setup savegame fields. The original wrote out multiple files and
// then loaded them up once the game starts. Now we're simply setting
// up the savegame fields and letting the game read from them later,
// as if a savegame had been loaded
SaveGame &saveGame = *g_ultima->_saveGame;
SaveGamePlayerRecord avatar;
Common::OutSaveFile *saveGameFile = g_system->getSavefileManager()->openForSaving(PARTY_SAV_BASE_FILENAME);
if (!saveGameFile) {
_questionArea.disableCursor();
_errorMessage = "Unable to create save game!";
updateScreen();
return;
}
avatar.init();
strcpy(avatar.name, nameBuffer.c_str());
avatar._sex = sex;
@ -770,18 +765,6 @@ void IntroController::finishInitiateGame(const Common::String &nameBuffer, SexTy
saveGame._reagents[REAG_GARLIC] = 4;
saveGame._torches = 2;
Common::Serializer ser(nullptr, saveGameFile);
saveGame.synchronize(ser);
saveGameFile->finalize();
delete saveGameFile;
saveGameFile = g_system->getSavefileManager()->openForSaving(MONSTERS_SAV_BASE_FILENAME);
if (saveGameFile) {
Common::Serializer ser2(nullptr, saveGameFile);
SaveGameMonsterRecord::synchronize(nullptr, ser2);
delete saveGameFile;
}
_justInitiatedNewGame = true;
// show the text thats segues into the main game