Initial commit
This commit is contained in:
2121
engines/lastexpress/game/action.cpp
Normal file
2121
engines/lastexpress/game/action.cpp
Normal file
File diff suppressed because it is too large
Load Diff
457
engines/lastexpress/game/beetle.cpp
Normal file
457
engines/lastexpress/game/beetle.cpp
Normal file
@@ -0,0 +1,457 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "lastexpress/data/archive.h"
|
||||
#include "lastexpress/data/gold_archive.h"
|
||||
#include "lastexpress/game/beetle.h"
|
||||
#include "lastexpress/game/logic.h"
|
||||
|
||||
#include "lastexpress/lastexpress.h"
|
||||
|
||||
namespace LastExpress {
|
||||
|
||||
CBeetle::CBeetle(LastExpressEngine *engine) {
|
||||
_engine = engine;
|
||||
|
||||
// Walk sequences
|
||||
_sequences[0] = _engine->getArchiveManager()->loadSeq("BW000.seq", 15, 0);
|
||||
_sequences[3] = _engine->getArchiveManager()->loadSeq("BW045.seq", 15, 0);
|
||||
_sequences[6] = _engine->getArchiveManager()->loadSeq("BW090.seq", 15, 0);
|
||||
_sequences[9] = _engine->getArchiveManager()->loadSeq("BW135.seq", 15, 0);
|
||||
_sequences[12] = _engine->getArchiveManager()->loadSeq("BW180.seq", 15, 0);
|
||||
_sequences[15] = _engine->getArchiveManager()->loadSeq("BW225.seq", 15, 0);
|
||||
_sequences[18] = _engine->getArchiveManager()->loadSeq("BW270.seq", 15, 0);
|
||||
_sequences[21] = _engine->getArchiveManager()->loadSeq("BW315.seq", 15, 0);
|
||||
|
||||
// Angle turn sequences
|
||||
_sequences[1] = _engine->getArchiveManager()->loadSeq("BT000045.seq", 15, 0);
|
||||
_sequences[4] = _engine->getArchiveManager()->loadSeq("BT045090.seq", 15, 0);
|
||||
_sequences[7] = _engine->getArchiveManager()->loadSeq("BT090135.seq", 15, 0);
|
||||
_sequences[10] = _engine->getArchiveManager()->loadSeq("BT135180.seq", 15, 0);
|
||||
_sequences[13] = _engine->getArchiveManager()->loadSeq("BT180225.seq", 15, 0);
|
||||
_sequences[16] = _engine->getArchiveManager()->loadSeq("BT225270.seq", 15, 0);
|
||||
_sequences[19] = _engine->getArchiveManager()->loadSeq("BT270315.seq", 15, 0);
|
||||
_sequences[22] = _engine->getArchiveManager()->loadSeq("BT315000.seq", 15, 0);
|
||||
|
||||
// Inverse angle turn sequences
|
||||
_sequences[2] = _engine->getArchiveManager()->loadSeq("BT045000.seq", 15, 0);
|
||||
_sequences[5] = _engine->getArchiveManager()->loadSeq("BT090045.seq", 15, 0);
|
||||
_sequences[8] = _engine->getArchiveManager()->loadSeq("BT135090.seq", 15, 0);
|
||||
_sequences[11] = _engine->getArchiveManager()->loadSeq("BT180135.seq", 15, 0);
|
||||
_sequences[14] = _engine->getArchiveManager()->loadSeq("BT225180.seq", 15, 0);
|
||||
_sequences[17] = _engine->getArchiveManager()->loadSeq("BT270225.seq", 15, 0);
|
||||
_sequences[20] = _engine->getArchiveManager()->loadSeq("BT315270.seq", 15, 0);
|
||||
_sequences[23] = _engine->getArchiveManager()->loadSeq("BT000315.seq", 15, 0);
|
||||
|
||||
// Other sequences
|
||||
_sequences[24] = _engine->getArchiveManager()->loadSeq("BA135.seq", 15, 0);
|
||||
_sequences[25] = _engine->getArchiveManager()->loadSeq("BL045.seq", 15, 0);
|
||||
_sequences[26] = _engine->getArchiveManager()->loadSeq("BL000.seq", 15, 0);
|
||||
_sequences[27] = _engine->getArchiveManager()->loadSeq("BL315.seq", 15, 0);
|
||||
_sequences[28] = _engine->getArchiveManager()->loadSeq("BL180.seq", 15, 0);
|
||||
_sequences[29] = nullptr;
|
||||
_loaded = true;
|
||||
|
||||
for (int i = 0; i < 29; i++) {
|
||||
if (!_sequences[i]) {
|
||||
_loaded = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_fleeSpeed = 10;
|
||||
_coordOffset = 5;
|
||||
_coords.y = 178;
|
||||
_currentSequence = nullptr;
|
||||
_currentDirectionIndex = 0;
|
||||
_frame = nullptr;
|
||||
_mouseCooldown = 0;
|
||||
_directions[0] = 29;
|
||||
_spawnCounter = 0;
|
||||
}
|
||||
|
||||
CBeetle::~CBeetle() {
|
||||
if (_currentSequence)
|
||||
_engine->getSpriteManager()->removeSprite(&_currentSequence->sprites[_currentFrame]);
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(_sequences); i++) {
|
||||
if (_sequences[i]) {
|
||||
_engine->getMemoryManager()->freeMem(_sequences[i]->rawSeqData);
|
||||
delete _sequences[i];
|
||||
_sequences[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
_currentSequence = nullptr;
|
||||
}
|
||||
|
||||
void CBeetle::tick() {
|
||||
if (!_loaded)
|
||||
return;
|
||||
|
||||
checkMouse();
|
||||
|
||||
if (_mouseCooldown)
|
||||
_mouseCooldown--;
|
||||
|
||||
if (!_currentSequence || _directions[_currentDirectionIndex] == 29) {
|
||||
if (_engine->getLogicManager()->_items[kItemBeetle].floating != 3 ||
|
||||
((_spawnCounter || rnd(10)) && (_spawnCounter >= 3 || rnd(30)) && rnd(100))) {
|
||||
return;
|
||||
}
|
||||
|
||||
_spawnCounter++;
|
||||
if (_spawnCounter > 3)
|
||||
_spawnCounter = 0;
|
||||
|
||||
_engine->_beetle->setDirection(24);
|
||||
_coords.x = rnd(250) + 190;
|
||||
_coordOffset = rnd(5) + 5;
|
||||
|
||||
if (_fleeSpeed > 1)
|
||||
_fleeSpeed--;
|
||||
}
|
||||
|
||||
if (_frame) {
|
||||
_engine->getSpriteManager()->queueErase(_frame);
|
||||
_engine->getSpriteManager()->removeSprite(_frame);
|
||||
}
|
||||
|
||||
int curDir = _directions[_currentDirectionIndex];
|
||||
if (curDir == 0 || curDir == 3 || curDir == 6 || curDir == 9 || curDir == 12 || curDir == 15 || curDir == 18 || curDir == 21 || curDir == 24 || curDir == 27 || curDir == 26 || curDir == 25 || curDir == 28) {
|
||||
_currentFrame++;
|
||||
} else {
|
||||
_currentFrame += 10;
|
||||
}
|
||||
|
||||
bool terminate = false;
|
||||
|
||||
assert(_currentSequence);
|
||||
|
||||
if (_currentSequence->numFrames <= _currentFrame) {
|
||||
curDir = _directions[_currentDirectionIndex];
|
||||
if (curDir != 0 && curDir != 3 && curDir != 6 && curDir != 9 && curDir != 12 && curDir != 15 && curDir != 18 && curDir != 21) {
|
||||
_currentDirectionIndex++;
|
||||
_currentSequence = _sequences[_directions[_currentDirectionIndex]];
|
||||
}
|
||||
|
||||
_currentFrame = 0;
|
||||
if (_directions[_currentDirectionIndex] == 29) {
|
||||
_frame = nullptr;
|
||||
_currentSequence = nullptr;
|
||||
terminate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (terminate) {
|
||||
return;
|
||||
}
|
||||
|
||||
curDir = _directions[_currentDirectionIndex];
|
||||
if (!curDir || curDir == 3 || curDir == 6 || curDir == 9 || curDir == 12 || curDir == 15 || curDir == 18 || curDir == 21) {
|
||||
switch (curDir) {
|
||||
case 0:
|
||||
_coords.y -= _coordOffset;
|
||||
break;
|
||||
case 3:
|
||||
_coords.x += _coordOffset;
|
||||
_coords.y -= _coordOffset;
|
||||
break;
|
||||
case 6:
|
||||
_coords.x += _coordOffset;
|
||||
break;
|
||||
case 9:
|
||||
_coords.x += _coordOffset;
|
||||
_coords.y += _coordOffset;
|
||||
break;
|
||||
case 12:
|
||||
_coords.y += _coordOffset;
|
||||
break;
|
||||
case 15:
|
||||
_coords.x -= _coordOffset;
|
||||
_coords.y += _coordOffset;
|
||||
break;
|
||||
case 18:
|
||||
_coords.x -= _coordOffset;
|
||||
break;
|
||||
case 21:
|
||||
_coords.x -= _coordOffset;
|
||||
_coords.y -= _coordOffset;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint randNum = rnd(100);
|
||||
if (_coords.x > 465) {
|
||||
if (randNum >= 30) {
|
||||
if (randNum >= 70)
|
||||
setDirection(15);
|
||||
else
|
||||
setDirection(18);
|
||||
} else {
|
||||
setDirection(21);
|
||||
}
|
||||
}
|
||||
|
||||
if (_coords.x < 165) {
|
||||
if (randNum >= 30) {
|
||||
if (randNum >= 70)
|
||||
setDirection(9);
|
||||
else
|
||||
setDirection(6);
|
||||
} else {
|
||||
setDirection(3);
|
||||
}
|
||||
}
|
||||
|
||||
if (_coords.y < 178) {
|
||||
curDir = _directions[_currentDirectionIndex];
|
||||
if (curDir) {
|
||||
if (curDir == 3) {
|
||||
setDirection(25);
|
||||
} else if (curDir == 21) {
|
||||
setDirection(27);
|
||||
}
|
||||
} else {
|
||||
setDirection(26);
|
||||
}
|
||||
}
|
||||
|
||||
if (_coords.y > 354) {
|
||||
curDir = _directions[_currentDirectionIndex];
|
||||
if (curDir == 9 || curDir == 12 || curDir == 15)
|
||||
setDirection(28);
|
||||
}
|
||||
|
||||
curDir = _directions[_currentDirectionIndex];
|
||||
if (curDir == 24 || curDir == 27 || curDir == 26 || curDir == 25 || curDir == 28)
|
||||
_coords.y = -_coords.y;
|
||||
|
||||
_engine->positionSprite(&_currentSequence->sprites[_currentFrame], _coords);
|
||||
|
||||
curDir = _directions[_currentDirectionIndex];
|
||||
if (curDir == 24 || curDir == 27 || curDir == 26 || curDir == 25 || curDir == 28)
|
||||
_coords.y = -_coords.y;
|
||||
|
||||
_engine->getSpriteManager()->drawSprite(&_currentSequence->sprites[_currentFrame]);
|
||||
|
||||
_frame = &_currentSequence->sprites[_currentFrame];
|
||||
}
|
||||
|
||||
void CBeetle::checkMouse() {
|
||||
int16 cursorX;
|
||||
int16 cursorY;
|
||||
int curDir;
|
||||
int scaledDiffY;
|
||||
int16 diffX;
|
||||
int16 diffY;
|
||||
|
||||
cursorX = _engine->_cursorX;
|
||||
cursorY = _engine->_cursorY;
|
||||
curDir = _directions[_currentDirectionIndex];
|
||||
|
||||
if (curDir != 29 && curDir != 27 && curDir != 26 && curDir != 25 && curDir != 28 && curDir != 24 &&
|
||||
!_mouseCooldown && ABS<int16>(cursorX - _coords.x) <= 35) {
|
||||
|
||||
if (ABS<int16>(cursorY - _coords.y) <= 35) {
|
||||
diffX = cursorX - _coords.x;
|
||||
diffY = _coords.y - cursorY;
|
||||
|
||||
if (diffX < 0) {
|
||||
if (diffY <= 0) {
|
||||
scaledDiffY = 100 * diffY;
|
||||
if (scaledDiffY - 41 * diffX <= 0) {
|
||||
if (scaledDiffY - 241 * diffX <= 0) {
|
||||
setDirection(0);
|
||||
} else {
|
||||
setDirection(3);
|
||||
}
|
||||
} else {
|
||||
setDirection(6);
|
||||
}
|
||||
|
||||
if (_coordOffset >= 15) {
|
||||
_mouseCooldown = 10;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
scaledDiffY = 100 * diffY;
|
||||
if (scaledDiffY + 241 * diffX <= 0) {
|
||||
if (scaledDiffY + 41 * diffX <= 0) {
|
||||
setDirection(6);
|
||||
} else {
|
||||
setDirection(9);
|
||||
}
|
||||
} else {
|
||||
setDirection(12);
|
||||
}
|
||||
|
||||
if (_coordOffset >= 15) {
|
||||
_mouseCooldown = 10;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (diffY <= 0) {
|
||||
scaledDiffY = 100 * diffY;
|
||||
if (scaledDiffY + 41 * diffX <= 0) {
|
||||
if (scaledDiffY + 241 * diffX <= 0) {
|
||||
setDirection(0);
|
||||
} else {
|
||||
setDirection(21);
|
||||
}
|
||||
} else {
|
||||
setDirection(18);
|
||||
}
|
||||
|
||||
if (_coordOffset >= 15) {
|
||||
_mouseCooldown = 10;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
scaledDiffY = 100 * diffY;
|
||||
if (scaledDiffY - 241 * diffX <= 0) {
|
||||
if (scaledDiffY - 41 * diffX <= 0) {
|
||||
setDirection(18);
|
||||
} else {
|
||||
setDirection(15);
|
||||
}
|
||||
} else {
|
||||
setDirection(12);
|
||||
}
|
||||
|
||||
if (_coordOffset >= 15) {
|
||||
_mouseCooldown = 10;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_coordOffset = _coordOffset + 4 * (rnd(100)) / 100 + _fleeSpeed;
|
||||
_mouseCooldown = 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CBeetle::setDirection(int direction) {
|
||||
if (_loaded) {
|
||||
if (direction == 27 || direction == 26 || direction == 25 || direction == 28) {
|
||||
_directions[0] = direction;
|
||||
_directions[1] = 29;
|
||||
_currentDirectionIndex = 0;
|
||||
_currentSequence = _sequences[direction];
|
||||
_currentFrame = 0;
|
||||
_index = direction;
|
||||
} else if (_sequences[direction]) {
|
||||
if (direction != _index) {
|
||||
_currentDirectionIndex = 0;
|
||||
|
||||
if (direction == 24) {
|
||||
_directions[0] = 24;
|
||||
_coords.y = 178;
|
||||
|
||||
if (_coords.x >= 265) {
|
||||
_directions[1] = 15;
|
||||
} else {
|
||||
_directions[1] = 9;
|
||||
}
|
||||
|
||||
_currentSequence = _sequences[24];
|
||||
_currentFrame = 0;
|
||||
_index = _directions[1];
|
||||
} else {
|
||||
if (direction <= _index) {
|
||||
for (int i = _index - 1; i > direction; ++_currentDirectionIndex) {
|
||||
_directions[_currentDirectionIndex] = i;
|
||||
i -= 3;
|
||||
}
|
||||
} else {
|
||||
for (int j = _index + 1; j < direction; ++_currentDirectionIndex) {
|
||||
_directions[_currentDirectionIndex] = j;
|
||||
j += 3;
|
||||
}
|
||||
}
|
||||
|
||||
_index = direction;
|
||||
_directions[_currentDirectionIndex] = direction;
|
||||
_currentFrame = 0;
|
||||
_currentDirectionIndex = 0;
|
||||
_currentSequence = _sequences[_directions[0]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CBeetle::onTable() {
|
||||
return _directions[_currentDirectionIndex] != 29;
|
||||
}
|
||||
|
||||
bool CBeetle::click() {
|
||||
if (_engine->getLogicManager()->_activeItem == kItemMatchBox &&
|
||||
_engine->getLogicManager()->cathHasItem(12) &&
|
||||
ABS<int16>(_engine->_cursorX - _coords.x) < 10 &&
|
||||
ABS<int16>(_engine->_cursorY - _coords.y) < 10) {
|
||||
return true;
|
||||
}
|
||||
|
||||
_mouseCooldown = 0;
|
||||
checkMouse();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void LastExpressEngine::doBeetle() {
|
||||
int32 chapter = getLogicManager()->_globals[11];
|
||||
if (chapter >= 2 && chapter <= 3 && !_beetle && getLogicManager()->_items[kItemBeetle].floating == 3) {
|
||||
_beetle = new CBeetle(this);
|
||||
}
|
||||
}
|
||||
|
||||
void LastExpressEngine::endBeetle() {
|
||||
if (_beetle) {
|
||||
delete _beetle;
|
||||
_beetle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void LastExpressEngine::positionSprite(Sprite *sprite, Common::Point coord) {
|
||||
if (sprite) {
|
||||
int spriteWidth = sprite->rect.right - sprite->rect.left + 1;
|
||||
int spriteHeight = sprite->rect.bottom - sprite->rect.top + 1;
|
||||
int widthFactor = sprite->rect.width + 2 * (-640 * sprite->rect.top - sprite->rect.left);
|
||||
int heightFactor = 2 * (sprite->rect.right + 640 * sprite->rect.bottom) - sprite->rect.height;
|
||||
|
||||
if (coord.x > 0)
|
||||
sprite->rect.left = coord.x;
|
||||
|
||||
if (coord.y > 0)
|
||||
sprite->rect.top = coord.y;
|
||||
|
||||
|
||||
sprite->rect.right = sprite->rect.left + spriteWidth - 1;
|
||||
sprite->rect.bottom = sprite->rect.top + spriteHeight - 1;
|
||||
sprite->rect.width = widthFactor + 2 * (sprite->rect.left + 640 * sprite->rect.top);
|
||||
sprite->rect.height = 2 * (sprite->rect.right + 640 * sprite->rect.bottom) - heightFactor;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace LastExpress
|
||||
70
engines/lastexpress/game/beetle.h
Normal file
70
engines/lastexpress/game/beetle.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LASTEXPRESS_BEETLE_H
|
||||
#define LASTEXPRESS_BEETLE_H
|
||||
|
||||
#include "lastexpress/lastexpress.h"
|
||||
|
||||
#include "lastexpress/helpers.h"
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/rect.h"
|
||||
#include "common/system.h"
|
||||
|
||||
namespace LastExpress {
|
||||
|
||||
class LastExpressEngine;
|
||||
struct Seq;
|
||||
struct Sprite;
|
||||
|
||||
class CBeetle {
|
||||
public:
|
||||
CBeetle(LastExpressEngine *engine);
|
||||
~CBeetle();
|
||||
|
||||
void tick();
|
||||
void checkMouse();
|
||||
void setDirection(int direction);
|
||||
bool onTable();
|
||||
bool click();
|
||||
|
||||
private:
|
||||
LastExpressEngine *_engine = nullptr;
|
||||
|
||||
Seq *_sequences[30];
|
||||
Seq *_currentSequence = nullptr;
|
||||
int _currentFrame = 0;
|
||||
int _index = 0;
|
||||
int _coordOffset = 0;
|
||||
Common::Point _coords;
|
||||
int _directions[16];
|
||||
int _currentDirectionIndex = 0;
|
||||
Sprite *_frame = nullptr;
|
||||
bool _loaded = false;
|
||||
int _mouseCooldown = 0;
|
||||
int _fleeSpeed = 0;
|
||||
int _spawnCounter = 0;
|
||||
};
|
||||
|
||||
} // End of namespace LastExpress
|
||||
|
||||
#endif // LASTEXPRESS_BEETLE_H
|
||||
561
engines/lastexpress/game/credits.cpp
Normal file
561
engines/lastexpress/game/credits.cpp
Normal file
@@ -0,0 +1,561 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "lastexpress/lastexpress.h"
|
||||
|
||||
#include "common/memstream.h"
|
||||
|
||||
namespace LastExpress {
|
||||
|
||||
void LastExpressEngine::doCredits() {
|
||||
if (!_doCredits) {
|
||||
TGAHeader creditsTextTga;
|
||||
TGAHeader mapTgas[35];
|
||||
byte *nextTga = nullptr;
|
||||
|
||||
int tgaIndex = 0;
|
||||
int textHeight = 0;
|
||||
int32 scrollSpeed = 0;
|
||||
|
||||
uint16 tgaPalette[256];
|
||||
uint16 mapPalette[256];
|
||||
|
||||
memset(tgaPalette, 0, sizeof(tgaPalette));
|
||||
|
||||
_doCredits = 1;
|
||||
bool oldCanDrawMouse = getGraphicsManager()->canDrawMouse();
|
||||
getGraphicsManager()->setMouseDrawable(false);
|
||||
getGraphicsManager()->burstMouseArea();
|
||||
getGraphicsManager()->stepBG(49);
|
||||
getMemoryManager()->freeFX();
|
||||
getOtisManager()->wipeAllGSysInfo();
|
||||
|
||||
readTGAIntoMemory("credits.tga", &creditsTextTga);
|
||||
|
||||
if (_doCredits)
|
||||
constructPalette(&creditsTextTga, tgaPalette);
|
||||
|
||||
for (int i = 1; _doCredits && i < ARRAYSIZE(mapTgas); i++) {
|
||||
char currentTgaFilename[24];
|
||||
memset(currentTgaFilename, 0, sizeof(currentTgaFilename));
|
||||
|
||||
Common::sprintf_s(currentTgaFilename, "map%05d.tga", i + 1);
|
||||
readTGAIntoMemory(currentTgaFilename, &mapTgas[i]);
|
||||
|
||||
handleEvents();
|
||||
getSoundManager()->soundThread();
|
||||
}
|
||||
|
||||
if (_doCredits) {
|
||||
tgaIndex = 0;
|
||||
textHeight = creditsTextTga.height - 355;
|
||||
nextTga = (creditsTextTga.rawDataPtr + 3 * creditsTextTga.colorMapLength + creditsTextTga.idLength + 18);
|
||||
Slot *cacheSlot = getSoundManager()->_soundCache;
|
||||
|
||||
while (cacheSlot) {
|
||||
if (cacheSlot->hasTag(kSoundTagLink))
|
||||
break;
|
||||
|
||||
cacheSlot = cacheSlot->getNext();
|
||||
}
|
||||
|
||||
if (cacheSlot) {
|
||||
scrollSpeed = (textHeight << 16) / (2 * cacheSlot->getBlockCount()); // Scroll speed based on music length
|
||||
} else {
|
||||
_doCredits = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (_doCredits) {
|
||||
_savedMouseEventHandle = getMessageManager()->getEventHandle(1);
|
||||
_savedTimerEventHandle = getMessageManager()->getEventHandle(3);
|
||||
|
||||
getMessageManager()->setEventHandle(kEventChannelMouse, &LastExpressEngine::creditsMouseWrapper);
|
||||
getMessageManager()->setEventHandle(kEventChannelTimer, &LastExpressEngine::creditsTimerWrapper);
|
||||
|
||||
do {
|
||||
getSoundManager()->soundThread();
|
||||
} while (getMessageManager()->process());
|
||||
|
||||
waitForTimer(5);
|
||||
|
||||
setEventTickInternal(false);
|
||||
|
||||
_savedFrameInterval = getSoundFrameCounter();
|
||||
|
||||
// Main loop
|
||||
while (_doCredits) {
|
||||
do {
|
||||
getSoundManager()->soundThread();
|
||||
} while (_doCredits && getMessageManager()->process());
|
||||
|
||||
waitForTimer(5);
|
||||
|
||||
if (!_savedFrameCounter) {
|
||||
// Handle the background map transition
|
||||
if (_doCredits == 1) {
|
||||
int mapChangeRate = (322 - creditsTextTga.height) / 34;
|
||||
int nextMapIndex = textHeight / mapChangeRate + 34;
|
||||
|
||||
// Switch to a new iteration of the map
|
||||
if (nextMapIndex != tgaIndex && nextMapIndex <= 34) {
|
||||
TGAHeader *currentMap = &mapTgas[nextMapIndex];
|
||||
tgaIndex = nextMapIndex;
|
||||
|
||||
constructPalette(currentMap, mapPalette);
|
||||
|
||||
// 18 is the size of the original TARGA struct
|
||||
byte *mapDataPtr = ¤tMap->rawDataPtr[18] + 3 * currentMap->colorMapLength + currentMap->idLength;
|
||||
|
||||
// Draw the map to the screen
|
||||
if (getGraphicsManager()->acquireSurface()) {
|
||||
PixMap *screenPtr = getGraphicsManager()->_backBuffer + 20496;
|
||||
byte *surfacePtr = (byte *)getGraphicsManager()->_screenSurface.getPixels() + 40992;
|
||||
|
||||
for (int row = 0; row < currentMap->height; row++) {
|
||||
int remainingWidth = currentMap->width;
|
||||
|
||||
// Decompress the RLE image
|
||||
while (remainingWidth > 0) {
|
||||
byte controlByte = *mapDataPtr;
|
||||
byte runLength = (controlByte & 0x7F) + 1;
|
||||
remainingWidth -= runLength;
|
||||
|
||||
if (remainingWidth < 0) {
|
||||
abortCredits();
|
||||
break;
|
||||
}
|
||||
|
||||
mapDataPtr++;
|
||||
|
||||
if ((controlByte & 0x80) == 0) { // Non-repeated sequence
|
||||
for (int i = 0; i < runLength; i++) {
|
||||
if (*mapDataPtr)
|
||||
*screenPtr = mapPalette[*mapDataPtr];
|
||||
screenPtr++;
|
||||
mapDataPtr++;
|
||||
}
|
||||
} else { // Repeated sequence
|
||||
if (*mapDataPtr) { // Non-transparent pixels
|
||||
uint16 paletteValue = mapPalette[*mapDataPtr];
|
||||
for (int i = 0; i < runLength; i++) {
|
||||
*screenPtr++ = paletteValue;
|
||||
}
|
||||
} else { // Transparent pixels (skip)
|
||||
screenPtr += runLength;
|
||||
}
|
||||
|
||||
mapDataPtr++;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy to surface
|
||||
memcpy(surfacePtr, &screenPtr[-currentMap->width], 2 * currentMap->width);
|
||||
surfacePtr += 1280;
|
||||
screenPtr += 640 - currentMap->width;
|
||||
}
|
||||
|
||||
getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
_doCredits = 2; // Signal that a map change occurred
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate scroll position for credits text
|
||||
int scrollPosition = ((scrollSpeed * (getSoundFrameCounter() - _savedFrameInterval)) >> 16) - creditsTextTga.height + textHeight + 355;
|
||||
|
||||
// Scroll the credits...
|
||||
if (scrollPosition >= 1) {
|
||||
if (scrollPosition > textHeight)
|
||||
scrollPosition = textHeight;
|
||||
|
||||
byte *textTgaData = nextTga;
|
||||
|
||||
// Draw credits text to screen...
|
||||
if (getGraphicsManager()->acquireSurface()) {
|
||||
PixMap *surfacePtr = (PixMap *)((byte *)getGraphicsManager()->_screenSurface.getPixels() + 79920);
|
||||
int rowCounter = 355;
|
||||
PixMap *screenPtr = getGraphicsManager()->_backBuffer + 39960;
|
||||
|
||||
while (rowCounter > 0) {
|
||||
memcpy(surfacePtr, screenPtr, 640);
|
||||
int remainingWidth = 320;
|
||||
|
||||
// RLE decompression of credits text
|
||||
while (remainingWidth > 0) {
|
||||
byte controlByte = *textTgaData;
|
||||
byte runLength = (controlByte & 0x7F) + 1;
|
||||
remainingWidth -= runLength;
|
||||
|
||||
if (remainingWidth < 0) {
|
||||
abortCredits();
|
||||
break;
|
||||
}
|
||||
|
||||
textTgaData++;
|
||||
|
||||
if ((controlByte & 0x80) == 0) { // Non-repeated sequence
|
||||
for (int i = 0; i < runLength; i++) {
|
||||
byte colorIndex = *textTgaData;
|
||||
if (colorIndex) {
|
||||
// Apply brightness/fade at top and bottom of screen
|
||||
if (rowCounter <= 339) {
|
||||
if (rowCounter > 16)
|
||||
*surfacePtr = tgaPalette[colorIndex];
|
||||
else
|
||||
*surfacePtr = (getGraphicsManager()->_brightnessData[4 - ((rowCounter - 1) >> 2)] & tgaPalette[colorIndex]) >> (4 - ((rowCounter - 1) >> 2));
|
||||
} else {
|
||||
*surfacePtr = (getGraphicsManager()->_brightnessData[4 - ((355 - rowCounter) >> 2)] & tgaPalette[colorIndex]) >> (4 - ((355 - rowCounter) >> 2));
|
||||
}
|
||||
}
|
||||
++surfacePtr;
|
||||
++textTgaData;
|
||||
}
|
||||
} else { // Repeated sequence
|
||||
byte colorIndex = *textTgaData;
|
||||
if (colorIndex) {
|
||||
// Apply brightness/fade at top and bottom of screen
|
||||
uint16 paletteValue;
|
||||
if (rowCounter <= 339) {
|
||||
if (rowCounter > 16)
|
||||
paletteValue = tgaPalette[colorIndex];
|
||||
else
|
||||
paletteValue = (getGraphicsManager()->_brightnessData[4 - ((rowCounter - 1) >> 2)] & tgaPalette[colorIndex]) >> (4 - ((rowCounter - 1) >> 2));
|
||||
} else {
|
||||
paletteValue = (getGraphicsManager()->_brightnessData[4 - ((355 - rowCounter) >> 2)] & tgaPalette[colorIndex]) >> (4 - ((355 - rowCounter) >> 2));
|
||||
}
|
||||
|
||||
for (int i = 0; i < runLength; i++) {
|
||||
*surfacePtr++ = paletteValue;
|
||||
}
|
||||
} else { // Transparent pixels (skip)
|
||||
surfacePtr += runLength;
|
||||
}
|
||||
++textTgaData;
|
||||
}
|
||||
}
|
||||
|
||||
screenPtr += 640;
|
||||
surfacePtr += 320;
|
||||
--rowCounter;
|
||||
}
|
||||
|
||||
getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
// Blit stuff to screen
|
||||
if (_doCredits == 2) {
|
||||
getGraphicsManager()->burstBox(
|
||||
getGraphicsManager()->_renderBox1.x,
|
||||
getGraphicsManager()->_renderBox1.y,
|
||||
getGraphicsManager()->_renderBox1.width,
|
||||
getGraphicsManager()->_renderBox1.height
|
||||
);
|
||||
|
||||
_doCredits = 1;
|
||||
} else {
|
||||
getGraphicsManager()->burstBox(280, 62, 320, 355);
|
||||
}
|
||||
|
||||
textHeight -= scrollPosition;
|
||||
|
||||
// Check if we have more text to display
|
||||
if (textHeight > 0) {
|
||||
for (int i = 0; i < scrollPosition && textHeight > 0; i++) {
|
||||
int remainingWidth = 320;
|
||||
|
||||
while (remainingWidth > 0) {
|
||||
byte controlByte = *nextTga;
|
||||
byte runLength = (controlByte & 0x7F) + 1;
|
||||
remainingWidth -= runLength;
|
||||
|
||||
if (remainingWidth < 0) {
|
||||
abortCredits();
|
||||
break;
|
||||
}
|
||||
|
||||
nextTga++;
|
||||
|
||||
if ((controlByte & 0x80) == 0) // Non-repeated sequence
|
||||
nextTga += runLength;
|
||||
else // Repeated sequence
|
||||
nextTga++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
abortCredits();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean-up and exit...
|
||||
getGraphicsManager()->setMouseDrawable(oldCanDrawMouse);
|
||||
|
||||
getGraphicsManager()->burstMouseArea();
|
||||
Slot *cacheSlot = getSoundManager()->_soundCache;
|
||||
if (cacheSlot) {
|
||||
do {
|
||||
if (cacheSlot->getTag() == kSoundTagLink)
|
||||
break;
|
||||
|
||||
cacheSlot = cacheSlot->getNext();
|
||||
} while (cacheSlot);
|
||||
|
||||
if (cacheSlot && cacheSlot->getBlockCount())
|
||||
cacheSlot->setFade(0);
|
||||
}
|
||||
|
||||
getLogicManager()->fadeToBlack();
|
||||
|
||||
if (nextTga)
|
||||
getMemoryManager()->lockFX();
|
||||
|
||||
getMessageManager()->setEventHandle(kEventChannelMouse, _savedMouseEventHandle);
|
||||
getMessageManager()->setEventHandle(kEventChannelTimer, _savedTimerEventHandle);
|
||||
|
||||
_doCredits = 0;
|
||||
|
||||
if (creditsTextTga.rawDataPtr) {
|
||||
free(creditsTextTga.rawDataPtr);
|
||||
creditsTextTga.rawDataPtr = nullptr;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 35; i++) {
|
||||
if (mapTgas[i].rawDataPtr) {
|
||||
free(mapTgas[i].rawDataPtr);
|
||||
mapTgas[i].rawDataPtr = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LastExpressEngine::abortCredits() {
|
||||
_doCredits = 0;
|
||||
}
|
||||
|
||||
void LastExpressEngine::creditsMouse(Event *event) {
|
||||
_cursorX = event->x;
|
||||
_cursorY = event->y;
|
||||
|
||||
mouseSetRightClicked(false);
|
||||
|
||||
if ((event->flags & kMouseFlagLeftDown) != 0)
|
||||
_savedFrameCounter = getSoundFrameCounter();
|
||||
|
||||
if (_savedFrameCounter && (event->flags & kMouseFlagLeftUp) != 0) {
|
||||
_savedFrameInterval += getSoundFrameCounter() - _savedFrameCounter;
|
||||
_savedFrameCounter = 0;
|
||||
}
|
||||
|
||||
if ((event->flags & kMouseFlagRightDown) != 0)
|
||||
abortCredits();
|
||||
}
|
||||
|
||||
void LastExpressEngine::creditsTimer(Event *event) {
|
||||
setEventTickInternal(false);
|
||||
}
|
||||
|
||||
int32 LastExpressEngine::readTGAIntoMemory(const char *filename, TGAHeader *tgaHeader) {
|
||||
int32 fileSize = 0;
|
||||
|
||||
HPF *archive = getArchiveManager()->openHPF(filename);
|
||||
if (!archive) {
|
||||
warning("Error opening file \"%s\". It probably doesn\'t exist or is write protected.", filename);
|
||||
abortCredits();
|
||||
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
if (_doCredits) {
|
||||
fileSize = archive->size * MEM_PAGE_SIZE;
|
||||
|
||||
byte *tgaRawData = (byte *)malloc(fileSize);
|
||||
|
||||
assert(tgaRawData);
|
||||
|
||||
getArchiveManager()->readHPF(archive, tgaRawData, archive->size);
|
||||
|
||||
Common::SeekableReadStream *tgaReadStream = new Common::MemoryReadStream(tgaRawData, fileSize, DisposeAfterUse::NO);
|
||||
|
||||
tgaHeader->idLength = tgaReadStream->readByte();
|
||||
tgaHeader->colorMapType = tgaReadStream->readByte();
|
||||
tgaHeader->imageType = tgaReadStream->readByte();
|
||||
tgaHeader->colorMapFirstEntryIndex = tgaReadStream->readUint16LE();
|
||||
tgaHeader->colorMapLength = tgaReadStream->readUint16LE();
|
||||
tgaHeader->colorMapEntrySize = tgaReadStream->readByte();
|
||||
tgaHeader->xOrigin = tgaReadStream->readUint16LE();
|
||||
tgaHeader->yOrigin = tgaReadStream->readUint16LE();
|
||||
tgaHeader->width = tgaReadStream->readUint16LE();
|
||||
tgaHeader->height = tgaReadStream->readUint16LE();
|
||||
tgaHeader->bitsPerPixel = tgaReadStream->readByte();
|
||||
tgaHeader->imageDescriptor = tgaReadStream->readByte();
|
||||
tgaHeader->rawDataPtr = tgaRawData;
|
||||
|
||||
delete tgaReadStream;
|
||||
|
||||
getArchiveManager()->closeHPF(archive);
|
||||
|
||||
if ((tgaHeader->imageDescriptor & 0x10) != 0)
|
||||
abortCredits();
|
||||
|
||||
if (tgaHeader->bitsPerPixel != 8)
|
||||
abortCredits();
|
||||
|
||||
if (!tgaHeader->colorMapType)
|
||||
abortCredits();
|
||||
|
||||
if (tgaHeader->colorMapEntrySize != 24)
|
||||
abortCredits();
|
||||
|
||||
if ((tgaHeader->colorMapFirstEntryIndex >= 256 || tgaHeader->colorMapLength > 256 - tgaHeader->colorMapFirstEntryIndex)) {
|
||||
abortCredits();
|
||||
}
|
||||
}
|
||||
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
void LastExpressEngine::constructPalette(TGAHeader *tgaHeader, uint16 *palette) {
|
||||
uint16 *paletteEntry;
|
||||
|
||||
byte *colorMapData = tgaHeader->rawDataPtr + 18 + tgaHeader->idLength;
|
||||
int currentIndex = tgaHeader->colorMapFirstEntryIndex;
|
||||
|
||||
if (currentIndex + tgaHeader->colorMapLength > currentIndex) {
|
||||
paletteEntry = &palette[currentIndex];
|
||||
|
||||
// Read BGR components (as the TGA format stores them)...
|
||||
do {
|
||||
// The component are reduced to 5 bits...
|
||||
byte blue = colorMapData[0] >> 3;
|
||||
byte green = colorMapData[1] >> 3;
|
||||
byte red = colorMapData[2] >> 3;
|
||||
|
||||
colorMapData += 3;
|
||||
|
||||
// Construct 16-bit color value in RGB555 format
|
||||
*paletteEntry = (red << 10) | (green << 5) | blue;
|
||||
|
||||
paletteEntry++;
|
||||
currentIndex++;
|
||||
} while (tgaHeader->colorMapLength + tgaHeader->colorMapFirstEntryIndex > currentIndex);
|
||||
}
|
||||
|
||||
getGraphicsManager()->modifyPalette(palette, 256);
|
||||
}
|
||||
|
||||
bool LastExpressEngine::demoEnding(bool wonGame) {
|
||||
bool exitFlag = false;
|
||||
int frameIndex = 0;
|
||||
bool savedMouseState = getGraphicsManager()->canDrawMouse();
|
||||
|
||||
const char backgroundNames[35][9] = {
|
||||
"CROSSING", "ABBOT", "ANCATH", "MORNING", "GUNS", "DRINKUP", "SERBSRES",
|
||||
"ANALXR13", "TATIANA", "KRONAN", "CONCERT", "AUDIENCE", "COUPLE", "RUSSIANS",
|
||||
"SPIES", "1017DOG", "CARRIAGE", "TYLEREGG", "TRNM2", "MAHMUD", "CATHMIL",
|
||||
"FRANCY", "ONROOF", "COPS2", "MILOSVES", "KAHINGUN", "1041KISS", "EVERYONE",
|
||||
"BONDAGE", "KILL", "HIGHFITE", "1315GUNS", "BOOM2", "ISTANBUL", "LASTSHOT"};
|
||||
|
||||
if (wonGame) {
|
||||
getMessageManager()->setEventHandle(kEventChannelMouse, &LastExpressEngine::emptyHandler);
|
||||
} else {
|
||||
getMessageManager()->setEventHandle(kEventChannelMouse, &LastExpressEngine::demoEndingMouseWrapper);
|
||||
}
|
||||
|
||||
getMessageManager()->setEventHandle(kEventChannelTimer, &LastExpressEngine::demoEndingTimerWrapper);
|
||||
getGraphicsManager()->setMouseDrawable(false);
|
||||
|
||||
mouseSetRightClicked(false);
|
||||
|
||||
if (getGraphicsManager()->acquireSurface()) {
|
||||
getGraphicsManager()->clear(getGraphicsManager()->_screenSurface, 0, 0, 640, 480);
|
||||
getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
getGraphicsManager()->burstAll();
|
||||
getSoundManager()->playSoundFile("MUSSELL.SND", kSoundTypeNIS | kVolumeFull, 0, 0);
|
||||
|
||||
while (!exitFlag && frameIndex < 35) {
|
||||
Slot *soundSlot = getSoundManager()->_soundCache;
|
||||
int frameDuration = 180;
|
||||
|
||||
if (soundSlot) {
|
||||
while (soundSlot && soundSlot->getTag() != kSoundTagNIS)
|
||||
soundSlot = soundSlot->getNext();
|
||||
|
||||
if (soundSlot)
|
||||
frameDuration = 2 * soundSlot->getBlockCount() / (35 - frameIndex);
|
||||
}
|
||||
|
||||
int targetTime = getSoundFrameCounter() + frameDuration;
|
||||
int bgResult = getArchiveManager()->loadBG(backgroundNames[frameIndex]);
|
||||
|
||||
if (bgResult < 0) {
|
||||
exitFlag = true;
|
||||
break;
|
||||
}
|
||||
|
||||
TBM *renderBox = (bgResult == 0) ? &getGraphicsManager()->_renderBox1 : &getGraphicsManager()->_renderBox2;
|
||||
getGraphicsManager()->stepDissolve(renderBox);
|
||||
|
||||
while (getSoundFrameCounter() < targetTime && !exitFlag) {
|
||||
if (wonGame) {
|
||||
if (mouseHasRightClicked()) {
|
||||
exitFlag = true;
|
||||
}
|
||||
} else {
|
||||
if (getMenu()->getEggTimerDelta())
|
||||
exitFlag = true;
|
||||
}
|
||||
|
||||
if (!exitFlag) {
|
||||
bool haveMoreMessages = getMessageManager()->process();
|
||||
getSoundManager()->soundThread();
|
||||
getSubtitleManager()->subThread();
|
||||
|
||||
// Only wait and handle events if we've processed all messages, unlike the original which had a separate thread for input...
|
||||
if (!haveMoreMessages)
|
||||
waitForTimer(5);
|
||||
}
|
||||
}
|
||||
|
||||
frameIndex++;
|
||||
}
|
||||
|
||||
getGraphicsManager()->setMouseDrawable(savedMouseState);
|
||||
getMenu()->setEggTimerDelta(DEMO_TIMEOUT);
|
||||
|
||||
return exitFlag;
|
||||
}
|
||||
|
||||
void LastExpressEngine::demoEndingMouse(Event *event) {
|
||||
if (event->flags || ABS<int32>((int32)event->x - _cursorX) > 5 || ABS<int32>((int32)event->y - _cursorY) > 5)
|
||||
getMenu()->setEggTimerDelta(DEMO_TIMEOUT);
|
||||
}
|
||||
|
||||
void LastExpressEngine::demoEndingTimer(Event *event) {
|
||||
setEventTickInternal(false);
|
||||
}
|
||||
|
||||
} // End of namespace LastExpress
|
||||
332
engines/lastexpress/game/events.cpp
Normal file
332
engines/lastexpress/game/events.cpp
Normal file
@@ -0,0 +1,332 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "lastexpress/lastexpress.h"
|
||||
#include "lastexpress/game/events.h"
|
||||
|
||||
namespace LastExpress {
|
||||
|
||||
MessageManager::MessageManager(LastExpressEngine *engine) {
|
||||
_engine = engine;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
_eventHandles[i] = nullptr;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 40; i++) {
|
||||
_messageHandles[i] = nullptr;
|
||||
}
|
||||
|
||||
_autoMessages = nullptr;
|
||||
}
|
||||
|
||||
MessageManager::~MessageManager() {
|
||||
free(_autoMessages);
|
||||
_autoMessages = nullptr;
|
||||
}
|
||||
|
||||
void MessageManager::setMessageHandle(int handleChannel, void (LogicManager::*handle)(Message *)) {
|
||||
_messageHandles[handleChannel] = handle;
|
||||
}
|
||||
|
||||
void (LogicManager::*MessageManager::getMessageHandle(int handleChannel))(Message *) {
|
||||
return _messageHandles[handleChannel];
|
||||
}
|
||||
|
||||
void MessageManager::setEventHandle(int handleChannel, void (LastExpressEngine::*handle)(Event *)) {
|
||||
_eventHandles[handleChannel] = handle;
|
||||
}
|
||||
|
||||
void (LastExpressEngine::*MessageManager::getEventHandle(int handleChannel))(Event *) {
|
||||
return _eventHandles[handleChannel];
|
||||
}
|
||||
|
||||
void MessageManager::addEvent(int channel, int x, int y, int flags) {
|
||||
Common::StackLock lock(*_engine->_soundMutex);
|
||||
|
||||
if (_numEventsInQueue >= 127)
|
||||
return;
|
||||
|
||||
if (channel == kEventChannelTimer) {
|
||||
_engine->setEventTickInternal(true);
|
||||
} else if (channel == kEventChannelMouse) {
|
||||
if ((flags & kMouseFlagLeftButton) != 0) {
|
||||
// Originally _engine->mouseSetLeftClicked(true); was called from here,
|
||||
// but it's been moved under the "if" because this lead to fake double
|
||||
// clicks when registering mouse movement events (which are re-paired
|
||||
// with RMOUSE and LMOUSE flags when sent to the engine via this function).
|
||||
if (!_systemEventLeftMouseDown) {
|
||||
_engine->mouseSetLeftClicked(true);
|
||||
flags |= kMouseFlagLeftDown;
|
||||
_systemEventLeftMouseDown = true;
|
||||
|
||||
if (_doubleClickMaxFrames + _latestTickLeftMousePressed > _engine->getSoundFrameCounter())
|
||||
flags |= (kMouseFlagDoubleClick | kMouseFlagLeftDown);
|
||||
|
||||
_latestTickLeftMousePressed = _engine->getSoundFrameCounter();
|
||||
}
|
||||
} else {
|
||||
if (_systemEventLeftMouseDown)
|
||||
flags |= kMouseFlagLeftUp;
|
||||
|
||||
_systemEventLeftMouseDown = false;
|
||||
}
|
||||
|
||||
if ((flags & kMouseFlagRightButton) != 0) {
|
||||
// Originally _engine->mouseSetRightClicked(true); was called from here,
|
||||
// but it's been moved under the "if" because this lead to fake double
|
||||
// clicks when registering mouse movement events (which are re-paired
|
||||
// with RMOUSE and LMOUSE flags when sent to the engine via this function).
|
||||
if (!_systemEventRightMouseDown) {
|
||||
_engine->mouseSetRightClicked(true);
|
||||
flags |= kMouseFlagRightDown;
|
||||
_systemEventRightMouseDown = true;
|
||||
}
|
||||
} else {
|
||||
if (_systemEventRightMouseDown)
|
||||
flags |= kMouseFlagRightUp;
|
||||
|
||||
_systemEventRightMouseDown = false;
|
||||
}
|
||||
|
||||
// Originally "if (!flags)"; this tames slowdowns when dragging the mouse
|
||||
// with one of the buttons pressed. Hopefully it doesn't break anything...
|
||||
if (!flags || ((flags & kMouseFlagLeftButton) != 0 || (flags & kMouseFlagRightButton) != 0)) {
|
||||
if (_lastEventIndex != 128)
|
||||
_events[_lastEventIndex].channel = 0;
|
||||
|
||||
_lastEventIndex = _curEventIndex;
|
||||
}
|
||||
}
|
||||
|
||||
_events[_curEventIndex].channel = channel;
|
||||
_events[_curEventIndex].x = x;
|
||||
_events[_curEventIndex].y = y;
|
||||
_events[_curEventIndex].flags = flags;
|
||||
|
||||
_curEventIndex++;
|
||||
_numEventsInQueue++;
|
||||
_curEventIndex &= 0x7F;
|
||||
}
|
||||
|
||||
Event *MessageManager::getEvent() {
|
||||
Event *result = nullptr;
|
||||
|
||||
Common::StackLock lock(*_engine->_soundMutex);
|
||||
|
||||
if (_nextEventIndex == _curEventIndex) {
|
||||
// No events in queue...
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Wrap around the circular buffer...
|
||||
if (_lastEventIndex == _nextEventIndex) {
|
||||
_lastEventIndex = 128;
|
||||
}
|
||||
|
||||
result = &_events[_nextEventIndex];
|
||||
|
||||
// Update event queue indexes...
|
||||
_numEventsInQueue--;
|
||||
_nextEventIndex++;
|
||||
_nextEventIndex &= 0x7F; // Wrap around the circular buffer...
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void MessageManager::addMessage(int receiver, int actionId, int sender, ConsCallParam param) {
|
||||
if (_numMsgsInQueue < 127) {
|
||||
Message *newMsg = &_messages[_curMsgIndex];
|
||||
newMsg->receiver = receiver;
|
||||
newMsg->action = actionId;
|
||||
newMsg->sender = sender;
|
||||
newMsg->param = param;
|
||||
|
||||
_curMsgIndex++;
|
||||
_numMsgsInQueue++;
|
||||
_curMsgIndex &= 0x7F;
|
||||
}
|
||||
}
|
||||
|
||||
Message *MessageManager::getMessage() {
|
||||
if (_nextMsgIndex == _curMsgIndex)
|
||||
return nullptr;
|
||||
|
||||
Message *msg = &_messages[_nextMsgIndex];
|
||||
|
||||
_nextMsgIndex = (_nextMsgIndex + 1) & 0x7F;
|
||||
_numMsgsInQueue--;
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
bool MessageManager::process() {
|
||||
Event *event;
|
||||
|
||||
event = getEvent();
|
||||
|
||||
if (!event || event->channel >= 16)
|
||||
return false;
|
||||
|
||||
if (_eventHandles[event->channel])
|
||||
(_engine->*_eventHandles[event->channel])(event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MessageManager::flush() {
|
||||
Message *message;
|
||||
|
||||
do {
|
||||
message = getMessage();
|
||||
if (!message)
|
||||
break;
|
||||
|
||||
if (!_engine->getLogicManager()->doAutoMessage(message)) {
|
||||
if (_messageHandles[message->receiver])
|
||||
(_engine->getLogicManager()->*_messageHandles[message->receiver])(message);
|
||||
}
|
||||
|
||||
} while (message && _engine->_navigationEngineIsRunning);
|
||||
}
|
||||
|
||||
void MessageManager::flushTime() {
|
||||
for (int i = 1; i < ARRAYSIZE(_messageHandles) && _engine->_navigationEngineIsRunning; i++) {
|
||||
if (_messageHandles[i])
|
||||
(_engine->getLogicManager()->*_messageHandles[i])(&_emptyMessage);
|
||||
}
|
||||
|
||||
if (_engine->_navigationEngineIsRunning)
|
||||
flush();
|
||||
}
|
||||
|
||||
void MessageManager::forceMessage(Message *msg) {
|
||||
if (_messageHandles[msg->receiver])
|
||||
(_engine->getLogicManager()->*_messageHandles[msg->receiver])(msg);
|
||||
}
|
||||
|
||||
void MessageManager::clearMessageQueue() {
|
||||
Common::StackLock lock(*_engine->_soundMutex);
|
||||
_numMsgsInQueue = 0;
|
||||
_nextMsgIndex = 0;
|
||||
_curMsgIndex = 0;
|
||||
}
|
||||
|
||||
void MessageManager::clearEventQueue() {
|
||||
Common::StackLock lock(*_engine->_soundMutex);
|
||||
_numEventsInQueue = 0;
|
||||
_nextEventIndex = 0;
|
||||
_curEventIndex = 0;
|
||||
_engine->setEventTickInternal(false);
|
||||
_engine->mouseSetLeftClicked(false);
|
||||
_engine->mouseSetRightClicked(false);
|
||||
}
|
||||
|
||||
void MessageManager::clearClickEvents() {
|
||||
Common::StackLock lock(*_engine->_soundMutex);
|
||||
if (_numEventsInQueue) {
|
||||
for (int i = _nextEventIndex; _curEventIndex != i; i = ((i + 1) & 0x7F)) {
|
||||
if (_events[i].channel == 1 && _events[i].flags)
|
||||
_events[i].flags = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageManager::saveMessages(CVCRFile *file) {
|
||||
byte *msgData = (byte *)malloc(16 * 128);
|
||||
assert(msgData);
|
||||
|
||||
for (int i = 0; i < 128; i++) {
|
||||
WRITE_LE_INT32(msgData + (i * 16) + 0, _autoMessages[i].receiver);
|
||||
WRITE_LE_INT32(msgData + (i * 16) + 4, _autoMessages[i].action);
|
||||
WRITE_LE_INT32(msgData + (i * 16) + 8, _autoMessages[i].sender);
|
||||
WRITE_LE_INT32(msgData + (i * 16) + 12, _autoMessages[i].param.intParam);
|
||||
}
|
||||
|
||||
file->writeRLE(msgData, 16, 128);
|
||||
file->writeRLE(&_numMsgsInQueue, 4, 1);
|
||||
|
||||
free(msgData);
|
||||
msgData = nullptr;
|
||||
|
||||
if (_numMsgsInQueue) {
|
||||
int nextIdx = _nextMsgIndex;
|
||||
|
||||
while (_curMsgIndex != nextIdx) {
|
||||
Message *msgToSave = &_messages[nextIdx];
|
||||
|
||||
msgData = (byte *)malloc(16);
|
||||
assert(msgData);
|
||||
|
||||
WRITE_LE_INT32(msgData + 0, msgToSave->receiver);
|
||||
WRITE_LE_INT32(msgData + 4, msgToSave->action);
|
||||
WRITE_LE_INT32(msgData + 8, msgToSave->sender);
|
||||
WRITE_LE_INT32(msgData + 12, msgToSave->param.intParam);
|
||||
|
||||
nextIdx = (nextIdx + 1) & 0x7F;
|
||||
file->writeRLE(msgData, 16, 1);
|
||||
|
||||
free(msgData);
|
||||
msgData = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageManager::loadMessages(CVCRFile *file) {
|
||||
byte *msgData = (byte *)malloc(16 * 128);
|
||||
assert(msgData);
|
||||
|
||||
file->readRLE(msgData, 16, 128);
|
||||
|
||||
for (int i = 0; i < 128; i++) {
|
||||
_autoMessages[i].receiver = READ_LE_INT32(msgData + (i * 16) + 0);
|
||||
_autoMessages[i].action = READ_LE_INT32(msgData + (i * 16) + 4);
|
||||
_autoMessages[i].sender = READ_LE_INT32(msgData + (i * 16) + 8);
|
||||
_autoMessages[i].param.intParam = READ_LE_INT32(msgData + (i * 16) + 12);
|
||||
_autoMessages[i].param.stringParam = nullptr;
|
||||
}
|
||||
|
||||
free(msgData);
|
||||
msgData = nullptr;
|
||||
|
||||
int numMsgsInQueue;
|
||||
file->readRLE(&numMsgsInQueue, 4, 1);
|
||||
|
||||
Message loadedMsg;
|
||||
for (int i = 0; i < numMsgsInQueue; i++) {
|
||||
msgData = (byte *)malloc(16);
|
||||
assert(msgData);
|
||||
|
||||
file->readRLE(msgData, 16, 1);
|
||||
|
||||
loadedMsg.receiver = READ_LE_INT32(msgData + 0);
|
||||
loadedMsg.action = READ_LE_INT32(msgData + 4);
|
||||
loadedMsg.sender = READ_LE_INT32(msgData + 8);
|
||||
loadedMsg.param.intParam = READ_LE_INT32(msgData + 12);
|
||||
loadedMsg.param.stringParam = nullptr;
|
||||
|
||||
addMessage(loadedMsg.receiver, loadedMsg.action, loadedMsg.sender, loadedMsg.param);
|
||||
free(msgData);
|
||||
msgData = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace LastExpress
|
||||
128
engines/lastexpress/game/events.h
Normal file
128
engines/lastexpress/game/events.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LASTEXPRESS_EVENTS_H
|
||||
#define LASTEXPRESS_EVENTS_H
|
||||
|
||||
#include "lastexpress/lastexpress.h"
|
||||
|
||||
namespace LastExpress {
|
||||
|
||||
class LastExpressEngine;
|
||||
class LogicManager;
|
||||
|
||||
struct ConsCallParam {
|
||||
ConsCallParam() : intParam(0), stringParam(nullptr) {}
|
||||
|
||||
template<typename T>
|
||||
ConsCallParam(T param) : intParam(static_cast<int32>(param)), stringParam(nullptr) {}
|
||||
|
||||
ConsCallParam(const char *param) : intParam(0), stringParam(param) {}
|
||||
ConsCallParam(char *param) : intParam(0), stringParam(param) {}
|
||||
|
||||
int32 intParam;
|
||||
const char *stringParam;
|
||||
};
|
||||
|
||||
typedef struct Message {
|
||||
int receiver;
|
||||
int action;
|
||||
int sender;
|
||||
ConsCallParam param;
|
||||
|
||||
Message() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
receiver = 0;
|
||||
action = 0;
|
||||
sender = 0;
|
||||
}
|
||||
} Message;
|
||||
|
||||
typedef struct Event {
|
||||
int channel;
|
||||
int x;
|
||||
int y;
|
||||
int flags;
|
||||
|
||||
Event() {
|
||||
channel = 0;
|
||||
x = 0;
|
||||
y = 0;
|
||||
flags = 0;
|
||||
}
|
||||
} Event;
|
||||
|
||||
class MessageManager {
|
||||
friend class LastExpressEngine;
|
||||
friend class LogicManager;
|
||||
friend class MemoryManager;
|
||||
|
||||
public:
|
||||
MessageManager(LastExpressEngine *engine);
|
||||
~MessageManager();
|
||||
|
||||
void setMessageHandle(int handleChannel, void (LogicManager::*handle)(Message *));
|
||||
void (LogicManager::*getMessageHandle(int handleChannel))(Message *);
|
||||
void setEventHandle(int handleChannel, void (LastExpressEngine::*handle)(Event *));
|
||||
void (LastExpressEngine::*getEventHandle(int handleChannel))(Event *);
|
||||
void addEvent(int channel, int x, int y, int flags);
|
||||
Event *getEvent();
|
||||
void addMessage(int receiver, int actionId, int sender, ConsCallParam param);
|
||||
Message *getMessage();
|
||||
bool process();
|
||||
void flush();
|
||||
void flushTime();
|
||||
void forceMessage(Message *msg);
|
||||
void clearMessageQueue();
|
||||
void clearEventQueue();
|
||||
void clearClickEvents();
|
||||
void saveMessages(CVCRFile *file);
|
||||
void loadMessages(CVCRFile *file);
|
||||
|
||||
private:
|
||||
LastExpressEngine *_engine = nullptr;
|
||||
|
||||
Message _messages[128];
|
||||
Message _emptyMessage;
|
||||
void (LogicManager::*_messageHandles[40])(Message *);
|
||||
int _curMsgIndex = 0;
|
||||
int _numMsgsInQueue = 0;
|
||||
int _nextMsgIndex = 0;
|
||||
Message *_autoMessages = nullptr;
|
||||
|
||||
Event _events[128];
|
||||
void (LastExpressEngine::*_eventHandles[16])(Event *);
|
||||
int _doubleClickMaxFrames = 30;
|
||||
int _latestTickLeftMousePressed = 0;
|
||||
int _lastEventIndex = 128;
|
||||
int _curEventIndex = 0;
|
||||
int _numEventsInQueue = 0;
|
||||
int _nextEventIndex = 0;
|
||||
bool _systemEventRightMouseDown = false;
|
||||
bool _systemEventLeftMouseDown = false;
|
||||
};
|
||||
|
||||
} // End of namespace LastExpress
|
||||
|
||||
#endif // LASTEXPRESS_EVENTS_H
|
||||
3581
engines/lastexpress/game/logic.cpp
Normal file
3581
engines/lastexpress/game/logic.cpp
Normal file
File diff suppressed because it is too large
Load Diff
6256
engines/lastexpress/game/logic.h
Normal file
6256
engines/lastexpress/game/logic.h
Normal file
File diff suppressed because it is too large
Load Diff
1119
engines/lastexpress/game/navigation.cpp
Normal file
1119
engines/lastexpress/game/navigation.cpp
Normal file
File diff suppressed because it is too large
Load Diff
978
engines/lastexpress/game/nis.cpp
Normal file
978
engines/lastexpress/game/nis.cpp
Normal file
@@ -0,0 +1,978 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "lastexpress/lastexpress.h"
|
||||
#include "lastexpress/game/nis.h"
|
||||
#include "lastexpress/data/archive.h"
|
||||
|
||||
namespace LastExpress {
|
||||
|
||||
NISManager::NISManager(LastExpressEngine *engine) {
|
||||
_engine = engine;
|
||||
|
||||
_background1 = new NisSprite();
|
||||
_background2 = new NisSprite();
|
||||
_waneSprite = new NisSprite();
|
||||
_waxSprite = new NisSprite();
|
||||
}
|
||||
|
||||
NISManager::~NISManager() {
|
||||
SAFE_DELETE(_background1);
|
||||
SAFE_DELETE(_background2);
|
||||
SAFE_DELETE(_waneSprite);
|
||||
SAFE_DELETE(_waxSprite);
|
||||
SAFE_DELETE_ARR(_events);
|
||||
}
|
||||
|
||||
void NISManager::clearBounds() {
|
||||
_nisRect.left = 640;
|
||||
_nisRect.top = 480;
|
||||
_nisRect.right = 0;
|
||||
_nisRect.bottom = 0;
|
||||
}
|
||||
|
||||
void NISManager::addBounds(Extent extent) {
|
||||
if (_nisRect.left > (int16)extent.left)
|
||||
_nisRect.left = (int16)extent.left;
|
||||
|
||||
if (_nisRect.right < (int16)extent.right)
|
||||
_nisRect.right = (int16)extent.right;
|
||||
|
||||
if (_nisRect.top > (int16)extent.top)
|
||||
_nisRect.top = (int16)extent.top;
|
||||
|
||||
if (_nisRect.bottom < (int16)extent.bottom)
|
||||
_nisRect.bottom = (int16)extent.bottom;
|
||||
}
|
||||
|
||||
void NISManager::convertNSPR16(byte *spriteData, NisSprite *outSprite) {
|
||||
uint32 compDataOffset = READ_LE_UINT32(spriteData);
|
||||
uint32 eraseMaskOffset = READ_LE_UINT32(spriteData + 4);
|
||||
uint32 colorPaletteOffset = READ_LE_UINT32(spriteData + 8);
|
||||
|
||||
outSprite->rect.left = READ_LE_INT32(spriteData + 12);
|
||||
outSprite->rect.top = READ_LE_INT32(spriteData + 16);
|
||||
outSprite->rect.right = READ_LE_INT32(spriteData + 20);
|
||||
outSprite->rect.bottom = READ_LE_INT32(spriteData + 24);
|
||||
outSprite->rect.width = READ_LE_INT32(spriteData + 28);
|
||||
outSprite->rect.height = READ_LE_INT32(spriteData + 32);
|
||||
|
||||
outSprite->colorPalette = (uint16 *)&spriteData[colorPaletteOffset];
|
||||
|
||||
WRITE_LE_UINT16(&outSprite->colorPalette[0], 0);
|
||||
WRITE_LE_UINT16(&outSprite->colorPalette[1], 0);
|
||||
|
||||
for (int i = 0; i < 128; ++i)
|
||||
outSprite->colorPalette[i] = READ_LE_UINT16(&outSprite->colorPalette[i]);
|
||||
|
||||
_engine->getGraphicsManager()->modifyPalette((uint16 *)outSprite->colorPalette, 128);
|
||||
|
||||
for (int i = 0; i < 128; i++)
|
||||
outSprite->gammaPalette[i] = READ_UINT16(spriteData + 2 * i + 36);
|
||||
|
||||
outSprite->compBits = spriteData[2 * 128 + 36];
|
||||
|
||||
outSprite->compData = &spriteData[compDataOffset];
|
||||
|
||||
if (eraseMaskOffset)
|
||||
outSprite->eraseMask = &spriteData[eraseMaskOffset];
|
||||
}
|
||||
|
||||
void NISManager::getStream(byte *data, int32 size) {
|
||||
if (size > _remainingStreamBytes) {
|
||||
Slot *slot;
|
||||
|
||||
for (slot = _engine->getSoundManager()->_soundCache; slot; slot = slot->getNext()) {
|
||||
if (!slot->hasTag(kSoundTagNIS))
|
||||
break;
|
||||
}
|
||||
|
||||
if (slot && _currentNISSound)
|
||||
_currentNISSound->addStatusFlag(kSoundFlagPauseRequested);
|
||||
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
if ((_flags & kNisFlagDataChunksAvailable) != 0)
|
||||
loadChunk(32);
|
||||
}
|
||||
|
||||
getStream(data, size);
|
||||
|
||||
if (_currentNISSound)
|
||||
_currentNISSound->removeStatusFlag(kSoundFlagPauseRequested | kSoundFlagPaused);
|
||||
} else if (size + _streamCurrentPosition <= _streamBufferSize) {
|
||||
memcpy(data, (byte *)_backgroundSurface + _streamCurrentPosition, size);
|
||||
|
||||
_streamCurrentPosition += size;
|
||||
_remainingStreamBytes -= size;
|
||||
|
||||
if (_streamCurrentPosition >= _streamBufferSize)
|
||||
_streamCurrentPosition = 0;
|
||||
} else {
|
||||
int32 prevSize = _streamBufferSize - _streamCurrentPosition;
|
||||
getStream(data, _streamBufferSize - _streamCurrentPosition);
|
||||
getStream(&data[prevSize], size - prevSize);
|
||||
}
|
||||
}
|
||||
|
||||
void NISManager::loadSnd(int32 size) {
|
||||
byte *currentBufferPtr = _currentNISSound->getCurrentBufferPtr();
|
||||
byte *dataEnd = _currentNISSound->getDataEnd();
|
||||
int32 availableSize = dataEnd - currentBufferPtr;
|
||||
|
||||
if (size < dataEnd - currentBufferPtr) {
|
||||
getStream(_currentNISSound->getCurrentBufferPtr(), size);
|
||||
_currentNISSound->advanceCurrentBufferPtrBy(size);
|
||||
_currentNISSound->advanceLoadedBytesBy(size);
|
||||
} else {
|
||||
getStream(_currentNISSound->getCurrentBufferPtr(), dataEnd - currentBufferPtr);
|
||||
_currentNISSound->advanceCurrentBufferPtrBy(availableSize);
|
||||
_currentNISSound->setCurrentBufferPtr(_currentNISSound->getDataStart());
|
||||
loadSnd(size - availableSize);
|
||||
}
|
||||
|
||||
if (_currentNISSound->getCurrentDataPtr() < _currentNISSound->getDataStart() + 88064)
|
||||
memcpy(_currentNISSound->getDataEnd(), _currentNISSound->getDataStart(), NIS_SOUND_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
int NISManager::loadChunk(int32 size) {
|
||||
int32 sizeToLoad = _totalStreamPages - ((_remainingStreamBytes + 2047) / MEM_PAGE_SIZE);
|
||||
|
||||
if (!_archive || (_archive->status & 2) == 0)
|
||||
return 0;
|
||||
|
||||
if (sizeToLoad > _totalStreamPages)
|
||||
sizeToLoad = 0;
|
||||
|
||||
if (sizeToLoad > size)
|
||||
sizeToLoad = size;
|
||||
|
||||
if (!sizeToLoad)
|
||||
return 0;
|
||||
|
||||
uint16 sizeArch = _archive->size;
|
||||
uint16 currentPos = _archive->currentPos;
|
||||
|
||||
if (sizeArch <= currentPos)
|
||||
return 0;
|
||||
|
||||
if (sizeToLoad > 16)
|
||||
sizeToLoad = 16;
|
||||
|
||||
if (currentPos + sizeToLoad > sizeArch) {
|
||||
_flags ^= kNisFlagDataChunksAvailable;
|
||||
sizeToLoad = sizeArch - currentPos;
|
||||
}
|
||||
|
||||
if (sizeToLoad + _currentStreamPage >= _totalStreamPages)
|
||||
sizeToLoad = _totalStreamPages - _currentStreamPage;
|
||||
|
||||
_engine->getArchiveManager()->readHPF(_archive, ((byte *)_backgroundSurface + (_currentStreamPage * MEM_PAGE_SIZE)), sizeToLoad);
|
||||
|
||||
_currentStreamPage += sizeToLoad;
|
||||
_remainingStreamBytes += sizeToLoad * MEM_PAGE_SIZE;
|
||||
|
||||
if (_currentStreamPage >= _totalStreamPages)
|
||||
_currentStreamPage -= _totalStreamPages;
|
||||
|
||||
return sizeToLoad;
|
||||
}
|
||||
|
||||
bool NISManager::initNIS(const char *filename, int32 flags) {
|
||||
int32 chunkSizeRead = 0;
|
||||
int32 eventSize = 0;
|
||||
Slot *slot;
|
||||
|
||||
_currentNISSound = nullptr;
|
||||
_backgroundType = 0;
|
||||
_selectBackgroundType = 0;
|
||||
_backgroundFlag = false;
|
||||
_cumulativeEventSize = 0;
|
||||
_flags = flags | kNisFlagDataChunksAvailable;
|
||||
_decompressToBackBuffer = true;
|
||||
_firstNISBackgroundDraw = true;
|
||||
|
||||
clearBounds();
|
||||
|
||||
_currentStreamPage = 0;
|
||||
_streamCurrentPosition = 0;
|
||||
_remainingStreamBytes = 0;
|
||||
_streamBufferSize = 1530 * MEM_PAGE_SIZE;
|
||||
_originalBackgroundSurface = _engine->getGraphicsManager()->_frontBuffer;
|
||||
_totalBackgroundPages = 1530;
|
||||
_totalStreamPages = 1530;
|
||||
_backgroundSurface = _engine->getGraphicsManager()->_frontBuffer;
|
||||
|
||||
_archive = _engine->getArchiveManager()->openHPF(filename);
|
||||
if (!_archive) {
|
||||
warning("NIS %s not ready", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
_engine->getMemoryManager()->lockSeqMem((_totalBackgroundPages - 300) * MEM_PAGE_SIZE);
|
||||
getStream((byte *)&_eventsCount, 4);
|
||||
|
||||
_eventsCount = READ_LE_INT32(&_eventsCount);
|
||||
|
||||
_eventsByteStream = (byte *)(_backgroundSurface + 2);
|
||||
|
||||
_background1Offset = READ_LE_INT32((int32 *)_backgroundSurface + 2);
|
||||
_background1Offset += 16;
|
||||
_background1Offset &= 0xFFFFFFF0;
|
||||
_streamBufferSize -= _background1Offset;
|
||||
_background1ByteStream = (byte *)((byte *)_backgroundSurface + _streamBufferSize);
|
||||
|
||||
_waneSpriteOffset = READ_LE_INT32((int32 *)_backgroundSurface + 4);
|
||||
_waneSpriteOffset += 16;
|
||||
_waneSpriteOffset &= 0xFFFFFFF0;
|
||||
_streamBufferSize -= _waneSpriteOffset;
|
||||
|
||||
_waneSpriteByteStream = (byte *)((byte *)_backgroundSurface + _streamBufferSize);
|
||||
_streamBufferSize -= 8 * _eventsCount;
|
||||
_eventsByteStream = (byte *)((byte *)_backgroundSurface + _streamBufferSize);
|
||||
_totalStreamPages = (_streamBufferSize / MEM_PAGE_SIZE);
|
||||
_streamBufferSize = (_streamBufferSize / MEM_PAGE_SIZE) * MEM_PAGE_SIZE;
|
||||
|
||||
chunkSizeRead = loadChunk(32);
|
||||
|
||||
getStream(_eventsByteStream, 8 * _eventsCount);
|
||||
|
||||
SAFE_DELETE_ARR(_events);
|
||||
_events = new NisEvents[_eventsCount];
|
||||
|
||||
for (int i = 0; i < _eventsCount; i++) {
|
||||
_events[i].eventType = READ_LE_INT16(_eventsByteStream + i * 8);
|
||||
_events[i].eventTime = READ_LE_INT16(_eventsByteStream + i * 8 + 2);
|
||||
_events[i].eventSize = READ_LE_INT32(_eventsByteStream + i * 8 + 4);
|
||||
}
|
||||
|
||||
while ((_flags & kNisFlagDataChunksAvailable) != 0) {
|
||||
if (!_events[4].eventSize)
|
||||
break;
|
||||
|
||||
chunkSizeRead = loadChunk(32) * MEM_PAGE_SIZE;
|
||||
|
||||
if (!chunkSizeRead)
|
||||
break;
|
||||
|
||||
eventSize = _events[4].eventSize;
|
||||
_events[4].eventSize = chunkSizeRead >= eventSize ? 0 : eventSize - chunkSizeRead;
|
||||
}
|
||||
|
||||
if ((flags & kNisFlagSoundFade) != 0)
|
||||
_engine->getSoundManager()->NISFadeOut();
|
||||
|
||||
getNISSlot();
|
||||
|
||||
for (slot = _engine->getSoundManager()->_soundCache; slot; slot = slot->getNext()) {
|
||||
if (slot->hasTag(kSoundTagNIS))
|
||||
break;
|
||||
}
|
||||
|
||||
_currentNISSound = slot;
|
||||
|
||||
if (!slot) {
|
||||
if ((flags & kNisFlagSoundFade) != 0)
|
||||
_engine->getSoundManager()->NISFadeIn();
|
||||
|
||||
endNIS();
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::String noExtName = filename;
|
||||
noExtName.replace('.', '\0');
|
||||
_currentNISSound->setSub(noExtName.c_str());
|
||||
|
||||
Common::String lnkName = Common::String(noExtName.c_str()) + ".LNK";
|
||||
|
||||
HPF *lnkResource = _engine->getArchiveManager()->openHPF(lnkName.c_str());
|
||||
if (lnkResource) {
|
||||
_engine->getArchiveManager()->closeHPF(lnkResource);
|
||||
_chainedSoundSlot = new Slot(_engine->getSoundManager(), lnkName.c_str(), kSoundTypeLink | kVolumeFull, 105);
|
||||
} else {
|
||||
_chainedSoundSlot = nullptr;
|
||||
}
|
||||
|
||||
if (_chainedSoundSlot) {
|
||||
_currentNISSound->setChainedSound(_chainedSoundSlot);
|
||||
_currentNISSound->addStatusFlag(kSoundFlagHasLinkAfter);
|
||||
|
||||
if (!scumm_stricmp(lnkName.c_str(), "1917.LNK")) {
|
||||
_chainedSoundSlot->setAssociatedCharacter(kCharacterKronos);
|
||||
_chainedSoundSlot->removeStatusFlag(kSoundTypeLink | kSoundFlagFixedVolume);
|
||||
_chainedSoundSlot->addStatusFlag(kSoundTypeConcert);
|
||||
_chainedSoundSlot->assignDirectTag(kSoundTagConcert);
|
||||
_chainedSoundSlot->assignDirectVolume(16);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NISManager::endNIS() {
|
||||
if (_archive) {
|
||||
_engine->getArchiveManager()->closeHPF(_archive);
|
||||
_archive = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void NISManager::abortNIS() {
|
||||
if ((_flags & kNisFlagPlaying) != 0)
|
||||
_flags |= kNisFlagAbortRequested;
|
||||
}
|
||||
|
||||
void NISManager::nisMouse(Event *event) {
|
||||
if ((event->flags & kMouseFlagRightDown) != 0)
|
||||
abortNIS();
|
||||
}
|
||||
|
||||
void NISManager::nisTimer(Event *event) {
|
||||
_engine->setEventTickInternal(false);
|
||||
}
|
||||
|
||||
void NISManager::drawSprite(NisSprite *sprite) {
|
||||
Extent rect;
|
||||
|
||||
rect.left = sprite->rect.left;
|
||||
rect.right = sprite->rect.right;
|
||||
rect.top = sprite->rect.top;
|
||||
rect.bottom = sprite->rect.bottom;
|
||||
rect.width = sprite->rect.width;
|
||||
rect.height = sprite->rect.height;
|
||||
|
||||
addBounds(rect);
|
||||
|
||||
// Use a temp sprite and just fill out the bare minimum needed for decompression...
|
||||
Sprite tempSprite;
|
||||
tempSprite.compData = sprite->compData;
|
||||
tempSprite.colorPalette = sprite->colorPalette;
|
||||
tempSprite.rect = rect;
|
||||
|
||||
switch (sprite->compBits) {
|
||||
case 3:
|
||||
if (_decompressToBackBuffer) {
|
||||
_engine->getGraphicsManager()->bitBltSprite8(&tempSprite, _engine->getGraphicsManager()->_backBuffer);
|
||||
} else if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->bitBltSprite8(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
break;
|
||||
case 4:
|
||||
if (_decompressToBackBuffer) {
|
||||
_engine->getGraphicsManager()->bitBltSprite16(&tempSprite, _engine->getGraphicsManager()->_backBuffer);
|
||||
} else if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->bitBltSprite16(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
break;
|
||||
case 5:
|
||||
if (_decompressToBackBuffer) {
|
||||
_engine->getGraphicsManager()->bitBltSprite32(&tempSprite, _engine->getGraphicsManager()->_backBuffer);
|
||||
} else if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->bitBltSprite32(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
break;
|
||||
case 7:
|
||||
if (_decompressToBackBuffer) {
|
||||
_engine->getGraphicsManager()->bitBltSprite128(&tempSprite, _engine->getGraphicsManager()->_backBuffer);
|
||||
} else if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->bitBltSprite128(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
break;
|
||||
case 255:
|
||||
if (_decompressToBackBuffer) {
|
||||
_engine->getGraphicsManager()->bitBltSprite255(&tempSprite, _engine->getGraphicsManager()->_backBuffer);
|
||||
} else if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->bitBltSprite255(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void NISManager::waxSprite(NisSprite *sprite) {
|
||||
Extent rect;
|
||||
|
||||
rect.left = sprite->rect.left;
|
||||
rect.right = sprite->rect.right;
|
||||
rect.top = sprite->rect.top;
|
||||
rect.bottom = sprite->rect.bottom;
|
||||
rect.width = sprite->rect.width;
|
||||
rect.height = sprite->rect.height;
|
||||
|
||||
addBounds(rect);
|
||||
|
||||
// Use a temp sprite and just fill out the bare minimum needed for decompression...
|
||||
Sprite tempSprite;
|
||||
tempSprite.compData = sprite->compData;
|
||||
tempSprite.colorPalette = sprite->colorPalette;
|
||||
tempSprite.rect = rect;
|
||||
|
||||
switch (sprite->compBits) {
|
||||
case 3:
|
||||
if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->bitBltWax8(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
break;
|
||||
case 4:
|
||||
if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->bitBltWax16(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
break;
|
||||
case 5:
|
||||
if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->bitBltWax32(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
break;
|
||||
case 7:
|
||||
if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->bitBltWax128(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void NISManager::waneSprite(NisSprite *sprite) {
|
||||
Extent rect;
|
||||
|
||||
rect.left = sprite->rect.left;
|
||||
rect.right = sprite->rect.right;
|
||||
rect.top = sprite->rect.top;
|
||||
rect.bottom = sprite->rect.bottom;
|
||||
rect.width = sprite->rect.width;
|
||||
rect.height = sprite->rect.height;
|
||||
|
||||
addBounds(rect);
|
||||
|
||||
// Use a temp sprite and just fill out the bare minimum needed for decompression...
|
||||
Sprite tempSprite;
|
||||
tempSprite.compData = sprite->compData;
|
||||
tempSprite.colorPalette = sprite->colorPalette;
|
||||
tempSprite.rect = rect;
|
||||
|
||||
switch (sprite->compBits) {
|
||||
case 3:
|
||||
if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->bitBltWane8(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
break;
|
||||
case 4:
|
||||
if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->bitBltWane16(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
break;
|
||||
case 5:
|
||||
if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->bitBltWane32(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
break;
|
||||
case 7:
|
||||
if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->bitBltWane128(&tempSprite, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels());
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void NISManager::showNISStatus(int32 status) {
|
||||
if (status != -1)
|
||||
_nisStatus = status;
|
||||
}
|
||||
|
||||
bool NISManager::doNIS(const char *name, int32 flags) {
|
||||
if (_engine->isGoldEdition())
|
||||
return true;
|
||||
|
||||
Slot *slot;
|
||||
|
||||
if (_engine->mouseHasRightClicked())
|
||||
return false;
|
||||
|
||||
if (!initNIS(name, flags))
|
||||
return false;
|
||||
|
||||
for (slot = _engine->getSoundManager()->_soundCache; slot; slot = slot->getNext()) {
|
||||
if (slot->hasTag(kSoundTagNIS))
|
||||
break;
|
||||
}
|
||||
|
||||
if (slot) {
|
||||
_currentNISSound = slot;
|
||||
slot->addStatusFlag(kSoundFlagKeepAfterFinish);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 30; i++) {
|
||||
loadChunk(32);
|
||||
}
|
||||
|
||||
_savedMouseEventHandle = _engine->getMessageManager()->getEventHandle(1);
|
||||
_savedTimerEventHandle = _engine->getMessageManager()->getEventHandle(3);
|
||||
|
||||
_engine->getMessageManager()->setEventHandle(kEventChannelMouse, &LastExpressEngine::nisMouseWrapper);
|
||||
_engine->getMessageManager()->setEventHandle(kEventChannelTimer, &LastExpressEngine::nisTimerWrapper);
|
||||
|
||||
_engine->getSoundManager()->setSoundDriverTicks(0);
|
||||
|
||||
_numOfOverlays = 0;
|
||||
|
||||
for (slot = _engine->getSoundManager()->_soundCache; slot; slot = slot->getNext()) {
|
||||
if (slot->hasTag(kSoundTagNIS))
|
||||
break;
|
||||
}
|
||||
|
||||
_currentNISSound = slot;
|
||||
if (slot) {
|
||||
_flags |= (kNisFlagPlaying | kNisFlagHasSound);
|
||||
slot->addStatusFlag(kSoundFlagKeepAfterFinish);
|
||||
} else {
|
||||
_flags = 0;
|
||||
}
|
||||
|
||||
_engine->getSoundManager()->setSoundDriverTicks(0);
|
||||
|
||||
if (_engine->mouseHasRightClicked()) {
|
||||
abortNIS();
|
||||
_flags &= ~kNisFlagPlaying;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _eventsCount; i++) {
|
||||
if ((_flags & kNisFlagPlaying) == 0)
|
||||
break;
|
||||
|
||||
if (_events[i].eventTime && _events[i].eventTime + 60 < _engine->getSoundManager()->getSoundDriverTicks()) {
|
||||
_engine->getSoundManager()->addSoundDriverFlags(kSoundDriverNISHasRequestedDelay);
|
||||
_currentNISSound->addStatusFlag(kSoundFlagPauseRequested);
|
||||
loadChunk(32);
|
||||
loadChunk(32);
|
||||
}
|
||||
|
||||
bool useDriverTicks = false;
|
||||
|
||||
if ((_currentNISSound->getStatusFlags() & (kSoundFlagCloseRequested | kSoundFlagClosed)) != 0 || _currentNISSound->getTime() <= 1) {
|
||||
useDriverTicks = true;
|
||||
} else {
|
||||
_engine->getSoundManager()->setSoundDriverTicks(_currentNISSound->getTime());
|
||||
}
|
||||
|
||||
if ((_flags & kNisFlagPlaying) != 0) {
|
||||
do {
|
||||
if (useDriverTicks) {
|
||||
if (_events[i].eventTime <= _engine->getSoundManager()->getSoundDriverTicks())
|
||||
break;
|
||||
} else {
|
||||
if (_events[i].eventTime <= _currentNISSound->getTime())
|
||||
break;
|
||||
}
|
||||
|
||||
if ((_flags & kNisFlagDataChunksAvailable) != 0)
|
||||
loadChunk(32);
|
||||
|
||||
showNISStatus(-1);
|
||||
|
||||
if ((_currentNISSound->getStatusFlags() & kSoundFlagPaused) != 0) {
|
||||
_currentNISSound->removeStatusFlag(kSoundFlagPauseRequested | kSoundFlagPaused);
|
||||
_engine->getSoundManager()->removeSoundDriverFlags(kSoundDriverNISHasRequestedDelay);
|
||||
}
|
||||
|
||||
_engine->getSoundManager()->soundThread();
|
||||
_engine->getSubtitleManager()->subThread();
|
||||
if (!_engine->getMessageManager()->process()) {
|
||||
// Only wait and handle events if we've processed all messages, unlike the original which had a separate thread for input...
|
||||
_engine->waitForTimer(5);
|
||||
}
|
||||
|
||||
for (slot = _engine->getSoundManager()->_soundCache; slot; slot = slot->getNext()) {
|
||||
if (slot->hasTag(kSoundTagNIS))
|
||||
break;
|
||||
}
|
||||
|
||||
_currentNISSound = slot;
|
||||
|
||||
if (!_backgroundFlag && (_flags & kNisFlagAbortRequested) != 0)
|
||||
_flags &= ~kNisFlagPlaying;
|
||||
|
||||
} while ((_flags & kNisFlagPlaying) != 0);
|
||||
|
||||
if ((_flags & kNisFlagPlaying) != 0) {
|
||||
if (_events[i].eventTime) {
|
||||
showNISStatus(_engine->getSoundManager()->getSoundDriverTicks() - _events[i].eventTime);
|
||||
} else {
|
||||
showNISStatus(-1);
|
||||
}
|
||||
|
||||
processNIS(&_events[i]);
|
||||
|
||||
while ((_flags & kNisFlagPlaying) != 0) {
|
||||
if (!_engine->getMessageManager()->process())
|
||||
break;
|
||||
|
||||
_engine->handleEvents();
|
||||
_engine->getSubtitleManager()->subThread();
|
||||
_engine->getSoundManager()->soundThread();
|
||||
|
||||
if (!_backgroundFlag && (_flags & kNisFlagAbortRequested) != 0)
|
||||
_flags &= ~kNisFlagPlaying;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentNISSound && !_currentNISSound->getTime())
|
||||
_currentNISSound->addStatusFlag(kSoundFlagCloseRequested);
|
||||
|
||||
_engine->getMessageManager()->setEventHandle(kEventChannelMouse, _savedMouseEventHandle);
|
||||
_engine->getMessageManager()->setEventHandle(kEventChannelTimer, _savedTimerEventHandle);
|
||||
|
||||
if (_currentNISSound && (_flags & kNisFlagAbortRequested) != 0)
|
||||
_currentNISSound->addStatusFlag(kSoundFlagCloseRequested);
|
||||
|
||||
if (_currentNISSound) {
|
||||
if (_currentNISSound->getSubtitle()) {
|
||||
_currentNISSound->getSubtitle()->kill();
|
||||
_engine->getSubtitleManager()->subThread();
|
||||
}
|
||||
|
||||
_currentNISSound->removeStatusFlag(kSoundFlagKeepAfterFinish);
|
||||
}
|
||||
|
||||
if (_chainedSoundSlot && (_chainedSoundSlot->getStatusFlags() & (kSoundFlagPlayRequested | kSoundFlagPlaying)) == 0) {
|
||||
do {
|
||||
_engine->getSoundManager()->soundThread();
|
||||
_engine->handleEvents();
|
||||
} while ((_chainedSoundSlot->getStatusFlags() & (kSoundFlagPlayRequested | kSoundFlagPlaying)) == 0);
|
||||
}
|
||||
|
||||
endNIS();
|
||||
_engine->getMemoryManager()->freeSeqMem();
|
||||
_flags = 0;
|
||||
|
||||
if ((flags & kNisFlagSoundFade) != 0)
|
||||
_engine->getSoundManager()->NISFadeIn();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NISManager::processNIS(NisEvents *event) {
|
||||
int32 x = _engine->getGraphicsManager()->_renderBox1.x;
|
||||
int32 width = _engine->getGraphicsManager()->_renderBox1.width;
|
||||
|
||||
debug(2, "NISManager::processNIS(): event at time %d type %d size %d", event->eventTime, event->eventType, event->eventSize);
|
||||
|
||||
switch (event->eventType) {
|
||||
case kNISEventBackground1: // 10
|
||||
getStream(_background1ByteStream, event->eventSize);
|
||||
convertNSPR16(_background1ByteStream, _background1);
|
||||
|
||||
if (_backgroundType == 1)
|
||||
_backgroundType = 0;
|
||||
|
||||
return;
|
||||
case kNISEventSelectBackground1: // 11
|
||||
_selectBackgroundType = 1;
|
||||
return;
|
||||
case kNISEventBackground2: // 12
|
||||
_background2ByteStream = (_background1ByteStream + ((_background1Offset - event->eventSize) & 0xFFFFFFF0));
|
||||
|
||||
getStream(_background2ByteStream, event->eventSize);
|
||||
convertNSPR16(_background2ByteStream, _background2);
|
||||
|
||||
if (_backgroundType == 2)
|
||||
_backgroundType = 0;
|
||||
|
||||
return;
|
||||
case kNISEventSelectBackground2: // 13
|
||||
_selectBackgroundType = 2;
|
||||
return;
|
||||
case kNISEventOverlay: // 20
|
||||
if (!_decompressToBackBuffer) {
|
||||
addBounds(_spriteExtent);
|
||||
}
|
||||
|
||||
_waxWaneToggleFlag = !_waxWaneToggleFlag;
|
||||
|
||||
if (_waxWaneToggleFlag) {
|
||||
_waxSpriteByteStream = (_waneSpriteByteStream + ((_waneSpriteOffset - event->eventSize) & 0xFFFFFFF0));
|
||||
getStream(_waxSpriteByteStream, event->eventSize);
|
||||
convertNSPR16(_waxSpriteByteStream, _waxSprite);
|
||||
} else {
|
||||
getStream(_waneSpriteByteStream, event->eventSize);
|
||||
convertNSPR16(_waneSpriteByteStream, _waneSprite);
|
||||
}
|
||||
|
||||
_numOfOverlays++;
|
||||
|
||||
return;
|
||||
case kNISEventUpdate: // 21
|
||||
_backgroundFlag = 0;
|
||||
|
||||
if (_decompressToBackBuffer)
|
||||
_engine->getGraphicsManager()->clear(_engine->getGraphicsManager()->_backBuffer, 0, 0, 640, 480);
|
||||
|
||||
if (_selectBackgroundType != _backgroundType) {
|
||||
drawBK(_selectBackgroundType);
|
||||
} else if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
if (_backgroundType == 1) {
|
||||
_engine->getGraphicsManager()->copy(
|
||||
_engine->getGraphicsManager()->_backBuffer,
|
||||
(PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels(),
|
||||
_background1->rect.left,
|
||||
_background1->rect.top,
|
||||
_background1->rect.right - _background1->rect.left + 1,
|
||||
_background1->rect.bottom - _background1->rect.top + 1
|
||||
);
|
||||
} else if (_backgroundType == 2) {
|
||||
_engine->getGraphicsManager()->copy(
|
||||
_engine->getGraphicsManager()->_backBuffer,
|
||||
(PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels(),
|
||||
_background2->rect.left,
|
||||
_background2->rect.top,
|
||||
_background2->rect.right - _background2->rect.left + 1,
|
||||
_background2->rect.bottom - _background2->rect.top + 1
|
||||
);
|
||||
}
|
||||
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
if (!_decompressToBackBuffer) {
|
||||
addBounds(_spriteExtent);
|
||||
}
|
||||
|
||||
if (_waxWaneToggleFlag) {
|
||||
if (_waxSprite->rect.left != 640)
|
||||
drawSprite(_waxSprite);
|
||||
|
||||
_spriteExtent.left = _waxSprite->rect.left;
|
||||
_spriteExtent.right = _waxSprite->rect.right;
|
||||
_spriteExtent.top = _waxSprite->rect.top;
|
||||
_spriteExtent.bottom = _waxSprite->rect.bottom;
|
||||
_spriteExtent.width = _waxSprite->rect.width;
|
||||
_spriteExtent.height = _waxSprite->rect.height;
|
||||
} else {
|
||||
if (_waneSprite->rect.left != 640)
|
||||
drawSprite(_waneSprite);
|
||||
|
||||
_spriteExtent.left = _waneSprite->rect.left;
|
||||
_spriteExtent.right = _waneSprite->rect.right;
|
||||
_spriteExtent.top = _waneSprite->rect.top;
|
||||
_spriteExtent.bottom = _waneSprite->rect.bottom;
|
||||
_spriteExtent.width = _waneSprite->rect.width;
|
||||
_spriteExtent.height = _waneSprite->rect.height;
|
||||
}
|
||||
|
||||
if (_firstNISBackgroundDraw) {
|
||||
_engine->getGraphicsManager()->burstBox(80, 0, 480, 480);
|
||||
} else {
|
||||
_engine->getGraphicsManager()->burstBox(
|
||||
_nisRect.left,
|
||||
_nisRect.top,
|
||||
_nisRect.right - _nisRect.left + 1,
|
||||
_nisRect.bottom - _nisRect.top + 1
|
||||
);
|
||||
}
|
||||
|
||||
_engine->getSoundManager()->soundThread();
|
||||
_firstNISBackgroundDraw = 0;
|
||||
|
||||
if (_decompressToBackBuffer) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
_engine->getSoundManager()->soundThread();
|
||||
_engine->getGraphicsManager()->dissolve((2 * x) + (2 * (i & 1)), width, 480, _engine->getGraphicsManager()->_backBuffer);
|
||||
_engine->getGraphicsManager()->burstBox(x, 0, width, 480);
|
||||
_engine->getSoundManager()->soundThread();
|
||||
_engine->handleEvents();
|
||||
}
|
||||
|
||||
if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->copy(_engine->getGraphicsManager()->_backBuffer, (PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels(), x, 0, width, 480);
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
_engine->getGraphicsManager()->burstBox(x, 0, width, 480);
|
||||
_engine->getSoundManager()->soundThread();
|
||||
|
||||
if (_backgroundType == 1) {
|
||||
drawSprite(_background1);
|
||||
} else {
|
||||
drawSprite(_background2);
|
||||
}
|
||||
|
||||
_decompressToBackBuffer = 0;
|
||||
_currentNISSound->play();
|
||||
_engine->getSoundManager()->setSoundDriverTicks(0);
|
||||
}
|
||||
|
||||
clearBounds();
|
||||
|
||||
return;
|
||||
case kNISEventUpdateTransition: // 22
|
||||
_backgroundFlag = 1;
|
||||
|
||||
if (_selectBackgroundType != _backgroundType) {
|
||||
drawBK(_selectBackgroundType);
|
||||
} else {
|
||||
if (_engine->getGraphicsManager()->acquireSurface()) {
|
||||
if (_backgroundType == 1) {
|
||||
_engine->getGraphicsManager()->copy(
|
||||
_engine->getGraphicsManager()->_backBuffer,
|
||||
(PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels(),
|
||||
_background1->rect.left,
|
||||
_background1->rect.top,
|
||||
_background1->rect.right - _background1->rect.left + 1,
|
||||
_background1->rect.bottom - _background1->rect.top + 1
|
||||
);
|
||||
} else if (_backgroundType == 2) {
|
||||
_engine->getGraphicsManager()->copy(
|
||||
_engine->getGraphicsManager()->_backBuffer,
|
||||
(PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels(),
|
||||
_background2->rect.left,
|
||||
_background2->rect.top,
|
||||
_background2->rect.right - _background2->rect.left + 1,
|
||||
_background2->rect.bottom - _background2->rect.top + 1
|
||||
);
|
||||
}
|
||||
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
}
|
||||
|
||||
waxSprite(_waxSprite);
|
||||
waneSprite(_waneSprite);
|
||||
|
||||
_engine->getGraphicsManager()->burstBox(
|
||||
_nisRect.left,
|
||||
_nisRect.top,
|
||||
_nisRect.right - _nisRect.left + 1,
|
||||
_nisRect.bottom - _nisRect.top + 1
|
||||
);
|
||||
|
||||
_engine->getSoundManager()->soundThread();
|
||||
clearBounds();
|
||||
|
||||
return;
|
||||
case kNISEventSound1: // 30
|
||||
case kNISEventSound2: // 31
|
||||
_currentNISSound->setFade(event->eventSize);
|
||||
return;
|
||||
case kNISEventAudioData: // 32
|
||||
_cumulativeEventSize += event->eventSize;
|
||||
|
||||
while (_currentNISSound->getSize() + NIS_SOUND_CHUNK_SIZE * _currentNISSound->getTime() + 745 <= (event->eventSize + _currentNISSound->getNumLoadedBytes()))
|
||||
_engine->getSoundManager()->soundThread();
|
||||
|
||||
loadSnd(event->eventSize);
|
||||
|
||||
if ((_flags & kNisFlagSoundInitialized) == 0) {
|
||||
_flags |= kNisFlagSoundInitialized;
|
||||
_currentNISSound->setBlockCount(READ_LE_UINT16((uint16 *)_currentNISSound->getDataStart() + 2) - 1);
|
||||
_currentNISSound->setSize(0x16000);
|
||||
}
|
||||
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void NISManager::drawBK(int type) {
|
||||
Extent rect;
|
||||
|
||||
rect.left = 80;
|
||||
rect.right = 559;
|
||||
rect.bottom = 479;
|
||||
rect.top = 0;
|
||||
rect.width = 0;
|
||||
rect.height = 0;
|
||||
|
||||
if (!_decompressToBackBuffer && _engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->clear(_engine->getGraphicsManager()->_screenSurface, 80, 0, 480, 480);
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
if (type == 1) {
|
||||
drawSprite(_background1);
|
||||
} else {
|
||||
drawSprite(_background2);
|
||||
}
|
||||
|
||||
_firstNISBackgroundDraw = true;
|
||||
|
||||
if (!_decompressToBackBuffer && _engine->getGraphicsManager()->acquireSurface()) {
|
||||
_engine->getGraphicsManager()->copy((PixMap *)_engine->getGraphicsManager()->_screenSurface.getPixels(), _engine->getGraphicsManager()->_backBuffer, 0, 0, 640, 480);
|
||||
_engine->getGraphicsManager()->unlockSurface();
|
||||
}
|
||||
|
||||
addBounds(rect);
|
||||
_backgroundType = type;
|
||||
}
|
||||
|
||||
void NISManager::getNISSlot() {
|
||||
// This slot will automatically be included in the queue
|
||||
Slot *slot = new Slot(_engine->getSoundManager(), kSoundTypeNIS | kSoundFlagCyclicBuffer | kVolumeFull, 90);
|
||||
|
||||
slot->setCurrentBufferPtr(slot->getSoundBuffer());
|
||||
slot->setDataStart(slot->getSoundBuffer());
|
||||
slot->setDataEnd(slot->getSoundBuffer() + (44 * MEM_PAGE_SIZE));
|
||||
slot->setCurrentDataPtr(slot->getDataStart() + 6);
|
||||
slot->setSize(44 * MEM_PAGE_SIZE);
|
||||
}
|
||||
|
||||
Slot *NISManager::getChainedSound() {
|
||||
return _chainedSoundSlot;
|
||||
}
|
||||
|
||||
void NISManager::setChainedSound(Slot *slot) {
|
||||
_chainedSoundSlot = slot;
|
||||
}
|
||||
|
||||
int32 NISManager::getNISFlag() {
|
||||
return _flags;
|
||||
}
|
||||
|
||||
} // End of namespace LastExpress
|
||||
130
engines/lastexpress/game/nis.h
Normal file
130
engines/lastexpress/game/nis.h
Normal file
@@ -0,0 +1,130 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LASTEXPRESS_NIS_H
|
||||
#define LASTEXPRESS_NIS_H
|
||||
|
||||
#include "lastexpress/lastexpress.h"
|
||||
|
||||
#include "lastexpress/data/archive.h"
|
||||
#include "lastexpress/sound/slot.h"
|
||||
|
||||
namespace LastExpress {
|
||||
|
||||
class LastExpressEngine;
|
||||
class Slot;
|
||||
|
||||
struct HPF;
|
||||
struct Event;
|
||||
struct NisSprite;
|
||||
struct NisEvents;
|
||||
struct Extent;
|
||||
|
||||
#define NIS_SOUND_CHUNK_SIZE 739
|
||||
|
||||
class NISManager {
|
||||
|
||||
public:
|
||||
NISManager(LastExpressEngine *engine);
|
||||
~NISManager();
|
||||
|
||||
void clearBounds();
|
||||
void addBounds(Extent extent);
|
||||
void convertNSPR16(byte *spriteData, NisSprite *outSprite);
|
||||
void getStream(byte *data, int32 size);
|
||||
void loadSnd(int32 size);
|
||||
int loadChunk(int32 size);
|
||||
bool initNIS(const char *filename, int32 flags);
|
||||
void endNIS();
|
||||
void abortNIS();
|
||||
void nisMouse(Event *event);
|
||||
void nisTimer(Event *event);
|
||||
void drawSprite(NisSprite *sprite);
|
||||
void waxSprite(NisSprite *sprite);
|
||||
void waneSprite(NisSprite *sprite);
|
||||
void showNISStatus(int32 status);
|
||||
bool doNIS(const char *name, int32 flags);
|
||||
void processNIS(NisEvents *event);
|
||||
void drawBK(int type);
|
||||
void getNISSlot();
|
||||
|
||||
Slot *getChainedSound();
|
||||
void setChainedSound(Slot *slot);
|
||||
int32 getNISFlag();
|
||||
|
||||
private:
|
||||
LastExpressEngine *_engine = nullptr;
|
||||
|
||||
int32 _flags = 0;
|
||||
|
||||
Common::Rect _nisRect = Common::Rect(0, 0, 0, 0);
|
||||
|
||||
int32 _eventsCount = 0;
|
||||
NisEvents *_events = nullptr;
|
||||
byte *_eventsByteStream = nullptr;
|
||||
|
||||
int32 _numOfOverlays = 0;
|
||||
|
||||
HPF *_archive = nullptr;
|
||||
Slot *_currentNISSound = nullptr;
|
||||
Slot *_chainedSoundSlot = nullptr;
|
||||
|
||||
int32 _streamCurrentPosition = 0;
|
||||
int32 _currentStreamPage = 0;
|
||||
int32 _totalStreamPages = 0;
|
||||
int32 _remainingStreamBytes = 0;
|
||||
int32 _streamBufferSize = 0;
|
||||
int32 _totalBackgroundPages = 0;
|
||||
Extent _spriteExtent;
|
||||
|
||||
PixMap *_backgroundSurface = nullptr;
|
||||
int32 _nisStatus = 0;
|
||||
bool _decompressToBackBuffer = true;
|
||||
bool _firstNISBackgroundDraw = true;
|
||||
bool _waxWaneToggleFlag = false;
|
||||
|
||||
NisSprite *_background1 = nullptr;
|
||||
NisSprite *_background2 = nullptr;
|
||||
NisSprite *_waneSprite = nullptr;
|
||||
NisSprite *_waxSprite = nullptr;
|
||||
|
||||
byte *_background1ByteStream = nullptr;
|
||||
byte *_background2ByteStream = nullptr;
|
||||
byte *_waneSpriteByteStream = nullptr;
|
||||
byte *_waxSpriteByteStream = nullptr;
|
||||
|
||||
int32 _cumulativeEventSize = 0;
|
||||
bool _backgroundFlag = false;
|
||||
int32 _backgroundType = 0;
|
||||
int32 _selectBackgroundType = 0;
|
||||
int32 _background1Offset = 0;
|
||||
int32 _waneSpriteOffset = 0;
|
||||
|
||||
PixMap *_originalBackgroundSurface = nullptr;
|
||||
|
||||
|
||||
void (LastExpressEngine::*_savedTimerEventHandle)(Event *) = nullptr;
|
||||
void (LastExpressEngine::*_savedMouseEventHandle)(Event *) = nullptr;
|
||||
};
|
||||
|
||||
} // End of namespace LastExpress
|
||||
|
||||
#endif // LASTEXPRESS_NIS_H
|
||||
1545
engines/lastexpress/game/otis.cpp
Normal file
1545
engines/lastexpress/game/otis.cpp
Normal file
File diff suppressed because it is too large
Load Diff
72
engines/lastexpress/game/otis.h
Normal file
72
engines/lastexpress/game/otis.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LASTEXPRESS_OTIS_H
|
||||
#define LASTEXPRESS_OTIS_H
|
||||
|
||||
#include "lastexpress/lastexpress.h"
|
||||
|
||||
namespace LastExpress {
|
||||
|
||||
class LastExpressEngine;
|
||||
|
||||
struct Seq;
|
||||
struct Node;
|
||||
|
||||
class OtisManager {
|
||||
|
||||
public:
|
||||
OtisManager(LastExpressEngine *engine);
|
||||
~OtisManager() {}
|
||||
|
||||
void wipeLooseSprites();
|
||||
void wipeGSysInfo(int character);
|
||||
void wipeAllGSysInfo();
|
||||
bool fDirection(int nodeIdx);
|
||||
bool rDirection(int nodeIdx);
|
||||
bool doorView();
|
||||
bool corrRender(int nodeIdx);
|
||||
bool walkingRender();
|
||||
int checkMouse(int32 cursorX, int32 cursorY);
|
||||
void startSeq(int character, int direction, bool loadSequence);
|
||||
void getNewSeqName(int character, int direction, char *outSeqName, char *outSecondarySeqName);
|
||||
void drawLooseSprites();
|
||||
void refreshSequences();
|
||||
int findFrame(int character, Seq *sequence, int position, bool doProcessing);
|
||||
void initCurFrame(int character);
|
||||
bool mainWalkTooFar(int character);
|
||||
int getFudge();
|
||||
void updateCharacter(int character);
|
||||
void doNewSprite(int character, bool keepPreviousFrame, bool dontPlaySound);
|
||||
void doSeqChange(int character);
|
||||
void doNextSeq(int character);
|
||||
void doNoSeq(int character);
|
||||
void updateAll();
|
||||
void goUpdateAll();
|
||||
void adjustOtisTrueTime();
|
||||
|
||||
private:
|
||||
LastExpressEngine *_engine = nullptr;
|
||||
};
|
||||
|
||||
} // End of namespace LastExpress
|
||||
|
||||
#endif // LASTEXPRESS_OTIS_H
|
||||
769
engines/lastexpress/game/savegame.cpp
Normal file
769
engines/lastexpress/game/savegame.cpp
Normal file
@@ -0,0 +1,769 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "lastexpress/game/savegame.h"
|
||||
|
||||
#include "lastexpress/data/cvcrfile.h"
|
||||
|
||||
#include "lastexpress/game/logic.h"
|
||||
|
||||
#include "lastexpress/menu/menu.h"
|
||||
|
||||
#include "lastexpress/debug.h"
|
||||
#include "lastexpress/lastexpress.h"
|
||||
|
||||
#include "common/savefile.h"
|
||||
|
||||
namespace LastExpress {
|
||||
|
||||
SaveManager::SaveManager(LastExpressEngine *engine) {
|
||||
_engine = engine;
|
||||
}
|
||||
|
||||
void SaveManager::writeSavePoint(CVCRFile *file, int saveType, int character, int value) {
|
||||
int32 originalFilePos;
|
||||
int32 paddingSize;
|
||||
int32 posAfterWriting;
|
||||
SVCRSavePointHeader savePointHeader;
|
||||
byte emptyHeader[15];
|
||||
|
||||
savePointHeader.type = saveType;
|
||||
savePointHeader.magicNumber = 0xE660E660;
|
||||
savePointHeader.size = 0;
|
||||
savePointHeader.time = _engine->getLogicManager()->_gameTime;
|
||||
savePointHeader.partNumber = _engine->getLogicManager()->_globals[kGlobalChapter];
|
||||
savePointHeader.latestGameEvent = value;
|
||||
savePointHeader.emptyField1 = 0;
|
||||
savePointHeader.emptyField2 = 0;
|
||||
|
||||
originalFilePos = file->tell();
|
||||
file->write(&savePointHeader, sizeof(SVCRSavePointHeader), 1, 0);
|
||||
file->flush();
|
||||
file->writeRLE(&character, 4, 1);
|
||||
file->writeRLE(&_engine->getLogicManager()->_gameTime, 4, 1);
|
||||
file->writeRLE(&_engine->getLogicManager()->_timeSpeed, 4, 1);
|
||||
file->writeRLE(&_engine->getLogicManager()->_realTime, 4, 1);
|
||||
file->writeRLE(&_engine->getLogicManager()->_activeNode, 4, 1);
|
||||
file->writeRLE(&_engine->getLogicManager()->_closeUp, 1, 1);
|
||||
file->writeRLE(&_engine->getLogicManager()->_nodeReturn, 4, 1);
|
||||
file->writeRLE(&_engine->getLogicManager()->_nodeReturn2, 4, 1);
|
||||
file->writeRLE(&_engine->getLogicManager()->_activeItem, 4, 1);
|
||||
|
||||
file->writeRLE(_engine->getLogicManager()->_blockedViews, 4, 1000);
|
||||
file->writeRLE(_engine->getLogicManager()->_blockedX, 4, 16);
|
||||
file->writeRLE(_engine->getLogicManager()->_softBlockedX, 4, 16);
|
||||
file->writeRLE(_engine->getLogicManager()->_globals, 4, 128);
|
||||
file->writeRLE(_engine->getLogicManager()->_doneNIS, 1, 512);
|
||||
|
||||
// Handle complex types (which were fine in the original, but not within a crossplatform context)...
|
||||
byte *inventoryBuffer = (byte *)malloc(32 * 7); // 32 items, 7 bytes each
|
||||
byte *doorsBuffer = (byte *)malloc(128 * 5); // 128 objects, 5 bytes each
|
||||
byte *charactersBuffer = (byte *)malloc(40 * 1262); // 128 objects, 5 bytes each
|
||||
|
||||
assert(inventoryBuffer && doorsBuffer && charactersBuffer);
|
||||
|
||||
// Copy Item data...
|
||||
for (int i = 0; i < 32; i++) {
|
||||
int offset = i * 7;
|
||||
inventoryBuffer[offset] = _engine->getLogicManager()->_items[i].mnum;
|
||||
WRITE_LE_UINT16(&inventoryBuffer[offset + 1], _engine->getLogicManager()->_items[i].closeUp);
|
||||
inventoryBuffer[offset + 3] = _engine->getLogicManager()->_items[i].useable;
|
||||
inventoryBuffer[offset + 4] = _engine->getLogicManager()->_items[i].haveIt;
|
||||
inventoryBuffer[offset + 5] = _engine->getLogicManager()->_items[i].inPocket;
|
||||
inventoryBuffer[offset + 6] = _engine->getLogicManager()->_items[i].floating;
|
||||
}
|
||||
|
||||
file->writeRLE(inventoryBuffer, 7, 32);
|
||||
|
||||
// Copy Door data...
|
||||
for (int i = 0; i < 128; i++) {
|
||||
int offset = i * 5;
|
||||
doorsBuffer[offset] = _engine->getLogicManager()->_doors[i].who;
|
||||
doorsBuffer[offset + 1] = _engine->getLogicManager()->_doors[i].status;
|
||||
doorsBuffer[offset + 2] = _engine->getLogicManager()->_doors[i].windowCursor;
|
||||
doorsBuffer[offset + 3] = _engine->getLogicManager()->_doors[i].handleCursor;
|
||||
doorsBuffer[offset + 4] = _engine->getLogicManager()->_doors[i].model;
|
||||
}
|
||||
|
||||
file->writeRLE(doorsBuffer, 5, 128);
|
||||
|
||||
// Copy Character data...
|
||||
for (int i = 0; i < 40; i++) {
|
||||
int offset = 0;
|
||||
Character characterStruct = getCharacter(i);
|
||||
|
||||
// First copy CallParams (9 sets of 32 integers)...
|
||||
for (int j = 0; j < 9; j++) {
|
||||
for (int k = 0; k < 32; k++) {
|
||||
WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], characterStruct.callParams[j].parameters[k]);
|
||||
offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy callbacks array (16 bytes)...
|
||||
for (int j = 0; j < 16; j++) {
|
||||
charactersBuffer[i * 1262 + offset++] = characterStruct.callbacks[j];
|
||||
}
|
||||
|
||||
// Copy currentCall (1 byte)...
|
||||
charactersBuffer[i * 1262 + offset++] = characterStruct.currentCall;
|
||||
|
||||
// Copy characterPosition (3 uint16)...
|
||||
WRITE_LE_UINT16(&charactersBuffer[i * 1262 + offset], characterStruct.characterPosition.position); offset += 2;
|
||||
WRITE_LE_UINT16(&charactersBuffer[i * 1262 + offset], characterStruct.characterPosition.location); offset += 2;
|
||||
WRITE_LE_UINT16(&charactersBuffer[i * 1262 + offset], characterStruct.characterPosition.car); offset += 2;
|
||||
|
||||
// Copy the remaining basic fields...
|
||||
charactersBuffer[i * 1262 + offset++] = characterStruct.walkCounter;
|
||||
charactersBuffer[i * 1262 + offset++] = characterStruct.attachedConductor;
|
||||
charactersBuffer[i * 1262 + offset++] = characterStruct.inventoryItem;
|
||||
charactersBuffer[i * 1262 + offset++] = characterStruct.direction;
|
||||
|
||||
WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.waitedTicksUntilCycleRestart); offset += 2;
|
||||
WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.currentFrameSeq1); offset += 2;
|
||||
WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.currentFrameSeq2); offset += 2;
|
||||
WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.elapsedFrames); offset += 2;
|
||||
WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.walkStepSize); offset += 2;
|
||||
|
||||
charactersBuffer[i * 1262 + offset++] = characterStruct.clothes;
|
||||
charactersBuffer[i * 1262 + offset++] = characterStruct.position2;
|
||||
charactersBuffer[i * 1262 + offset++] = characterStruct.car2;
|
||||
charactersBuffer[i * 1262 + offset++] = characterStruct.doProcessEntity;
|
||||
charactersBuffer[i * 1262 + offset++] = characterStruct.needsPosFudge;
|
||||
charactersBuffer[i * 1262 + offset++] = characterStruct.needsSecondaryPosFudge;
|
||||
charactersBuffer[i * 1262 + offset++] = characterStruct.directionSwitch;
|
||||
|
||||
// Copy string fields
|
||||
memcpy(&charactersBuffer[i * 1262 + offset], characterStruct.sequenceName, 13); offset += 13;
|
||||
memcpy(&charactersBuffer[i * 1262 + offset], characterStruct.sequenceName2, 13); offset += 13;
|
||||
memcpy(&charactersBuffer[i * 1262 + offset], characterStruct.sequenceNamePrefix, 7); offset += 7;
|
||||
memcpy(&charactersBuffer[i * 1262 + offset], characterStruct.sequenceNameCopy, 13); offset += 13;
|
||||
|
||||
// Set pointers to zero (each 4 bytes)
|
||||
WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // frame1
|
||||
WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // frame2
|
||||
WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // sequence1
|
||||
WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // sequence2
|
||||
WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // sequence3
|
||||
|
||||
// At this point, offset should equal 1262!
|
||||
assert(offset == 1262);
|
||||
}
|
||||
|
||||
file->writeRLE(charactersBuffer, 1262, 40);
|
||||
|
||||
free(inventoryBuffer);
|
||||
free(doorsBuffer);
|
||||
free(charactersBuffer);
|
||||
|
||||
_engine->getSoundManager()->saveSoundInfo(file);
|
||||
_engine->getMessageManager()->saveMessages(file);
|
||||
|
||||
savePointHeader.size = file->flush();
|
||||
|
||||
if ((savePointHeader.size & 0xF) != 0) {
|
||||
memset(emptyHeader, 0, sizeof(emptyHeader));
|
||||
paddingSize = ((~(savePointHeader.size & 0xFF) & 0xF) + 1);
|
||||
file->write(&emptyHeader, 1, paddingSize, 0);
|
||||
savePointHeader.size += paddingSize;
|
||||
}
|
||||
|
||||
posAfterWriting = file->tell();
|
||||
file->seek(originalFilePos, 0);
|
||||
checkSavePointHeader(&savePointHeader);
|
||||
file->write(&savePointHeader, 32, 1, false);
|
||||
file->seek(posAfterWriting, 0);
|
||||
}
|
||||
|
||||
void SaveManager::readSavePoint(CVCRFile *file, int *saveType, uint8 *character, int *saveEvent, bool skipSoundLoading) {
|
||||
int latestGameEvent;
|
||||
int32 originalPos;
|
||||
int32 posDiff;
|
||||
SVCRSavePointHeader savePointHeader;
|
||||
|
||||
if (saveType && character && saveEvent) {
|
||||
*saveType = 1;
|
||||
*character = kCharacterCath;
|
||||
*saveEvent = 0;
|
||||
|
||||
file->read(&savePointHeader, sizeof(SVCRSavePointHeader), 1, false, true);
|
||||
|
||||
if (checkSavePointHeader(&savePointHeader)) {
|
||||
latestGameEvent = savePointHeader.latestGameEvent;
|
||||
*saveType = savePointHeader.type;
|
||||
*saveEvent = latestGameEvent;
|
||||
file->flush();
|
||||
originalPos = file->tell();
|
||||
|
||||
// The original treats the "character" arg as uint8, but then asks
|
||||
// for a four bytes integer, causing a stack corruption around it.
|
||||
// This is our workaround...
|
||||
int32 intCharacter;
|
||||
file->readRLE(&intCharacter, 4, 1);
|
||||
*character = (uint8)(intCharacter & 0xFF);
|
||||
|
||||
if (*character >= 40) {
|
||||
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
file->readRLE(&_engine->getLogicManager()->_gameTime, 4, 1);
|
||||
if (_engine->getLogicManager()->_gameTime < 1061100) {
|
||||
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
if (_engine->getLogicManager()->_gameTime > 4941000) {
|
||||
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
file->readRLE(&_engine->getLogicManager()->_timeSpeed, 4, 1);
|
||||
if (_engine->getLogicManager()->_gameTime > 4941000) {
|
||||
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
file->readRLE(&_engine->getLogicManager()->_realTime, 4, 1);
|
||||
|
||||
file->readRLE(&_engine->getLogicManager()->_activeNode, 4, 1);
|
||||
if (_engine->getLogicManager()->_activeNode >= 2500) {
|
||||
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
file->readRLE(&_engine->getLogicManager()->_closeUp, 1, 1);
|
||||
if (_engine->getLogicManager()->_closeUp > 1) {
|
||||
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
file->readRLE(&_engine->getLogicManager()->_nodeReturn, 4, 1);
|
||||
if (_engine->getLogicManager()->_nodeReturn >= 2500) {
|
||||
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
file->readRLE(&_engine->getLogicManager()->_nodeReturn2, 4, 1);
|
||||
if (_engine->getLogicManager()->_nodeReturn2 >= 2500) {
|
||||
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
file->readRLE(&_engine->getLogicManager()->_activeItem, 4, 1);
|
||||
if (_engine->getLogicManager()->_activeItem >= 32) {
|
||||
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
file->readRLE(_engine->getLogicManager()->_blockedViews, 4, 1000);
|
||||
file->readRLE(_engine->getLogicManager()->_blockedX, 4, 16);
|
||||
file->readRLE(_engine->getLogicManager()->_softBlockedX, 4, 16);
|
||||
file->readRLE(_engine->getLogicManager()->_globals, 4, 128);
|
||||
file->readRLE(_engine->getLogicManager()->_doneNIS, 1, 512);
|
||||
|
||||
// Handle complex types (which were fine in the original, but not within a crossplatform context)...
|
||||
byte *inventoryBuffer = (byte *)malloc(32 * 7); // 32 items, 7 bytes each
|
||||
byte *doorsBuffer = (byte *)malloc(128 * 5); // 128 objects, 5 bytes each
|
||||
byte *charactersBuffer = (byte *)malloc(40 * 1262); // 40 characters, 1262 bytes each
|
||||
|
||||
assert(inventoryBuffer && doorsBuffer && charactersBuffer);
|
||||
|
||||
// Read data from file
|
||||
file->readRLE(inventoryBuffer, 7, 32);
|
||||
file->readRLE(doorsBuffer, 5, 128);
|
||||
file->readRLE(charactersBuffer, 1262, 40);
|
||||
|
||||
// Copy Item data from buffer to structures
|
||||
for (int i = 0; i < 32; i++) {
|
||||
int offset = i * 7;
|
||||
_engine->getLogicManager()->_items[i].mnum = inventoryBuffer[offset];
|
||||
_engine->getLogicManager()->_items[i].closeUp = READ_LE_UINT16(&inventoryBuffer[offset + 1]);
|
||||
_engine->getLogicManager()->_items[i].useable = inventoryBuffer[offset + 3];
|
||||
_engine->getLogicManager()->_items[i].haveIt = inventoryBuffer[offset + 4];
|
||||
_engine->getLogicManager()->_items[i].inPocket = inventoryBuffer[offset + 5];
|
||||
_engine->getLogicManager()->_items[i].floating = inventoryBuffer[offset + 6];
|
||||
}
|
||||
|
||||
// Copy Door data from buffer to structures
|
||||
for (int i = 0; i < 128; i++) {
|
||||
int offset = i * 5;
|
||||
_engine->getLogicManager()->_doors[i].who = doorsBuffer[offset];
|
||||
_engine->getLogicManager()->_doors[i].status = doorsBuffer[offset + 1];
|
||||
_engine->getLogicManager()->_doors[i].windowCursor = doorsBuffer[offset + 2];
|
||||
_engine->getLogicManager()->_doors[i].handleCursor = doorsBuffer[offset + 3];
|
||||
_engine->getLogicManager()->_doors[i].model = doorsBuffer[offset + 4];
|
||||
}
|
||||
|
||||
// Copy Character data from buffer to structures
|
||||
for (int i = 0; i < 40; i++) {
|
||||
int offset = 0;
|
||||
Character *characterStruct = &getCharacter(i);
|
||||
|
||||
// Copy CallParams (9 sets of 32 integers)
|
||||
for (int j = 0; j < 9; j++) {
|
||||
for (int k = 0; k < 32; k++) {
|
||||
characterStruct->callParams[j].parameters[k] = READ_LE_UINT32(&charactersBuffer[i * 1262 + offset]);
|
||||
offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy callbacks array (16 bytes)
|
||||
for (int j = 0; j < 16; j++) {
|
||||
characterStruct->callbacks[j] = charactersBuffer[i * 1262 + offset]; offset++;
|
||||
}
|
||||
|
||||
// Copy currentCall (1 byte)
|
||||
characterStruct->currentCall = charactersBuffer[i * 1262 + offset]; offset++;
|
||||
|
||||
// Copy characterPosition (3 uint16)
|
||||
characterStruct->characterPosition.position = READ_LE_UINT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
|
||||
characterStruct->characterPosition.location = READ_LE_UINT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
|
||||
characterStruct->characterPosition.car = READ_LE_UINT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
|
||||
|
||||
// Copy the remaining basic fields
|
||||
characterStruct->walkCounter = charactersBuffer[i * 1262 + offset++];
|
||||
characterStruct->attachedConductor = charactersBuffer[i * 1262 + offset++];
|
||||
characterStruct->inventoryItem = charactersBuffer[i * 1262 + offset++];
|
||||
characterStruct->direction = charactersBuffer[i * 1262 + offset++];
|
||||
|
||||
characterStruct->waitedTicksUntilCycleRestart = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
|
||||
characterStruct->currentFrameSeq1 = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
|
||||
characterStruct->currentFrameSeq2 = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
|
||||
characterStruct->elapsedFrames = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
|
||||
characterStruct->walkStepSize = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2;
|
||||
|
||||
characterStruct->clothes = charactersBuffer[i * 1262 + offset]; offset++;
|
||||
characterStruct->position2 = charactersBuffer[i * 1262 + offset]; offset++;
|
||||
characterStruct->car2 = charactersBuffer[i * 1262 + offset]; offset++;
|
||||
characterStruct->doProcessEntity = charactersBuffer[i * 1262 + offset]; offset++;
|
||||
characterStruct->needsPosFudge = charactersBuffer[i * 1262 + offset]; offset++;
|
||||
characterStruct->needsSecondaryPosFudge = charactersBuffer[i * 1262 + offset]; offset++;
|
||||
characterStruct->directionSwitch = charactersBuffer[i * 1262 + offset]; offset++;
|
||||
|
||||
// Copy string fields
|
||||
memcpy(characterStruct->sequenceName, &charactersBuffer[i * 1262 + offset], 13); offset += 13;
|
||||
memcpy(characterStruct->sequenceName2, &charactersBuffer[i * 1262 + offset], 13); offset += 13;
|
||||
memcpy(characterStruct->sequenceNamePrefix, &charactersBuffer[i * 1262 + offset], 7); offset += 7;
|
||||
memcpy(characterStruct->sequenceNameCopy, &charactersBuffer[i * 1262 + offset], 13); offset += 13;
|
||||
|
||||
// Skip pointer data...
|
||||
offset += 4; // frame1
|
||||
offset += 4; // frame2
|
||||
offset += 4; // sequence1
|
||||
offset += 4; // sequence2
|
||||
offset += 4; // sequence3
|
||||
|
||||
// At this point, offset should equal 1262!
|
||||
assert(offset == 1262);
|
||||
}
|
||||
|
||||
free(inventoryBuffer);
|
||||
free(doorsBuffer);
|
||||
free(charactersBuffer);
|
||||
|
||||
_engine->getSoundManager()->loadSoundInfo(file, skipSoundLoading);
|
||||
_engine->getMessageManager()->loadMessages(file);
|
||||
|
||||
_engine->getLogicManager()->_globals[kGlobalChapter] = savePointHeader.partNumber;
|
||||
|
||||
file->flush();
|
||||
|
||||
posDiff = (file->tell() - originalPos) & 0xFF;
|
||||
if ((posDiff & 0xF) != 0)
|
||||
file->seek(((~posDiff & 0xF) + 1), 1);
|
||||
|
||||
for (int i = 0; i < 40; i++) {
|
||||
getCharacter(i).frame1 = nullptr;
|
||||
getCharacter(i).frame2 = nullptr;
|
||||
getCharacter(i).sequence1 = nullptr;
|
||||
getCharacter(i).sequence2 = nullptr;
|
||||
getCharacter(i).sequence3 = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SaveManager::validateSaveFile(bool flag) {
|
||||
SVCRSavePointHeader savePointHeader;
|
||||
SVCRFileHeader fileHeader;
|
||||
|
||||
CVCRFile *tempFile = new CVCRFile(_engine);
|
||||
CVCRFile *saveFile = new CVCRFile(_engine);
|
||||
|
||||
bool hasValidationError = false;
|
||||
|
||||
if (flag) {
|
||||
if (_engine->_savePointHeaders)
|
||||
_engine->getMemoryManager()->freeMem(_engine->_savePointHeaders);
|
||||
|
||||
_engine->_savePointHeaders = (SVCRSavePointHeader *)_engine->getMemoryManager()->allocMem(
|
||||
sizeof(SVCRSavePointHeader), _engine->_savegameFilename, kCharacterMaster
|
||||
);
|
||||
|
||||
if (!_engine->_savePointHeaders) {
|
||||
error("Out of memory");
|
||||
}
|
||||
|
||||
_engine->_savePointHeaders->time = _engine->isDemo() ? 2241000 : 1037700;
|
||||
_engine->_savePointHeaders->partNumber = _engine->isDemo() ? 3 : 1;
|
||||
}
|
||||
|
||||
saveFile->open(_engine->_savegameFilename, CVCRMODE_RB);
|
||||
|
||||
saveFile->seek(0, SEEK_END);
|
||||
int fileSize = saveFile->tell();
|
||||
saveFile->seek(0, SEEK_SET);
|
||||
|
||||
if (fileSize >= (int32)sizeof(SVCRFileHeader)) {
|
||||
saveFile->read(&fileHeader, sizeof(SVCRFileHeader), 1, false, true);
|
||||
if (checkFileHeader(&fileHeader)) {
|
||||
if (flag) {
|
||||
if (_engine->_savePointHeaders)
|
||||
_engine->getMemoryManager()->freeMem(_engine->_savePointHeaders);
|
||||
|
||||
_engine->_savePointHeaders = (SVCRSavePointHeader *)_engine->getMemoryManager()->allocMem(
|
||||
sizeof(SVCRSavePointHeader) * (fileHeader.numVCRGames + 1),
|
||||
_engine->_savegameFilename,
|
||||
kCharacterMaster
|
||||
);
|
||||
|
||||
if (!_engine->_savePointHeaders) {
|
||||
error("Out of memory");
|
||||
}
|
||||
|
||||
_engine->_savePointHeaders->time = _engine->isDemo() ? 2241000 : 1037700;
|
||||
_engine->_savePointHeaders->partNumber = _engine->isDemo() ? 3 : 1;
|
||||
}
|
||||
|
||||
for (int i = 0; fileSize >= (int32)sizeof(SVCRFileHeader) && i < fileHeader.numVCRGames; ++i) {
|
||||
_engine->getSoundManager()->soundThread();
|
||||
|
||||
saveFile->read(&savePointHeader, sizeof(SVCRSavePointHeader), 1, false, true);
|
||||
if (flag) {
|
||||
memcpy(&_engine->_savePointHeaders[i + 1], &savePointHeader, sizeof(savePointHeader));
|
||||
}
|
||||
|
||||
fileSize -= sizeof(SVCRSavePointHeader);
|
||||
if (fileSize >= 0) {
|
||||
if (checkSavePointHeader(&savePointHeader)) {
|
||||
fileSize -= savePointHeader.size;
|
||||
if (fileSize >= 0) {
|
||||
saveFile->seek(savePointHeader.size, SEEK_CUR);
|
||||
} else {
|
||||
hasValidationError = true;
|
||||
}
|
||||
} else {
|
||||
fileSize = 0;
|
||||
hasValidationError = true;
|
||||
}
|
||||
} else {
|
||||
hasValidationError = true;
|
||||
}
|
||||
}
|
||||
|
||||
saveFile->close();
|
||||
} else {
|
||||
hasValidationError = true;
|
||||
saveFile->close();
|
||||
}
|
||||
} else {
|
||||
hasValidationError = true;
|
||||
saveFile->close();
|
||||
}
|
||||
|
||||
if (hasValidationError) {
|
||||
saveFile->open(_engine->_savegameFilename, CVCRMODE_RB);
|
||||
saveFile->seek(0, SEEK_END);
|
||||
fileSize = saveFile->tell();
|
||||
saveFile->seek(0, SEEK_SET);
|
||||
|
||||
if (fileSize < (int32)sizeof(SVCRFileHeader)) {
|
||||
if (fileSize) {
|
||||
error("Attempting to salvage corrupt save game file \"%s\"", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
saveFile->close();
|
||||
_engine->getVCR()->virginSaveFile();
|
||||
|
||||
delete tempFile;
|
||||
delete saveFile;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
saveFile->read(&fileHeader, sizeof(SVCRFileHeader), 1, false, true);
|
||||
if (!checkFileHeader(&fileHeader)) {
|
||||
error("Attempting to salvage corrupt save game file \"%s\"", _engine->_savegameFilename);
|
||||
saveFile->close();
|
||||
_engine->getVCR()->virginSaveFile();
|
||||
|
||||
delete tempFile;
|
||||
delete saveFile;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
if (_engine->_savePointHeaders)
|
||||
_engine->getMemoryManager()->freeMem(_engine->_savePointHeaders);
|
||||
|
||||
_engine->_savePointHeaders = (SVCRSavePointHeader *)_engine->getMemoryManager()->allocMem(
|
||||
sizeof(SVCRSavePointHeader) * (fileHeader.numVCRGames + 1),
|
||||
_engine->_savegameFilename,
|
||||
kCharacterMaster
|
||||
);
|
||||
|
||||
if (!_engine->_savePointHeaders) {
|
||||
error("Out of memory");
|
||||
}
|
||||
|
||||
_engine->_savePointHeaders->time = _engine->isDemo() ? 2241000 : 1037700;
|
||||
_engine->_savePointHeaders->partNumber = _engine->isDemo() ? 3 : 1;
|
||||
}
|
||||
|
||||
int offset = sizeof(SVCRFileHeader);
|
||||
tempFile->open("temp.egg", CVCRMODE_WB);
|
||||
tempFile->seek(sizeof(SVCRFileHeader), 0);
|
||||
tempFile->close();
|
||||
fileHeader.realWritePos = sizeof(SVCRFileHeader);
|
||||
fileHeader.nextWritePos = sizeof(SVCRFileHeader);
|
||||
fileHeader.lastIsTemporary = 0;
|
||||
int numSavePoints = 0;
|
||||
|
||||
for (int j = 0; true; j++) {
|
||||
if (fileSize < (int32)sizeof(SVCRFileHeader) || j >= fileHeader.numVCRGames) {
|
||||
saveFile->close();
|
||||
fileHeader.numVCRGames = numSavePoints;
|
||||
tempFile->open("temp.egg", CVCRMODE_RWB);
|
||||
tempFile->seek(0, SEEK_SET);
|
||||
tempFile->write(&fileHeader, sizeof(SVCRFileHeader), 1, false);
|
||||
tempFile->seek(offset, SEEK_SET);
|
||||
tempFile->close();
|
||||
|
||||
if (removeSavegame(_engine->_savegameFilename)) {
|
||||
error("Error deleting file \"%s\"", _engine->_savegameFilename);
|
||||
} else if (renameSavegame("temp.egg", _engine->_savegameFilename)) {
|
||||
error("Error renaming file \"%s\" to \"%s\"", "temp.egg", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
delete tempFile;
|
||||
delete saveFile;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_engine->getSoundManager()->soundThread();
|
||||
saveFile->read(&savePointHeader, sizeof(SVCRSavePointHeader), 1, false, true);
|
||||
if (flag) {
|
||||
memcpy(&_engine->_savePointHeaders[j + 1], &savePointHeader, sizeof(savePointHeader));
|
||||
}
|
||||
|
||||
fileSize -= sizeof(SVCRSavePointHeader);
|
||||
if (fileSize < 0)
|
||||
break;
|
||||
|
||||
if (checkSavePointHeader(&savePointHeader)) {
|
||||
fileSize -= savePointHeader.size;
|
||||
if (fileSize < 0)
|
||||
break;
|
||||
|
||||
numSavePoints++;
|
||||
|
||||
byte *tempMem = (byte *)malloc(savePointHeader.size);
|
||||
|
||||
saveFile->read(tempMem, savePointHeader.size, 1, false, true);
|
||||
tempFile->open("temp.egg", CVCRMODE_RWB);
|
||||
tempFile->seek(offset, SEEK_SET);
|
||||
|
||||
if (savePointHeader.type != 3 && savePointHeader.type != 4)
|
||||
fileHeader.realWritePos = offset;
|
||||
|
||||
tempFile->write(&savePointHeader, sizeof(SVCRSavePointHeader), 1, false);
|
||||
tempFile->write(tempMem, savePointHeader.size, 1, false);
|
||||
tempFile->close();
|
||||
|
||||
free(tempMem);
|
||||
|
||||
offset += savePointHeader.size + sizeof(SVCRSavePointHeader);
|
||||
if (savePointHeader.type == 3 || savePointHeader.type == 4) {
|
||||
fileHeader.lastIsTemporary = 1;
|
||||
} else {
|
||||
fileHeader.lastIsTemporary = 0;
|
||||
fileHeader.nextWritePos = offset;
|
||||
}
|
||||
} else {
|
||||
fileSize = 0;
|
||||
error("Attempting to salvage corrupt save game file \"%s\"", _engine->_savegameFilename);
|
||||
}
|
||||
}
|
||||
|
||||
error("Attempting to salvage corrupt save game file \"%s\"", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
delete tempFile;
|
||||
delete saveFile;
|
||||
}
|
||||
|
||||
bool SaveManager::checkFileHeader(SVCRFileHeader *fileHeader) {
|
||||
if (fileHeader->magicNumber == 0x12001200 &&
|
||||
fileHeader->numVCRGames >= 0 &&
|
||||
fileHeader->nextWritePos >= (int32)sizeof(SVCRFileHeader) &&
|
||||
fileHeader->realWritePos >= (int32)sizeof(SVCRFileHeader) &&
|
||||
fileHeader->lastIsTemporary < 2 &&
|
||||
fileHeader->brightness <= 6 &&
|
||||
fileHeader->volume < 8) {
|
||||
|
||||
if (fileHeader->saveVersion == 9) {
|
||||
return true;
|
||||
} else {
|
||||
error("Save game file \"%s\" is incompatible with this version of the game", _engine->_savegameFilename);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SaveManager::checkSavePointHeader(SVCRSavePointHeader *savePointHeader) {
|
||||
if ((uint32)savePointHeader->magicNumber == 0xE660E660) {
|
||||
if (savePointHeader->type > 0 && savePointHeader->type <= 5) {
|
||||
if (savePointHeader->time >= 1061100 && savePointHeader->time <= 4941000) {
|
||||
if (savePointHeader->size > 0 && (savePointHeader->size & 0xF) == 0 && savePointHeader->partNumber > 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SaveManager::continueGame() {
|
||||
uint8 character;
|
||||
int saveEvent;
|
||||
int saveType;
|
||||
SVCRFileHeader header;
|
||||
|
||||
_engine->_savegame->open(_engine->_savegameFilename, CVCRMODE_RB);
|
||||
_engine->_savegame->read(&header, sizeof(SVCRFileHeader), 1, false, true);
|
||||
|
||||
if (checkFileHeader(&header)) {
|
||||
_engine->_savegame->seek(header.realWritePos, SEEK_SET);
|
||||
readSavePoint(_engine->_savegame, &saveType, &character, &saveEvent, header.lastIsTemporary);
|
||||
_engine->getLogicManager()->_lastSavegameSessionTicks = _engine->getLogicManager()->_realTime;
|
||||
|
||||
if (header.lastIsTemporary) {
|
||||
_engine->getSoundManager()->destroyAllSound();
|
||||
readSavePoint(_engine->_savegame, &saveType, &character, &saveEvent, 0);
|
||||
}
|
||||
|
||||
_engine->_savegame->close();
|
||||
_engine->getOtisManager()->wipeAllGSysInfo();
|
||||
_engine->getLogicManager()->CONS_All(false, character);
|
||||
} else {
|
||||
_engine->_savegame->close();
|
||||
}
|
||||
}
|
||||
|
||||
void SaveManager::startRewoundGame() {
|
||||
SVCRFileHeader header;
|
||||
SVCRSavePointHeader savePointHeader;
|
||||
int saveEvent;
|
||||
int saveType;
|
||||
uint8 character = 0;
|
||||
|
||||
CVCRFile *saveFile = new CVCRFile(_engine);
|
||||
byte *buf = (byte *)malloc(0x2000);
|
||||
|
||||
_engine->_fightSkipCounter = 0;
|
||||
|
||||
_engine->_savegame->open(_engine->_savegameNames[_engine->_currentGameFileColorId], CVCRMODE_RB);
|
||||
_engine->_savegameFilename = _engine->_savegameTempNames[_engine->_currentGameFileColorId];
|
||||
saveFile->open(_engine->_savegameFilename, CVCRMODE_WB);
|
||||
|
||||
header.nextWritePos = sizeof(SVCRFileHeader);
|
||||
header.numVCRGames = _engine->_currentSavePoint;
|
||||
header.realWritePos = sizeof(SVCRFileHeader);
|
||||
header.magicNumber = 0x12001200;
|
||||
header.lastIsTemporary = 0;
|
||||
header.brightness = _engine->getGraphicsManager()->getGammaLevel();
|
||||
header.volume = _engine->getSoundManager()->getMasterVolume();
|
||||
header.saveVersion = 9;
|
||||
saveFile->write(&header, sizeof(header), 1, false);
|
||||
|
||||
_engine->_savegame->seek(sizeof(header), SEEK_SET);
|
||||
|
||||
if (_engine->_currentSavePoint > 1) {
|
||||
int count = _engine->_currentSavePoint - 1;
|
||||
do {
|
||||
_engine->_savegame->read(&savePointHeader, sizeof(savePointHeader), 1, false, true);
|
||||
saveFile->write(&savePointHeader, sizeof(savePointHeader), 1, false);
|
||||
for (; savePointHeader.size > 0x2000; savePointHeader.size -= 0x2000) {
|
||||
_engine->_savegame->read(buf, 0x2000, 1, false, true);
|
||||
saveFile->write(buf, 0x2000, 1, false);
|
||||
}
|
||||
_engine->_savegame->read(buf, savePointHeader.size, 1, false, true);
|
||||
saveFile->write(buf, savePointHeader.size, 1, false);
|
||||
--count;
|
||||
} while (count);
|
||||
}
|
||||
|
||||
free(buf);
|
||||
buf = nullptr;
|
||||
|
||||
if (_engine->_currentSavePoint) {
|
||||
readSavePoint(_engine->_savegame, &saveType, &character, &saveEvent, false);
|
||||
_engine->_savegame->close();
|
||||
_engine->getLogicManager()->_lastSavegameSessionTicks = _engine->getLogicManager()->_realTime;
|
||||
header.realWritePos = saveFile->tell();
|
||||
|
||||
writeSavePoint(saveFile, saveType, character, saveEvent);
|
||||
|
||||
header.nextWritePos = saveFile->tell();
|
||||
checkFileHeader(&header);
|
||||
saveFile->seek(0, SEEK_SET);
|
||||
saveFile->write(&header, sizeof(header), 1, false);
|
||||
}
|
||||
|
||||
_engine->_savegame->close();
|
||||
saveFile->close();
|
||||
delete saveFile;
|
||||
|
||||
_engine->_gracePeriodIndex = _engine->_currentSavePoint;
|
||||
_engine->_gracePeriodTimer = _engine->getLogicManager()->_globals[kGlobalJacket] < 2 ? 225 : 450;
|
||||
|
||||
if (_engine->_currentSavePoint) {
|
||||
_engine->getOtisManager()->wipeAllGSysInfo();
|
||||
_engine->getLogicManager()->CONS_All(false, character);
|
||||
} else {
|
||||
_engine->startNewGame();
|
||||
}
|
||||
}
|
||||
|
||||
bool SaveManager::fileExists(const char *filename) {
|
||||
return g_system->getSavefileManager()->exists(_engine->getTargetName() + "-" + Common::String(filename));
|
||||
}
|
||||
|
||||
bool SaveManager::removeSavegame(const char *filename) {
|
||||
return !g_system->getSavefileManager()->removeSavefile(_engine->getTargetName() + "-" + Common::String(filename));
|
||||
}
|
||||
|
||||
bool SaveManager::renameSavegame(const char *oldName, const char *newName) {
|
||||
return !g_system->getSavefileManager()->renameSavefile(
|
||||
_engine->getTargetName() + "-" + Common::String(oldName),
|
||||
_engine->getTargetName() + "-" + Common::String(newName), false);
|
||||
}
|
||||
|
||||
} // End of namespace LastExpress
|
||||
57
engines/lastexpress/game/savegame.h
Normal file
57
engines/lastexpress/game/savegame.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LASTEXPRESS_SAVELOAD_H
|
||||
#define LASTEXPRESS_SAVELOAD_H
|
||||
|
||||
#include "lastexpress/lastexpress.h"
|
||||
|
||||
namespace LastExpress {
|
||||
|
||||
class LastExpressEngine;
|
||||
class CVCRFile;
|
||||
struct SVCRFileHeader;
|
||||
struct SVCRSavePointHeader;
|
||||
|
||||
class SaveManager {
|
||||
public:
|
||||
SaveManager(LastExpressEngine *engine);
|
||||
~SaveManager() {}
|
||||
|
||||
void writeSavePoint(CVCRFile *file, int saveType, int entityIndex, int value);
|
||||
void readSavePoint(CVCRFile *savegameData, int *saveType, uint8 *entity, int *saveEvent, bool skipSoundLoading);
|
||||
void validateSaveFile(bool flag);
|
||||
bool checkFileHeader(SVCRFileHeader *fileHeader);
|
||||
bool checkSavePointHeader(SVCRSavePointHeader *savePointHeader);
|
||||
void continueGame();
|
||||
void startRewoundGame();
|
||||
bool fileExists(const char *filename);
|
||||
bool removeSavegame(const char *filename);
|
||||
bool renameSavegame(const char *oldName, const char *newName);
|
||||
|
||||
private:
|
||||
LastExpressEngine *_engine = nullptr;
|
||||
|
||||
};
|
||||
|
||||
} // End of namespace LastExpress
|
||||
|
||||
#endif // LASTEXPRESS_SAVELOAD_H
|
||||
709
engines/lastexpress/game/vcr.cpp
Normal file
709
engines/lastexpress/game/vcr.cpp
Normal file
@@ -0,0 +1,709 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "lastexpress/lastexpress.h"
|
||||
#include "lastexpress/data/cvcrfile.h"
|
||||
#include "lastexpress/menu/clock.h"
|
||||
#include "lastexpress/game/vcr.h"
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "common/savefile.h"
|
||||
|
||||
#include "engines/metaengine.h"
|
||||
|
||||
namespace LastExpress {
|
||||
|
||||
VCR::VCR(LastExpressEngine *engine) {
|
||||
_engine = engine;
|
||||
}
|
||||
|
||||
void VCR::virginSaveFile() {
|
||||
CVCRFile *saveFile = new CVCRFile(_engine);
|
||||
SVCRFileHeader fileHeader;
|
||||
|
||||
assert(_engine->_savegameFilename);
|
||||
|
||||
fileHeader.magicNumber = 0x12001200;
|
||||
fileHeader.numVCRGames = 0;
|
||||
fileHeader.nextWritePos = sizeof(SVCRFileHeader);
|
||||
fileHeader.realWritePos = sizeof(SVCRFileHeader);
|
||||
fileHeader.lastIsTemporary = 0;
|
||||
fileHeader.brightness = _engine->getGraphicsManager()->getGammaLevel();
|
||||
fileHeader.saveVersion = 9;
|
||||
fileHeader.volume = _engine->_soundMan->getMasterVolume();
|
||||
|
||||
saveFile->open(_engine->_savegameFilename, CVCRMODE_WB);
|
||||
saveFile->write(&fileHeader, sizeof(SVCRFileHeader), 1, 0);
|
||||
saveFile->close();
|
||||
delete saveFile;
|
||||
}
|
||||
|
||||
void VCR::writeSavePoint(int type, int entity, int event) {
|
||||
SVCRSavePointHeader savePointHeader;
|
||||
SVCRFileHeader fileHeader;
|
||||
|
||||
CVCRFile *saveFile = new CVCRFile(_engine);
|
||||
|
||||
if (_engine->getLogicManager()->_activeNode <= 30) {
|
||||
delete saveFile;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_engine->_savegameTempNames[_engine->_currentGameFileColorId] == _engine->_savegameFilename && !_engine->_gracePeriodTimer) {
|
||||
makePermanent();
|
||||
}
|
||||
|
||||
saveFile->open(_engine->_savegameFilename, CVCRMODE_RWB);
|
||||
saveFile->read(&fileHeader, 32, 1, 0, 1);
|
||||
|
||||
if (!_engine->getSaveManager()->checkFileHeader(&fileHeader)) {
|
||||
saveFile->close();
|
||||
delete saveFile;
|
||||
return;
|
||||
}
|
||||
|
||||
saveFile->seek(fileHeader.nextWritePos, 0);
|
||||
|
||||
if (fileHeader.numVCRGames > 0) {
|
||||
saveFile->seek(fileHeader.realWritePos, 0);
|
||||
saveFile->read(&savePointHeader, 32, 1, 0, 1);
|
||||
|
||||
if ((!_engine->getSaveManager()->checkSavePointHeader(&savePointHeader)) ||
|
||||
(savePointHeader.time > _engine->getLogicManager()->_gameTime) ||
|
||||
(type == 5 && savePointHeader.time == _engine->getLogicManager()->_gameTime)) {
|
||||
saveFile->close();
|
||||
delete saveFile;
|
||||
return;
|
||||
}
|
||||
|
||||
saveFile->seek(fileHeader.nextWritePos, 0);
|
||||
if ((type == 1 || type == 2) && savePointHeader.type == 5 && (_engine->getLogicManager()->_gameTime - savePointHeader.time) < 2700) {
|
||||
saveFile->seek(fileHeader.realWritePos, 0);
|
||||
fileHeader.numVCRGames--;
|
||||
}
|
||||
}
|
||||
|
||||
if (type != 3 && type != 4)
|
||||
fileHeader.realWritePos = saveFile->tell();
|
||||
|
||||
_engine->getSaveManager()->writeSavePoint(saveFile, type, entity, event);
|
||||
|
||||
if (!fileHeader.lastIsTemporary)
|
||||
++fileHeader.numVCRGames;
|
||||
|
||||
if (type == 3 || type == 4) {
|
||||
fileHeader.lastIsTemporary = 1;
|
||||
} else {
|
||||
fileHeader.lastIsTemporary = 0;
|
||||
fileHeader.nextWritePos = saveFile->tell();
|
||||
_engine->getLogicManager()->_lastSavegameSessionTicks = _engine->getLogicManager()->_realTime;
|
||||
}
|
||||
|
||||
_engine->getSaveManager()->checkFileHeader(&fileHeader);
|
||||
|
||||
saveFile->seek(0, 0);
|
||||
saveFile->write(&fileHeader, sizeof(SVCRFileHeader), 1, 0);
|
||||
saveFile->close();
|
||||
delete saveFile;
|
||||
}
|
||||
|
||||
void VCR::selectFromName(const char *filename) {
|
||||
// Search through valid savegame names...
|
||||
for (int saveNameIndex = 0; saveNameIndex < 6; saveNameIndex++) {
|
||||
Common::String curSaveName = _engine->getTargetName() + "-" + _engine->_savegameNames[saveNameIndex];
|
||||
|
||||
if (Common::String(filename).equalsIgnoreCase(curSaveName)) {
|
||||
// Found a match!
|
||||
setCurrentGameColor(saveNameIndex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VCR::shuffleGames() {
|
||||
int currentSlot;
|
||||
|
||||
// Process all save game slots
|
||||
for (currentSlot = 0; currentSlot < ARRAYSIZE(_engine->_savegameNames); currentSlot++) {
|
||||
|
||||
const char *currentFile = _engine->_savegameNames[currentSlot];
|
||||
|
||||
Common::InSaveFile *saveFile = _engine->getSaveFileManager()->openForLoading(_engine->getTargetName() + "-" + Common::String(currentFile));
|
||||
|
||||
bool slotFilled = false;
|
||||
|
||||
// Check if current slot has a valid save
|
||||
if (saveFile) {
|
||||
if (saveFile->size() <= 32) {
|
||||
delete saveFile;
|
||||
|
||||
// Remove invalid/corrupted save files
|
||||
if (_engine->getSaveManager()->removeSavegame(currentFile) != 0) {
|
||||
error("Error deleting file \"%s\"", currentFile);
|
||||
}
|
||||
|
||||
// Also remove the timestamp
|
||||
Common::String tsName = currentFile;
|
||||
tsName.chop(4);
|
||||
tsName = _engine->getTargetName() + "-" + tsName + ".timestamp";
|
||||
|
||||
if (g_system->getSavefileManager()->exists(tsName))
|
||||
g_system->getSavefileManager()->removeSavefile(tsName);
|
||||
} else {
|
||||
slotFilled = true;
|
||||
delete saveFile;
|
||||
}
|
||||
}
|
||||
|
||||
// If slot is empty, try to find a valid save to move here
|
||||
if (!slotFilled && currentSlot < ARRAYSIZE(_engine->_savegameNames) - 1) {
|
||||
for (const char **candidateFile = &_engine->_savegameNames[currentSlot + 1];
|
||||
candidateFile < _engine->_savegameTempNames;
|
||||
candidateFile++) {
|
||||
|
||||
Common::InSaveFile *candidateSave = _engine->getSaveFileManager()->openForLoading(_engine->getTargetName() + "-" + Common::String(*candidateFile));
|
||||
|
||||
if (candidateSave) {
|
||||
if (candidateSave->size() > 32) {
|
||||
delete candidateSave;
|
||||
|
||||
// Move this valid save to the empty slot
|
||||
if (_engine->getSaveManager()->renameSavegame(*candidateFile, currentFile) != 0) {
|
||||
error("Error renaming file \"%s\" to \"%s\"", *candidateFile, currentFile);
|
||||
}
|
||||
|
||||
slotFilled = true;
|
||||
break;
|
||||
} else {
|
||||
delete candidateSave;
|
||||
|
||||
// Remove invalid candidate files
|
||||
if (_engine->getSaveManager()->removeSavegame(*candidateFile) != 0) {
|
||||
error("Error deleting file \"%s\"", *candidateFile);
|
||||
}
|
||||
|
||||
// Also remove the timestamp
|
||||
Common::String tsName = *candidateFile;
|
||||
tsName.chop(4);
|
||||
tsName = _engine->getTargetName() + "-" + tsName + ".timestamp";
|
||||
|
||||
if (g_system->getSavefileManager()->exists(tsName))
|
||||
g_system->getSavefileManager()->removeSavefile(tsName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the current game color based on the most recent save...
|
||||
Common::String currentSaveName = _engine->getTargetName() + "-" + Common::String(_engine->_savegameNames[currentSlot % 6]);
|
||||
if (_engine->_currentGameFileColorId == -1 || !_engine->getSaveFileManager()->exists(currentSaveName)) {
|
||||
setCurrentGameColor(0); // Default color
|
||||
int32 newestSaveSecs = 0;
|
||||
uint32 newestSaveTime = 0;
|
||||
uint32 newestSaveDate = 0;
|
||||
|
||||
// Find the most recently modified save file
|
||||
for (int i = 0; i < 6; i++) {
|
||||
Common::String tsFilename = _engine->_savegameNames[i];
|
||||
tsFilename.chop(4);
|
||||
tsFilename = _engine->getTargetName() + "-" + tsFilename + ".timestamp";
|
||||
|
||||
Common::InSaveFile *saveFile = _engine->getSaveFileManager()->openForLoading(tsFilename);
|
||||
|
||||
if (saveFile) {
|
||||
int32 eshSecs = saveFile->readSint32LE();
|
||||
|
||||
ExtendedSavegameHeader esh;
|
||||
if (_engine->getMetaEngine()->readSavegameHeader(saveFile, &esh)) {
|
||||
if (esh.date > newestSaveDate ||
|
||||
(esh.date == newestSaveDate && esh.time > newestSaveTime) ||
|
||||
(esh.date == newestSaveDate && esh.time == newestSaveTime && eshSecs > newestSaveSecs)) {
|
||||
newestSaveSecs = eshSecs;
|
||||
newestSaveTime = esh.time;
|
||||
newestSaveDate = esh.date;
|
||||
|
||||
setCurrentGameColor(i);
|
||||
}
|
||||
|
||||
// SwapBytesVCR(i);
|
||||
}
|
||||
}
|
||||
|
||||
delete saveFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VCR::setCurrentGameColor(int index) {
|
||||
_engine->_currentGameFileColorId = index;
|
||||
_engine->_savegameFilename = _engine->_savegameNames[index];
|
||||
}
|
||||
|
||||
void VCR::init(bool doSaveGameFlag, int saveType, int32 time) {
|
||||
int cdNum;
|
||||
SVCRFileHeader header;
|
||||
char path[80];
|
||||
|
||||
int32 chosenTime = 0;
|
||||
bool flag = true;
|
||||
bool writeSavePoint = false;
|
||||
|
||||
if (_engine->_savegame && _engine->_savegame->fileIsOpen())
|
||||
_engine->_savegame->close();
|
||||
|
||||
if (_engine->_gracePeriodTimer) {
|
||||
if (_engine->isDemo()) {
|
||||
time = 0;
|
||||
_engine->_gracePeriodTimer = 0;
|
||||
doSaveGameFlag = false;
|
||||
|
||||
if (_engine->getSaveManager()->removeSavegame(_engine->_savegameFilename)) {
|
||||
error("Error deleting file \"%s\"", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
_engine->_currentSavePoint = _engine->_gracePeriodIndex;
|
||||
flag = false;
|
||||
_engine->_savegameFilename = _engine->_savegameNames[_engine->_currentGameFileColorId];
|
||||
} else {
|
||||
chosenTime = 0;
|
||||
|
||||
if (_engine->getLogicManager()->_globals[kGlobalChapter] <= 1) {
|
||||
cdNum = 1;
|
||||
} else {
|
||||
cdNum = (_engine->getLogicManager()->_globals[kGlobalChapter] > 3) + 2;
|
||||
}
|
||||
|
||||
if (_engine->getArchiveManager()->isCDAvailable(cdNum, path, sizeof(path))) {
|
||||
writeSavePoint = 0;
|
||||
_engine->_gracePeriodTimer = 0;
|
||||
|
||||
if (_engine->getSaveManager()->removeSavegame(_engine->_savegameFilename)) {
|
||||
error("Error deleting file \"%s\"", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
flag = false;
|
||||
_engine->_currentSavePoint = _engine->_gracePeriodIndex;
|
||||
_engine->_savegameFilename = _engine->_savegameNames[_engine->_currentGameFileColorId];
|
||||
} else {
|
||||
writeSavePoint = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_engine->_savegameFilename == _engine->_savegameTempNames[_engine->_currentGameFileColorId])
|
||||
_engine->getVCR()->makePermanent();
|
||||
|
||||
if (!_engine->isDemo()) {
|
||||
writeSavePoint = doSaveGameFlag;
|
||||
chosenTime = time;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_engine->getSaveManager()->fileExists(_engine->_savegameFilename))
|
||||
_engine->getVCR()->virginSaveFile();
|
||||
|
||||
if (_engine->isDemo()) {
|
||||
if (doSaveGameFlag)
|
||||
_engine->getVCR()->writeSavePoint(3, kCharacterCath, 0);
|
||||
} else {
|
||||
if (writeSavePoint)
|
||||
_engine->getVCR()->writeSavePoint(3, kCharacterCath, 0);
|
||||
}
|
||||
|
||||
if (!_engine->_gracePeriodTimer &&
|
||||
_engine->getSaveManager()->fileExists(_engine->_savegameTempNames[_engine->_currentGameFileColorId]) &&
|
||||
_engine->getSaveManager()->removeSavegame(_engine->_savegameTempNames[_engine->_currentGameFileColorId])
|
||||
) {
|
||||
error("Error deleting file \"%s\"", _engine->_savegameTempNames[_engine->_currentGameFileColorId]);
|
||||
}
|
||||
|
||||
_engine->getSaveManager()->validateSaveFile(true);
|
||||
|
||||
if (!_engine->_savegame)
|
||||
_engine->_savegame = new CVCRFile(_engine);
|
||||
|
||||
_engine->_savegame->open(_engine->_savegameFilename, CVCRMODE_RB);
|
||||
_engine->_savegame->read(&header, sizeof(SVCRFileHeader), 1, false, true);
|
||||
|
||||
if (!_engine->getSaveManager()->checkFileHeader(&header)) {
|
||||
_engine->_savegame->close();
|
||||
error("Save game file \"%s\" is corrupt", _engine->_savegameFilename);
|
||||
}
|
||||
|
||||
_engine->_lastSavePointIdInFile = header.numVCRGames;
|
||||
_engine->_gameTimeOfLastSavePointInFile = _engine->_savePointHeaders[header.numVCRGames].time;
|
||||
|
||||
if (flag)
|
||||
_engine->_currentSavePoint = _engine->_lastSavePointIdInFile;
|
||||
|
||||
if (!_engine->_gracePeriodTimer)
|
||||
_engine->_gracePeriodIndex = 0;
|
||||
|
||||
if (!_engine->getLogicManager()->_globals[kGlobalChapter])
|
||||
_engine->getLogicManager()->_globals[kGlobalChapter] = 1;
|
||||
|
||||
_engine->getLogicManager()->_gameTime = _engine->_savePointHeaders[_engine->_currentSavePoint].time;
|
||||
_engine->getLogicManager()->_globals[kGlobalChapter] = _engine->_savePointHeaders[_engine->_currentSavePoint].partNumber;
|
||||
|
||||
if (_engine->_gameTimeOfLastSavePointInFile >= 1061100) {
|
||||
_engine->getClock()->startClock(_engine->getLogicManager()->_gameTime);
|
||||
|
||||
if (_engine->isDemo()) {
|
||||
// Demo: use modified time parameter
|
||||
_engine->getVCR()->autoRewind(saveType, time);
|
||||
} else {
|
||||
_engine->getVCR()->autoRewind(saveType, chosenTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VCR::autoRewind(int saveType, int32 time) {
|
||||
int selectedIdx = 0;
|
||||
|
||||
if (time) {
|
||||
switch (saveType) {
|
||||
case 0:
|
||||
if (_engine->_currentSavePoint <= time) {
|
||||
selectedIdx = 1;
|
||||
} else {
|
||||
selectedIdx = _engine->_currentSavePoint - time;
|
||||
}
|
||||
|
||||
break;
|
||||
case 1:
|
||||
if (time < 1061100)
|
||||
break;
|
||||
|
||||
if (!_engine->_currentSavePoint)
|
||||
return;
|
||||
|
||||
selectedIdx = _engine->_currentSavePoint;
|
||||
do {
|
||||
if (_engine->_savePointHeaders[selectedIdx].time <= time)
|
||||
break;
|
||||
|
||||
selectedIdx--;
|
||||
} while (selectedIdx);
|
||||
|
||||
break;
|
||||
case 2:
|
||||
if (!_engine->_currentSavePoint)
|
||||
return;
|
||||
|
||||
selectedIdx = _engine->_currentSavePoint;
|
||||
do {
|
||||
if (_engine->_savePointHeaders[selectedIdx].latestGameEvent == time)
|
||||
break;
|
||||
|
||||
selectedIdx--;
|
||||
} while (selectedIdx);
|
||||
|
||||
break;
|
||||
case 3:
|
||||
selectedIdx = _engine->_currentSavePoint;
|
||||
if (_engine->_currentSavePoint > 1) {
|
||||
do {
|
||||
if (_engine->_savePointHeaders[selectedIdx].latestGameEvent == time)
|
||||
break;
|
||||
|
||||
selectedIdx--;
|
||||
} while (selectedIdx != 1);
|
||||
}
|
||||
|
||||
selectedIdx--;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (selectedIdx) {
|
||||
_currentSavePointInVCR = selectedIdx;
|
||||
_engine->getClock()->setClock(_engine->_savePointHeaders[selectedIdx].time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VCR::free() {
|
||||
_engine->getClock()->endClock();
|
||||
|
||||
if (_engine->_savePointHeaders) {
|
||||
_engine->getMemoryManager()->freeMem(_engine->_savePointHeaders);
|
||||
_engine->_savePointHeaders = nullptr;
|
||||
}
|
||||
|
||||
_engine->_savegame->close();
|
||||
}
|
||||
|
||||
bool VCR::isVirgin(int savegameIndex) {
|
||||
SVCRFileHeader header;
|
||||
CVCRFile *file = new CVCRFile(_engine);
|
||||
|
||||
if (_engine->getSaveManager()->fileExists(_engine->_savegameNames[savegameIndex]) &&
|
||||
file->open(_engine->_savegameNames[savegameIndex], CVCRMODE_RB)) {
|
||||
if (file->read(&header, sizeof(SVCRFileHeader), 1, false, false) &&
|
||||
_engine->getSaveManager()->checkFileHeader(&header) && header.numVCRGames > 0) {
|
||||
file->close();
|
||||
delete file;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
file->close();
|
||||
delete file;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VCR::currentEndsGame() {
|
||||
SVCRSavePointHeader *header = &_engine->_savePointHeaders[_engine->_currentSavePoint];
|
||||
int32 latestGameEvent = header->latestGameEvent;
|
||||
|
||||
return _engine->_lastSavePointIdInFile == _engine->_currentSavePoint && header->type == 2 &&
|
||||
( latestGameEvent == kEventAnnaKilled
|
||||
|| latestGameEvent == kEventKronosHostageAnnaNoFirebird
|
||||
|| latestGameEvent == kEventKahinaPunchBaggageCarEntrance
|
||||
|| latestGameEvent == kEventKahinaPunchBlue
|
||||
|| latestGameEvent == kEventKahinaPunchYellow
|
||||
|| latestGameEvent == kEventKahinaPunchSalon
|
||||
|| latestGameEvent == kEventKahinaPunchKitchen
|
||||
|| latestGameEvent == kEventKahinaPunchBaggageCar
|
||||
|| latestGameEvent == kEventKahinaPunchCar
|
||||
|| latestGameEvent == kEventKahinaPunchSuite4
|
||||
|| latestGameEvent == kEventKahinaPunchRestaurant
|
||||
|| latestGameEvent == kEventKahinaPunch
|
||||
|| latestGameEvent == kEventKronosGiveFirebird
|
||||
|| latestGameEvent == kEventAugustFindCorpse
|
||||
|| latestGameEvent == kEventMertensBloodJacket
|
||||
|| latestGameEvent == kEventMertensCorpseFloor
|
||||
|| latestGameEvent == kEventMertensCorpseBed
|
||||
|| latestGameEvent == kEventCoudertBloodJacket
|
||||
|| latestGameEvent == kEventGendarmesArrestation
|
||||
|| latestGameEvent == kEventAbbotDrinkGiveDetonator
|
||||
|| latestGameEvent == kEventMilosCorpseFloor
|
||||
|| latestGameEvent == kEventLocomotiveAnnaStopsTrain
|
||||
|| latestGameEvent == kEventTrainStopped
|
||||
|| latestGameEvent == kEventCathVesnaRestaurantKilled
|
||||
|| latestGameEvent == kEventCathVesnaTrainTopKilled
|
||||
|| latestGameEvent == kEventLocomotiveConductorsDiscovered
|
||||
|| latestGameEvent == kEventViennaAugustUnloadGuns
|
||||
|| latestGameEvent == kEventViennaKronosFirebird
|
||||
|| latestGameEvent == kEventVergesAnnaDead
|
||||
|| latestGameEvent == kEventTrainExplosionBridge
|
||||
|| latestGameEvent == kEventKronosBringNothing );
|
||||
}
|
||||
|
||||
bool VCR::makePermanent() {
|
||||
if (_engine->getSaveManager()->removeSavegame(_engine->_savegameNames[_engine->_currentGameFileColorId])) {
|
||||
error("Error deleting file \"%s\"", _engine->_savegameNames[_engine->_currentGameFileColorId]);
|
||||
return false;
|
||||
} else if (_engine->getSaveManager()->renameSavegame(_engine->_savegameFilename, _engine->_savegameNames[_engine->_currentGameFileColorId])) {
|
||||
error("Error renaming file \"%s\" to \"%s\"", _engine->_savegameFilename, _engine->_savegameNames[_engine->_currentGameFileColorId]);
|
||||
return false;
|
||||
} else {
|
||||
_engine->_savegameFilename = _engine->_savegameNames[_engine->_currentGameFileColorId];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int VCR::switchGames() {
|
||||
int index = 0;
|
||||
|
||||
if (!isVirgin(_engine->_currentGameFileColorId)) {
|
||||
index = (_engine->_currentGameFileColorId + 1) % 6;
|
||||
}
|
||||
|
||||
_engine->_currentGameFileColorId = index;
|
||||
_engine->_savegameFilename = _engine->_savegameNames[index];
|
||||
|
||||
if (!_engine->getSaveManager()->fileExists(_engine->_savegameFilename))
|
||||
virginSaveFile();
|
||||
|
||||
_engine->getLogicManager()->_gameTime = 0;
|
||||
_engine->getClock()->turnOnClock(false);
|
||||
|
||||
if (_engine->_savePointHeaders) {
|
||||
_engine->getMemoryManager()->freeMem(_engine->_savePointHeaders);
|
||||
_engine->_savePointHeaders = nullptr;
|
||||
}
|
||||
|
||||
_engine->_savegame->close();
|
||||
storeSettings();
|
||||
init(false, 0, 0);
|
||||
|
||||
return _engine->_currentGameFileColorId;
|
||||
}
|
||||
|
||||
void VCR::storeSettings() {
|
||||
SVCRFileHeader header;
|
||||
CVCRFile *file = new CVCRFile(_engine);
|
||||
|
||||
file->open(_engine->_savegameFilename, CVCRMODE_RWB);
|
||||
|
||||
if (file->read(&header, sizeof(SVCRFileHeader), 1, false, false) == 1) {
|
||||
if (!_engine->getSaveManager()->checkFileHeader(&header)) {
|
||||
file->close();
|
||||
delete file;
|
||||
return;
|
||||
}
|
||||
|
||||
header.brightness = _engine->getGraphicsManager()->getGammaLevel();
|
||||
header.volume = _engine->getSoundManager()->getMasterVolume();
|
||||
|
||||
file->seek(0, SEEK_SET);
|
||||
file->write(&header, sizeof(SVCRFileHeader), 1, false);
|
||||
file->close();
|
||||
} else {
|
||||
file->close();
|
||||
virginSaveFile();
|
||||
}
|
||||
|
||||
delete file;
|
||||
}
|
||||
|
||||
void VCR::loadSettings() {
|
||||
SVCRFileHeader header;
|
||||
CVCRFile *file = new CVCRFile(_engine);
|
||||
|
||||
if (_engine->getSaveManager()->fileExists(_engine->_savegameFilename)) {
|
||||
file->open(_engine->_savegameFilename, CVCRMODE_RB);
|
||||
|
||||
if (file->read(&header, sizeof(SVCRFileHeader), 1, false, false) == 1) {
|
||||
if (_engine->getSaveManager()->checkFileHeader(&header)) {
|
||||
file->seek(0, SEEK_SET);
|
||||
_engine->getGraphicsManager()->setGammaLevel(header.brightness);
|
||||
_engine->getSoundManager()->setMasterVolume(header.volume);
|
||||
}
|
||||
|
||||
file->close();
|
||||
} else {
|
||||
file->close();
|
||||
virginSaveFile();
|
||||
}
|
||||
}
|
||||
|
||||
delete file;
|
||||
|
||||
}
|
||||
|
||||
void VCR::rewind() {
|
||||
if (_engine->_currentSavePoint) {
|
||||
_currentSavePointInVCR = 0;
|
||||
_engine->getClock()->setClock(_engine->_savePointHeaders->time);
|
||||
}
|
||||
}
|
||||
|
||||
void VCR::forward() {
|
||||
if (_engine->_lastSavePointIdInFile > _engine->_currentSavePoint) {
|
||||
_currentSavePointInVCR = _engine->_lastSavePointIdInFile;
|
||||
_engine->getClock()->setClock(_engine->_savePointHeaders[_engine->_lastSavePointIdInFile].time);
|
||||
}
|
||||
}
|
||||
|
||||
void VCR::stop() {
|
||||
_currentSavePointInVCR = _engine->_currentSavePoint;
|
||||
_engine->getClock()->stopClock(_engine->_savePointHeaders[_engine->_currentSavePoint].time);
|
||||
}
|
||||
|
||||
void VCR::seekToTime(int32 time) {
|
||||
int bestIdx = 0;
|
||||
int32 minDiff = ABS<int32>(_engine->_savePointHeaders[0].time - time);
|
||||
|
||||
for (int i = 0; i <= _engine->_lastSavePointIdInFile; i++) {
|
||||
int32 curDiff = ABS<int32>(_engine->_savePointHeaders[i].time - time);
|
||||
if (curDiff < minDiff) {
|
||||
minDiff = curDiff;
|
||||
bestIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
_currentSavePointInVCR = bestIdx;
|
||||
_engine->getClock()->setClock(_engine->_savePointHeaders[bestIdx].time);
|
||||
}
|
||||
|
||||
void VCR::updateCurGame(int32 fromTime, int32 toTime, bool searchEntry) {
|
||||
int32 minTimeDiff = 0x7FFFFFFF;
|
||||
int newMenuIdx = 0;
|
||||
|
||||
if (toTime != fromTime) {
|
||||
newMenuIdx = _engine->_currentSavePoint;
|
||||
|
||||
if (toTime >= fromTime) {
|
||||
if (searchEntry) {
|
||||
for (int idx = _engine->_currentSavePoint; idx >= 0; --idx) {
|
||||
int32 gameTime = _engine->_savePointHeaders[idx].time;
|
||||
if (gameTime <= fromTime && minTimeDiff >= fromTime - gameTime) {
|
||||
minTimeDiff = fromTime - gameTime;
|
||||
newMenuIdx = idx;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newMenuIdx = _engine->_currentSavePoint - 1;
|
||||
}
|
||||
} else if (searchEntry) {
|
||||
for (int idx = _engine->_currentSavePoint; idx <= _engine->_lastSavePointIdInFile; ++idx) {
|
||||
int32 gameTime = _engine->_savePointHeaders[idx].time;
|
||||
if (gameTime >= fromTime && minTimeDiff > gameTime - fromTime) {
|
||||
minTimeDiff = gameTime - fromTime;
|
||||
newMenuIdx = idx;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newMenuIdx = _engine->_currentSavePoint + 1;
|
||||
}
|
||||
|
||||
_engine->_currentSavePoint = newMenuIdx;
|
||||
_engine->getMenu()->updateEgg();
|
||||
}
|
||||
|
||||
if (_engine->_currentSavePoint == _currentSavePointInVCR &&
|
||||
_engine->_savePointHeaders[newMenuIdx].partNumber != _engine->getLogicManager()->_globals[kGlobalChapter]) {
|
||||
_engine->getLogicManager()->_globals[kGlobalChapter] = _engine->_savePointHeaders[_engine->_currentSavePoint].partNumber;
|
||||
}
|
||||
}
|
||||
|
||||
void VCR::go() {
|
||||
free();
|
||||
|
||||
if (_engine->_savegameTempNames[_engine->_currentGameFileColorId] == _engine->_savegameFilename) {
|
||||
if (_engine->_lastSavePointIdInFile == _engine->_currentSavePoint) {
|
||||
_engine->getSaveManager()->continueGame();
|
||||
return;
|
||||
}
|
||||
_engine->getSaveManager()->startRewoundGame();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_engine->_lastSavePointIdInFile != _engine->_currentSavePoint) {
|
||||
_engine->getSaveManager()->startRewoundGame();
|
||||
return;
|
||||
}
|
||||
|
||||
_engine->_gracePeriodTimer = 0;
|
||||
if (_engine->_currentSavePoint) {
|
||||
_engine->getSaveManager()->continueGame();
|
||||
return;
|
||||
}
|
||||
|
||||
_engine->startNewGame();
|
||||
}
|
||||
|
||||
} // End of namespace LastExpress
|
||||
72
engines/lastexpress/game/vcr.h
Normal file
72
engines/lastexpress/game/vcr.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LASTEXPRESS_VCR_H
|
||||
#define LASTEXPRESS_VCR_H
|
||||
|
||||
#include "lastexpress/lastexpress.h"
|
||||
#include "lastexpress/data/cvcrfile.h"
|
||||
|
||||
namespace LastExpress {
|
||||
|
||||
class LastExpressEngine;
|
||||
class CVCRFile;
|
||||
|
||||
struct SVCRFileHeader;
|
||||
struct SVCRSavePointHeader;
|
||||
|
||||
class VCR {
|
||||
|
||||
public:
|
||||
VCR(LastExpressEngine *engine);
|
||||
~VCR() {}
|
||||
|
||||
void virginSaveFile();
|
||||
void writeSavePoint(int type, int entity, int event);
|
||||
void selectFromName(const char *filename);
|
||||
void shuffleGames();
|
||||
void setCurrentGameColor(int index);
|
||||
void init(bool doSaveGameFlag, int saveType, int32 time);
|
||||
void autoRewind(int saveType, int32 time);
|
||||
void free();
|
||||
bool isVirgin(int savegameIndex);
|
||||
bool currentEndsGame();
|
||||
bool makePermanent();
|
||||
int switchGames();
|
||||
void storeSettings();
|
||||
void loadSettings();
|
||||
void rewind();
|
||||
void forward();
|
||||
void stop();
|
||||
void seekToTime(int32 time);
|
||||
void updateCurGame(int32 fromTime, int32 toTime, bool searchEntry);
|
||||
void go();
|
||||
|
||||
protected:
|
||||
LastExpressEngine *_engine;
|
||||
|
||||
int32 _currentSavePointInVCR = 0;
|
||||
|
||||
};
|
||||
|
||||
} // End of namespace LastExpress
|
||||
|
||||
#endif // LASTEXPRESS_VCR_H
|
||||
Reference in New Issue
Block a user