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

View File

@ -51,7 +51,7 @@ struct IGame {
virtual ~IGame() {} virtual ~IGame() {}
virtual void loadLevel(TR::LevelID id) {} virtual void loadLevel(TR::LevelID id) {}
virtual void loadNextLevel() {} 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 loadGame(int slot) {}
virtual void applySettings(const Core::Settings &settings) {} 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)); 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); 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", saveStats.time, saveStats.distance,
sprintf(buf, "stats: time = %d, distance = %d, secrets = %c%c%c, pickups = %d, mediUsed = %d, ammoUsed = %d, kills = %d", stats.time, stats.distance, (saveStats.secrets & 4) ? '1' : '0',
(stats.secrets & 4) ? '1' : '0', (saveStats.secrets & 2) ? '1' : '0',
(stats.secrets & 2) ? '1' : '0', (saveStats.secrets & 1) ? '1' : '0', saveStats.pickups, saveStats.mediUsed, saveStats.ammoUsed, saveStats.kills);
(stats.secrets & 1) ? '1' : '0', stats.pickups, stats.mediUsed, stats.ammoUsed, stats.kills);
Debug::Draw::text(vec2(16, y += 16), vec4(1.0f), buf); Debug::Draw::text(vec2(16, y += 16), vec4(1.0f), buf);
y += 16; y += 16;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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