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:
parent
7e0a467016
commit
744da8e840
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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) {}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
21
src/game.h
21
src/game.h
@ -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);
|
||||
|
@ -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) {
|
||||
|
107
src/inventory.h
107
src/inventory.h
@ -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);
|
||||
|
19
src/lara.h
19
src/lara.h
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
128
src/level.h
128
src/level.h
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user