1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-02-24 23:42:49 +01:00

fix saving game, level end stats screen

This commit is contained in:
XProger 2018-10-22 00:42:06 +03:00
parent 7e0a467016
commit 744da8e840
11 changed files with 194 additions and 161 deletions

View File

@ -128,7 +128,7 @@ struct Character : Controller {
virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) {
if (getEntity().isEnemy() && health > 0.0f && health <= damage)
level->stats.kills++;
saveStats.kills++;
health = max(0.0f, health - damage);
}

View File

@ -51,7 +51,7 @@ struct IGame {
virtual ~IGame() {}
virtual void loadLevel(TR::LevelID id) {}
virtual void loadNextLevel() {}
virtual void saveGame(bool checkpoint, bool updateStats) {}
virtual void saveGame(TR::LevelID id, bool checkpoint, bool updateStats) {}
virtual void loadGame(int slot) {}
virtual void applySettings(const Core::Settings &settings) {}

View File

@ -711,11 +711,10 @@ namespace Debug {
sprintf(buf, "floor = %d, roomBelow = %d, roomAbove = %d, roomNext = %d, height = %d", info.floorIndex, info.roomBelow, info.roomAbove, info.roomNext, int(info.floor - info.ceiling));
Debug::Draw::text(vec2(16, y += 16), vec4(1.0f), buf);
const SaveProgress &stats = game->getLevel()->stats;
sprintf(buf, "stats: time = %d, distance = %d, secrets = %c%c%c, pickups = %d, mediUsed = %d, ammoUsed = %d, kills = %d", stats.time, stats.distance,
(stats.secrets & 4) ? '1' : '0',
(stats.secrets & 2) ? '1' : '0',
(stats.secrets & 1) ? '1' : '0', stats.pickups, stats.mediUsed, stats.ammoUsed, stats.kills);
sprintf(buf, "stats: time = %d, distance = %d, secrets = %c%c%c, pickups = %d, mediUsed = %d, ammoUsed = %d, kills = %d", saveStats.time, saveStats.distance,
(saveStats.secrets & 4) ? '1' : '0',
(saveStats.secrets & 2) ? '1' : '0',
(saveStats.secrets & 1) ? '1' : '0', saveStats.pickups, saveStats.mediUsed, saveStats.ammoUsed, saveStats.kills);
Debug::Draw::text(vec2(16, y += 16), vec4(1.0f), buf);
y += 16;

View File

@ -2344,7 +2344,6 @@ namespace TR {
}
};
SaveProgress stats;
SaveState state;
int cutEntity;
@ -2838,7 +2837,6 @@ namespace TR {
initRoomMeshes();
initAnimTex();
memset(&stats, 0, sizeof(stats));
memset(&state, 0, sizeof(state));
initExtra();

View File

@ -4,15 +4,17 @@
#include "core.h"
#include "format.h"
#include "cache.h"
#include "inventory.h"
#include "level.h"
#include "ui.h"
#include "savegame.h"
ShaderCache *shaderCache;
Inventory *inventory;
namespace Game {
Level *level;
Stream *nextLevel;
Level *level;
Stream *nextLevel;
void startLevel(Stream *lvl) {
TR::LevelID id = TR::LVL_MAX;
@ -23,7 +25,7 @@ namespace Game {
bool playVideo = true;
if (loadSlot != -1)
playVideo = (saveSlots[loadSlot].level & LVL_FLAG_CHECKPOINT) == 0;
playVideo = !saveSlots[loadSlot].isCheckpoint();
delete level;
level = new Level(*lvl);
@ -130,6 +132,8 @@ namespace Game {
else
strcpy(fileName, lvlName);
inventory = new Inventory();
init(new Stream(fileName));
}
@ -140,6 +144,7 @@ namespace Game {
Debug::deinit();
#endif
delete level;
delete inventory;
UI::deinit();
delete shaderCache;
Core::deinit();
@ -187,8 +192,8 @@ namespace Game {
return true;
#ifdef _DEBUG
if (Input::down[ik0] && !level->inventory->isActive()) {
level->inventory->toggle(0, Inventory::PAGE_LEVEL_STATS);
if (Input::down[ik0] && !inventory->isActive()) {
inventory->toggle(0, Inventory::PAGE_LEVEL_STATS);
Input::down[ik0] = false;
}
@ -198,13 +203,13 @@ namespace Game {
}
#endif
if (Input::down[ik5] && !level->inventory->isActive()) {
if (Input::down[ik5] && !inventory->isActive()) {
if (level->players[0]->canSaveGame())
level->saveGame(true, false);
level->saveGame(level->level.id, true, false);
Input::down[ik5] = false;
}
if (Input::down[ik9] && !level->inventory->isActive()) {
if (Input::down[ik9] && !inventory->isActive()) {
int slot = getSaveSlot(level->level.id, true);
if (slot == -1)
slot = getSaveSlot(level->level.id, false);

View File

@ -720,6 +720,10 @@ namespace TR {
id == LVL_TR3_CUT_9 || id == LVL_TR3_CUT_11 || id == LVL_TR3_CUT_12;
}
bool isTitleLevel(LevelID id) {
return id == LVL_TR1_TITLE || id == LVL_TR2_TITLE || id == LVL_TR3_TITLE;
}
Version getGameVersion() {
useEasyStart = true;
if (Stream::existsContent("DATA/GYM.PHD") || Stream::existsContent("GYM.PHD"))
@ -754,20 +758,6 @@ namespace TR {
return VER_UNKNOWN;
}
LevelID getNextSaveLevel(LevelID id) {
Version version = getGameVersionByLevel(id);
LevelID end = getEndId(version);
while (id != end) {
id = LevelID(id + 1);
if (!isCutsceneLevel(id))
return id;
}
return LVL_MAX;
}
void getGameLevelFile(char *dst, Version version, LevelID id) {
if (useEasyStart) {
switch (version) {

View File

@ -314,15 +314,14 @@ struct Inventory {
for (int i = 0; i < saveSlots.length; i++) {
const SaveSlot &slot = saveSlots[i];
bool isCheckpointSlot = (slot.level & LVL_FLAG_CHECKPOINT) != 0;
TR::LevelID id = TR::LevelID(slot.level & ~LVL_FLAG_CHECKPOINT);
TR::LevelID id = slot.getLevelID();
if (TR::getGameVersionByLevel(id) != (level->version & TR::VER_VERSION))
continue;
OptionItem item;
item.type = OptionItem::TYPE_BUTTON;
item.offset = isCheckpointSlot ? intptr_t(STR[STR_CURRENT_POSITION]) : intptr_t(TR::LEVEL_INFO[id].title); // offset as int pointer to level title string
item.offset = slot.isCheckpoint() ? intptr_t(STR[STR_CURRENT_POSITION]) : intptr_t(TR::LEVEL_INFO[id].title); // offset as int pointer to level title string
item.color = i; // color as slot index
optLoadSlots.push(item);
}
@ -563,15 +562,59 @@ struct Inventory {
inv->skipVideo();
}
Inventory(IGame *game) : game(game), active(false), chosen(false), index(0), targetIndex(0), page(PAGE_OPTION), targetPage(PAGE_OPTION), itemsCount(0), playerIndex(0), changeTimer(0.0f), nextLevel(TR::LVL_MAX), lastKey(cMAX) {
TR::LevelID id = game->getLevel()->id;
Inventory() : itemsCount(0) {
memset(background, 0, sizeof(background));
reset(NULL);
}
~Inventory() {
delete video;
clear();
}
void clear() {
for (int i = 0; i < itemsCount; i++)
delete items[i];
itemsCount = 0;
for (int i = 0; i < COUNT(background); i++) {
delete background[i];
background[i] = NULL;
}
}
void reset(IGame *game) {
clear();
this->game = game;
active = false;
chosen = false;
index = targetIndex = 0;
page = targetPage = PAGE_OPTION;
playerIndex = 0;
changeTimer = 0.0f;
nextLevel = TR::LVL_MAX;
lastKey = cMAX;
phaseRing = phasePage = phaseChoose = phaseSelect = 0.0f;
memset(pageItemIndex, 0, sizeof(pageItemIndex));
waitForKey = NULL;
video = NULL;
titleTimer = TITLE_LOADING;
if (!game) return;
TR::Level *level = game->getLevel();
TR::LevelID id = level->id;
add(TR::Entity::INV_PASSPORT);
add(TR::Entity::INV_DETAIL);
add(TR::Entity::INV_SOUND);
add(TR::Entity::INV_CONTROLS);
if (!game->getLevel()->isTitle() && id != TR::LVL_TR1_GYM && id != TR::LVL_TR2_ASSAULT) {
if (!level->isTitle() && id != TR::LVL_TR1_GYM && id != TR::LVL_TR2_ASSAULT) {
/*
if (level->extra.inv.map != -1)
add(TR::Entity::INV_MAP);
@ -600,34 +643,12 @@ struct Inventory {
#endif
}
TR::Level *level = game->getLevel();
memset(background, 0, sizeof(background));
if (level->isTitle()) {
add(TR::Entity::INV_HOME);
} else {
add(TR::Entity::INV_COMPASS);
add(TR::Entity::INV_STOPWATCH);
}
phaseRing = phasePage = phaseChoose = phaseSelect = 0.0f;
memset(pageItemIndex, 0, sizeof(pageItemIndex));
waitForKey = NULL;
video = NULL;
titleTimer = TITLE_LOADING;
}
~Inventory() {
delete video;
for (int i = 0; i < itemsCount; i++)
delete items[i];
for (int i = 0; i < COUNT(background); i++)
delete background[i];
}
void startVideo() {
@ -795,8 +816,10 @@ struct Inventory {
chosen = false;
if (active) {
for (int i = 0; i < itemsCount; i++)
items[i]->reset();
if (curPage != PAGE_LEVEL_STATS) {
for (int i = 0; i < itemsCount; i++)
items[i]->reset();
}
nextLevel = TR::LVL_MAX;
phasePage = 1.0f;
@ -1102,7 +1125,7 @@ struct Inventory {
Controller *controller = (Controller*)e.controller;
controller->deactivate(true);
}
game->saveGame(index > -1, false);
game->saveGame(game->getLevel()->id, index > -1, false);
}
toggle(playerIndex, targetPage);
}
@ -1173,6 +1196,9 @@ struct Inventory {
}
lastKey = key;
if (page == PAGE_SAVEGAME || page == PAGE_LEVEL_STATS)
return;
ready = active && phaseRing == 1.0f && phasePage == 1.0f;
float w = 90.0f * DEG2RAD * Core::deltaTime;
@ -1726,16 +1752,15 @@ struct Inventory {
char buf[256];
char time[16];
TR::Level *level = game->getLevel();
SaveProgress &stats = level->stats;
int secretsMax = 3;
int secrets = ((stats.secrets & 1) != 0) +
((stats.secrets & 2) != 0) +
((stats.secrets & 4) != 0);
int secrets = ((saveStats.secrets & 1) != 0) +
((saveStats.secrets & 2) != 0) +
((saveStats.secrets & 4) != 0);
int s = stats.time % 60;
int m = stats.time / 60 % 60;
int h = stats.time / 3600;
int s = saveStats.time % 60;
int m = saveStats.time / 60 % 60;
int h = saveStats.time / 3600;
if (h)
sprintf(time, "%d:%02d:%02d", h, m, s);
@ -1743,9 +1768,9 @@ struct Inventory {
sprintf(time, "%d:%02d", m, s);
sprintf(buf, STR[STR_LEVEL_STATS],
TR::LEVEL_INFO[level->id].title,
stats.kills,
stats.pickups,
TR::LEVEL_INFO[saveStats.level].title,
saveStats.kills,
saveStats.pickups,
secrets, secretsMax, time);
UI::textOut(pos, buf, UI::aCenter, UI::width);

View File

@ -595,6 +595,9 @@ struct Lara : Character {
}
virtual bool getSaveData(SaveEntity &data) {
if (camera->cameraIndex != 0) // only player1 can be saved
return false;
Character::getSaveData(data);
data.extraSize = sizeof(data.extra.lara);
data.extra.lara.velX = velocity.x;
@ -1020,7 +1023,7 @@ struct Lara : Character {
}
if (shots) {
level->stats.ammoUsed += ((wpnCurrent == TR::Entity::SHOTGUN) ? 1 : 2);
saveStats.ammoUsed += ((wpnCurrent == TR::Entity::SHOTGUN) ? 1 : 2);
game->playSound(wpnGetSound(), pos, Sound::PAN);
game->playSound(TR::SND_RICOCHET, nearPos, Sound::PAN);
@ -1635,7 +1638,7 @@ struct Lara : Character {
case TR::Entity::INV_UZIS : wpnChange(TR::Entity::UZIS); break;
case TR::Entity::INV_MEDIKIT_SMALL :
case TR::Entity::INV_MEDIKIT_BIG :
level->stats.mediUsed += (item == TR::Entity::INV_MEDIKIT_SMALL) ? 1 : 2;
saveStats.mediUsed += (item == TR::Entity::INV_MEDIKIT_SMALL) ? 1 : 2;
damageTime = LARA_DAMAGE_TIME;
health = min(LARA_MAX_HEALTH, health + (item == TR::Entity::INV_MEDIKIT_SMALL ? LARA_MAX_HEALTH / 2 : LARA_MAX_HEALTH));
game->playSound(TR::SND_HEALTH, pos, Sound::PAN);
@ -2107,8 +2110,8 @@ struct Lara : Character {
effect = TR::Effect::Type(cmd.args);
break;
case TR::Action::SECRET :
if (!(level->stats.secrets & (1 << cmd.args))) {
level->stats.secrets |= 1 << cmd.args;
if (!(saveStats.secrets & (1 << cmd.args))) {
saveStats.secrets |= 1 << cmd.args;
if (!game->playSound(TR::SND_SECRET, pos))
game->playTrack(TR::TRACK_TR1_SECRET, true);
}
@ -2839,7 +2842,7 @@ struct Lara : Character {
pickupList[i]->deactivate();
pickupList[i]->flags.invisible = true;
game->invAdd(pickupList[i]->getEntity().type, 1);
level->stats.pickups++;
saveStats.pickups++;
}
pickupListCount = 0;
}
@ -2999,7 +3002,7 @@ struct Lara : Character {
w *= TURN_FAST;
else if (state == STATE_FAST_BACK)
w *= TURN_FAST_BACK;
else if (state == STATE_TURN_LEFT || state == STATE_TURN_RIGHT || state == STATE_WALK || state == STATE_STOP)
else if (state == STATE_TURN_LEFT || state == STATE_TURN_RIGHT || state == STATE_WALK || (state == STATE_STOP && animation.index == ANIM_LANDING))
w *= TURN_NORMAL;
else if (state == STATE_FORWARD_JUMP || state == STATE_BACK)
w *= TURN_SLOW;
@ -3111,7 +3114,7 @@ struct Lara : Character {
vTilt *= 2.0f;
vTilt *= rotFactor.y;
bool VR = (Core::settings.detail.stereo == Core::Settings::STEREO_VR) && camera->firstPerson;
updateTilt((state == STATE_RUN || state == STATE_STOP || stand == STAND_UNDERWATER) && !VR, vTilt.x, vTilt.y);
updateTilt((state == STATE_RUN || (state == STATE_STOP && animation.index == ANIM_LANDING) || stand == STAND_UNDERWATER) && !VR, vTilt.x, vTilt.y);
collisionOffset = vec3(0.0f);
@ -3123,7 +3126,7 @@ struct Lara : Character {
statsDistDelta += (pos - oldPos).length();
while (statsDistDelta >= UNITS_PER_METER) {
statsDistDelta -= UNITS_PER_METER;
level->stats.distance++;
saveStats.distance++;
}
}
}

View File

@ -25,6 +25,7 @@
extern ShaderCache *shaderCache;
extern void loadLevelAsync(Stream *stream, void *userData);
extern Inventory *inventory;
extern Array<SaveSlot> saveSlots;
extern SaveResult saveResult;
extern int loadSlot;
@ -32,7 +33,6 @@ extern int loadSlot;
struct Level : IGame {
TR::Level level;
Inventory *inventory;
Texture *atlas;
MeshBuilder *mesh;
@ -59,6 +59,7 @@ struct Level : IGame {
bool needRedrawTitleBG;
bool needRedrawReflections;
bool needRenderGame;
bool showStats;
TR::LevelID nextLevel;
@ -77,29 +78,28 @@ struct Level : IGame {
}
virtual void loadNextLevel() {
if (nextLevel != TR::LVL_MAX) return;
TR::LevelID id = TR::LVL_MAX;
#ifdef _OS_WEB
if (level.id == TR::LVL_TR1_2 && level.version != TR::VER_TR1_PC) {
if (level.id == TR::LVL_TR1_2 && level.version != TR::VER_TR1_PC)
id = TR::LVL_TR1_TITLE;
return;
} else
else
#endif
id = (level.isEnd() || level.isHome()) ? level.getTitleId() : TR::LevelID(level.id + 1);
if (nextLevel == TR::LVL_MAX) {
if (!level.isTitle() && loadSlot == -1 && !TR::isCutsceneLevel(level.id)) {
// update statistics info for current level
saveGame(false, true);
// save next level
level.id = TR::getNextSaveLevel(level.id); // get next not cutscene level
if (level.id != TR::LVL_MAX && !level.isTitle()) {
memset(&level.stats, 0, sizeof(level.stats));
saveGame(false, false);
loadSlot = getSaveSlot(level.id, false);
}
if (!level.isTitle() && loadSlot == -1) {
// update statistics info for current level
if (!TR::isCutsceneLevel(level.id) && !level.isHome())
saveGame(level.id, false, true);
// save next level
if (!TR::isCutsceneLevel(id) && !TR::isTitleLevel(id)) {
saveGame(id, false, false);
loadSlot = getSaveSlot(id, false);
showStats = true;
}
loadLevel(id);
}
loadLevel(id);
}
virtual void invShow(int playerIndex, int page, int itemIndex = -1) {
@ -110,21 +110,22 @@ struct Level : IGame {
SaveSlot createSaveSlot(TR::LevelID id, bool checkpoint, bool dummy = false) {
SaveSlot slot;
slot.level = id | (checkpoint ? LVL_FLAG_CHECKPOINT : 0);
// allocate oversized data for save slot
slot.data = new uint8[sizeof(SaveProgress) + sizeof(SaveItem) * inventory->itemsCount + // for every save
sizeof(SaveState) + sizeof(SaveEntity) * level.entitiesCount]; // only for checkpoints
slot.data = new uint8[sizeof(SaveStats) + sizeof(SaveItem) * inventory->itemsCount + // for every save
sizeof(SaveState) + sizeof(SaveEntity) * level.entitiesCount]; // only for checkpoints
uint8 *ptr = slot.data;
// level progress stats
SaveProgress *levelStats = (SaveProgress*)ptr;
ptr += sizeof(*levelStats);
if (dummy)
memset(levelStats, 0, sizeof(*levelStats));
SaveStats *stats = (SaveStats*)ptr;
if (!checkpoint)
memset(stats, 0, sizeof(*stats));
else
*levelStats = level.stats;
*stats = saveStats;
stats->level = id;
stats->checkpoint = checkpoint;
ptr += sizeof(*stats);
// inventory items
int32 *itemsCount = (int32*)ptr;
@ -143,7 +144,7 @@ struct Level : IGame {
for (int i = 0; i < inventory->itemsCount; i++) {
Inventory::Item *invItem = inventory->items[i];
if (!checkpoint && !TR::Entity::isCrossLevelItem(TR::Entity::convFromInv(invItem->type))) continue;
if (!TR::Entity::isCrossLevelItem(TR::Entity::convFromInv(invItem->type))) continue;
SaveItem *item = (SaveItem*)ptr;
ptr += sizeof(*item);
@ -180,20 +181,17 @@ struct Level : IGame {
return slot;
}
void parseLoadSlot() {
if (loadSlot == -1) return;
const SaveSlot &slot = saveSlots[loadSlot];
loadSlot = -1;
void parseSaveSlot(const SaveSlot &slot) {
clearInventory();
uint8 *data = slot.data;
uint8 *ptr = data;
// level progress stats
level.stats = *(SaveProgress*)ptr;
ptr += sizeof(level.stats);
if (slot.isCheckpoint())
saveStats = *(SaveStats*)ptr; // start level current position
ptr += sizeof(saveStats);
// inventory items
int32 itemsCount = *(int32*)ptr;
@ -205,7 +203,7 @@ struct Level : IGame {
ptr += sizeof(*item);
}
if (slot.level & LVL_FLAG_CHECKPOINT) {
if (slot.isCheckpoint()) {
clearEntities();
// level state
@ -242,13 +240,13 @@ struct Level : IGame {
if (level.state.flags.flipped) {
flipMap();
level.state.flags.flipped = true;
}
uint8 track = level.state.flags.track;
level.state.flags.track = 0;
playTrack(track);
} else
memset(&level.stats, 0, sizeof(level.stats));
}
statsTimeDelta = 0.0f;
}
@ -265,7 +263,7 @@ struct Level : IGame {
}
}
virtual void saveGame(bool checkpoint, bool updateStats) {
virtual void saveGame(TR::LevelID id, bool checkpoint, bool updateStats) {
ASSERT(saveResult != SAVE_RESULT_WAIT);
if (saveResult == SAVE_RESULT_WAIT)
@ -273,8 +271,6 @@ struct Level : IGame {
LOG("Save Game...\n");
TR::LevelID id = level.id;
SaveSlot slot;
if (updateStats) {
removeSaveSlot(id, true);
@ -284,8 +280,7 @@ struct Level : IGame {
saveSlots.push(slot);
} else
slot = saveSlots[index];
SaveProgress *levelStats = (SaveProgress*)slot.data;
*levelStats = level.stats;
*(SaveStats*)slot.data = saveStats;
} else {
removeSaveSlot(id, checkpoint); // remove checkpoints and level saves
saveSlots.push(createSaveSlot(id, checkpoint));
@ -796,6 +791,7 @@ struct Level : IGame {
GAPI::freeEDRAM();
#endif
nextLevel = TR::LVL_MAX;
showStats = false;
params = (Params*)&Core::params;
params->time = 0.0f;
@ -808,9 +804,6 @@ struct Level : IGame {
initTextures();
mesh = new MeshBuilder(level, atlas);
initOverrides();
inventory = new Inventory(this);
initEntities();
shadow = NULL;
@ -860,16 +853,22 @@ struct Level : IGame {
camera->doCutscene(lara->pos, lara->angle.y);
}
*/
if (!level.isCutsceneLevel()) {
inventory->reset(this);
memset(&saveStats, 0, sizeof(saveStats));
saveStats.level = level.id;
}
saveResult = SAVE_RESULT_SUCCESS;
if (loadSlot != -1 && (saveSlots[loadSlot].level & ~LVL_FLAG_CHECKPOINT) == level.id)
parseLoadSlot();
if (loadSlot != -1 && saveSlots[loadSlot].getLevelID() == level.id) {
parseSaveSlot(saveSlots[loadSlot]);
loadSlot = -1;
}
Core::resetTime();
}
virtual ~Level() {
delete inventory;
for (int i = 0; i < level.entitiesCount; i++)
delete (Controller*)level.entities[i].controller;
@ -1742,16 +1741,12 @@ struct Level : IGame {
if (inventory->titleTimer > 1.0f)
return;
if (loadSlot > -1 && nextLevel == TR::LVL_MAX && !level.isCutsceneLevel()) {
if (inventory->isActive())
return;
TR::LevelID id = TR::LevelID(saveSlots[loadSlot].level & ~LVL_FLAG_CHECKPOINT);
loadLevel(id);
return;
}
if (nextLevel != TR::LVL_MAX && !inventory->isActive()) {
if (showStats) {
inventory->toggle(0, Inventory::PAGE_LEVEL_STATS);
showStats = false;
return;
}
isEnded = true;
char buf[64];
TR::getGameLevelFile(buf, level.version, nextLevel);
@ -1760,6 +1755,14 @@ struct Level : IGame {
return;
}
if (loadSlot > -1 && nextLevel == TR::LVL_MAX) {
if (inventory->isActive())
return;
loadLevel(saveSlots[loadSlot].getLevelID());
return;
}
UI::update();
float volWater, volTrack;
@ -1769,10 +1772,13 @@ struct Level : IGame {
volWater = 0.0f;
volTrack = level.isTitle() ? 0.9f : 0.0f;
} else {
statsTimeDelta += Core::deltaTime;
while (statsTimeDelta >= 1.0f) {
statsTimeDelta -= 1.0f;
level.stats.time++;
if (!level.isCutsceneLevel()) {
statsTimeDelta += Core::deltaTime;
while (statsTimeDelta >= 1.0f) {
statsTimeDelta -= 1.0f;
saveStats.time++;
}
}
params->time += Core::deltaTime;

View File

@ -6,9 +6,8 @@
#define MAX_FLIPMAP_COUNT 32
#define MAX_TRACKS_COUNT 256
#define LVL_FLAG_CHECKPOINT (1 << 31)
#define SAVE_FILENAME "savegame.dat"
#define SAVE_MAGIC FOURCC("OLS1")
#define SAVE_MAGIC FOURCC("OLS2")
enum SaveResult {
SAVE_RESULT_SUCCESS,
@ -21,7 +20,9 @@ struct SaveItem {
uint16 count;
};
struct SaveProgress {
struct SaveStats {
uint32 level:31;
uint32 checkpoint:1;
uint32 time;
uint32 distance;
uint32 secrets;
@ -89,15 +90,22 @@ struct SaveState {
};
struct SaveSlot {
uint32 level;
uint32 size;
uint8 *data;
TR::LevelID getLevelID() const {
return TR::LevelID(((SaveStats*)data)->level);
}
bool isCheckpoint() const {
return ((SaveStats*)data)->checkpoint;
}
static int cmp(const SaveSlot &a, const SaveSlot &b) {
if (a.level < b.level)
return -1;
if (a.level > b.level)
return +1;
uint32 ia = *(uint32*)a.data; // level + checkpoint flag
uint32 ib = *(uint32*)b.data;
if (ia < ib) return -1;
if (ia > ib) return +1;
return 0;
}
};
@ -105,6 +113,7 @@ struct SaveSlot {
Array<SaveSlot> saveSlots;
SaveResult saveResult;
int loadSlot;
SaveStats saveStats;
void freeSaveSlots() {
for (int i = 0; i < saveSlots.length; i++)
@ -121,7 +130,6 @@ void readSaveSlots(Stream *stream) {
SaveSlot slot;
while (stream->pos < stream->size) {
stream->read(slot.level);
stream->read(slot.size);
stream->read(slot.data, slot.size);
saveSlots.push(slot);
@ -131,7 +139,7 @@ void readSaveSlots(Stream *stream) {
uint8* writeSaveSlots(int &size) {
size = 4;
for (int i = 0; i < saveSlots.length; i++)
size += 4 + 4 + saveSlots[i].size;
size += 4 + saveSlots[i].size;
uint8 *data = new uint8[size];
uint8 *ptr = data;
@ -142,10 +150,9 @@ uint8* writeSaveSlots(int &size) {
for (int i = 0; i < saveSlots.length; i++) {
SaveSlot &s = saveSlots[i];
memcpy(ptr + 0, &s.level, 4);
memcpy(ptr + 4, &s.size, 4);
memcpy(ptr + 8, s.data, s.size);
ptr += 4 + 4 + s.size;
memcpy(ptr + 0, &s.size, 4);
memcpy(ptr + 4, s.data, s.size);
ptr += 4 + s.size;
}
return data;
@ -157,13 +164,12 @@ void removeSaveSlot(TR::LevelID levelID, bool checkpoint) {
for (int i = 0; i < saveSlots.length; i++) {
SaveSlot &slot = saveSlots[i];
bool isCheckpointSlot = (slot.level & LVL_FLAG_CHECKPOINT) != 0;
TR::LevelID id = TR::LevelID(slot.level & ~LVL_FLAG_CHECKPOINT);
TR::LevelID id = slot.getLevelID();
if (TR::getGameVersionByLevel(id) != version)
continue;
if (isCheckpointSlot || (!checkpoint && levelID == id)) {
if (slot.isCheckpoint() || (!checkpoint && levelID == id)) {
delete[] slot.data;
saveSlots.remove(i);
i--;
@ -178,13 +184,12 @@ int getSaveSlot(TR::LevelID levelID, bool checkpoint) {
for (int i = 0; i < saveSlots.length; i++) {
SaveSlot &slot = saveSlots[i];
bool isCheckpointSlot = (slot.level & LVL_FLAG_CHECKPOINT) != 0;
TR::LevelID id = TR::LevelID(slot.level & ~LVL_FLAG_CHECKPOINT);
TR::LevelID id = slot.getLevelID();
if (TR::getGameVersionByLevel(id) != version)
continue;
if ((checkpoint && isCheckpointSlot) || (!checkpoint && levelID == id))
if ((checkpoint && slot.isCheckpoint()) || (!checkpoint && levelID == id))
return i;
}

View File

@ -110,6 +110,8 @@ const char *helpText =
"Start - add second player or restore Lara@"
"H - Show or hide this help@"
"ALT + ENTER - Fullscreen@"
"5 - Save Game@"
"9 - Load Game@"
"C - Look@"
"R - Slow motion@"
"T - Fast motion@"