2012-01-26 13:11:43 +01:00
|
|
|
/* ResidualVM - A 3D game interpreter
|
|
|
|
*
|
|
|
|
* ResidualVM is the legal property of its developers, whose names
|
|
|
|
* are too numerous to list here. Please refer to the AUTHORS
|
|
|
|
* 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 2
|
|
|
|
* 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, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "engines/myst3/puzzles.h"
|
|
|
|
#include "engines/myst3/menu.h"
|
|
|
|
#include "engines/myst3/myst3.h"
|
|
|
|
#include "engines/myst3/state.h"
|
2012-01-28 20:38:55 +01:00
|
|
|
#include "engines/myst3/sound.h"
|
2012-01-26 13:11:43 +01:00
|
|
|
|
|
|
|
namespace Myst3 {
|
|
|
|
|
|
|
|
Puzzles::Puzzles(Myst3Engine *vm) :
|
|
|
|
_vm(vm) {
|
|
|
|
}
|
|
|
|
|
|
|
|
Puzzles::~Puzzles() {
|
|
|
|
}
|
|
|
|
|
|
|
|
void Puzzles::run(uint16 id, uint16 arg0, uint16 arg1, uint16 arg2) {
|
|
|
|
switch (id) {
|
2012-01-28 20:38:55 +01:00
|
|
|
case 1:
|
|
|
|
leversBall(arg0);
|
|
|
|
break;
|
2012-01-26 13:11:43 +01:00
|
|
|
case 8:
|
|
|
|
journalSaavedro(arg0);
|
|
|
|
break;
|
|
|
|
case 9:
|
|
|
|
journalAtrus(arg0, arg1);
|
|
|
|
break;
|
|
|
|
case 14:
|
|
|
|
projectorLoadBitmap(arg0);
|
|
|
|
break;
|
|
|
|
case 15:
|
|
|
|
projectorAddSpotItem(arg0, arg1, arg2);
|
|
|
|
break;
|
|
|
|
case 16:
|
|
|
|
projectorUpdateCoordinates();
|
|
|
|
break;
|
|
|
|
case 20:
|
|
|
|
saveLoadMenu(arg0, arg1);
|
|
|
|
break;
|
|
|
|
case 21:
|
|
|
|
mainMenu(arg0);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
warning("Puzzle %d is not implemented", id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-01-28 20:38:55 +01:00
|
|
|
void Puzzles::_drawForVarHelper(uint16 var, int32 startValue, int32 endValue) {
|
|
|
|
uint startFrame = _vm->_state->getFrameCount();
|
|
|
|
uint currentFrame = startFrame;
|
|
|
|
uint numValues = abs(endValue - startValue);
|
|
|
|
uint endFrame = startFrame + 2 * numValues;
|
|
|
|
|
|
|
|
if (startFrame < endFrame) {
|
|
|
|
int currentValue = -9999;
|
|
|
|
while (1) {
|
|
|
|
int nextValue = (currentFrame - startFrame) / 2;
|
|
|
|
if (currentValue != nextValue) {
|
|
|
|
currentValue = nextValue;
|
|
|
|
|
|
|
|
int16 varValue;
|
|
|
|
if (endValue > startValue)
|
|
|
|
varValue = startValue + currentValue;
|
|
|
|
else
|
|
|
|
varValue = startValue - currentValue;
|
|
|
|
|
|
|
|
_vm->_state->setVar(var, varValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
_vm->processInput(true);
|
|
|
|
_vm->drawFrame();
|
|
|
|
currentFrame = _vm->_state->getFrameCount();
|
|
|
|
|
|
|
|
if (currentFrame > endFrame)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_vm->_state->setVar(var, endValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Puzzles::leversBall(int16 var) {
|
|
|
|
struct NewPosition {
|
|
|
|
bool newLeft;
|
|
|
|
bool newRight;
|
|
|
|
uint16 newBallPosition;
|
|
|
|
uint16 movieStart;
|
|
|
|
uint16 movieEnd;
|
|
|
|
uint16 movieBallStart;
|
|
|
|
uint16 movieBallEnd;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Move {
|
|
|
|
bool oldLeft;
|
|
|
|
bool oldRight;
|
|
|
|
uint16 oldBallPosition;
|
|
|
|
NewPosition p[2];
|
|
|
|
};
|
|
|
|
|
|
|
|
static const Move moves[] = {
|
|
|
|
{ 0, 1, 2, { { 1, 1, 2, 127, 147, 0, 0 }, { 0, 0, 0, 703, 735, 0, 0 } } },
|
|
|
|
{ 0, 0, 4, { { 1, 0, 4, 43, 63, 0, 0 }, { 0, 1, 4, 64, 84, 0, 0 } } },
|
|
|
|
{ 0, 0, 1, { { 1, 0, 1, 85, 105, 0, 0 }, { 0, 1, 1, 22, 42, 0, 0 } } },
|
|
|
|
{ 1, 0, 4, { { 1, 1, 3, 514, 534, 169, 217 }, { 0, 0, 4, 577, 597, 0, 0 } } },
|
|
|
|
{ 1, 0, 3, { { 1, 1, 3, 493, 513, 0, 0 }, { 0, 0, 4, 451, 471, 410, 450 } } },
|
|
|
|
{ 1, 0, 1, { { 1, 1, 2, 472, 492, 312, 360 }, { 0, 0, 1, 598, 618, 0, 0 } } },
|
|
|
|
{ 0, 1, 4, { { 1, 1, 3, 148, 168, 169, 217 }, { 0, 0, 4, 619, 639, 0, 0 } } },
|
|
|
|
{ 0, 1, 2, { { 1, 1, 2, 127, 147, 0, 0 }, { 0, 0, 1, 1, 21, 271, 311 } } },
|
|
|
|
{ 0, 1, 1, { { 1, 1, 2, 106, 126, 312, 360 }, { 0, 0, 1, 640, 660, 0, 0 } } },
|
|
|
|
{ 1, 1, 3, { { 1, 0, 3, 661, 681, 0, 0 }, { 0, 1, 2, 535, 555, 218, 270 } } },
|
|
|
|
{ 1, 1, 2, { { 1, 0, 3, 556, 575, 361, 409 }, { 0, 1, 2, 682, 702, 0, 0 } } },
|
|
|
|
{ 0, 0, 0, { { 1, 0, 0, 757, 777, 0, 0 }, { 0, 1, 0, 736, 756, 0, 0 } } },
|
|
|
|
{ 1, 0, 0, { { 1, 1, 0, 799, 819, 0, 0 }, { 0, 0, 0, 841, 861, 0, 0 } } },
|
|
|
|
{ 0, 1, 0, { { 1, 1, 0, 778, 798, 0, 0 }, { 0, 0, 0, 820, 840, 0, 0 } } },
|
|
|
|
{ 1, 1, 0, { { 1, 0, 0, 883, 903, 0, 0 }, { 0, 1, 0, 862, 882, 0, 0 } } },
|
|
|
|
{ -1, 0, 0, { { 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0 } } }
|
|
|
|
};
|
|
|
|
|
|
|
|
uint16 oldPosition = _vm->_state->getBallPosition();
|
|
|
|
uint16 oldLeverLeft = _vm->_state->getBallLeverLeft();
|
|
|
|
uint16 oldLeverRight = _vm->_state->getBallLeverRight();
|
|
|
|
|
|
|
|
// Toggle lever position
|
|
|
|
_vm->_state->setVar(var, !_vm->_state->getVar(var));
|
|
|
|
|
|
|
|
uint16 newLeverLeft = _vm->_state->getBallLeverLeft();
|
|
|
|
uint16 newLeverRight = _vm->_state->getBallLeverRight();
|
|
|
|
|
|
|
|
const Move *move = 0;
|
|
|
|
for (uint i = _vm->_state->getBallDoorOpen() ? 0 : 1; i < ARRAYSIZE(moves); i++)
|
|
|
|
if (moves[i].oldBallPosition == oldPosition
|
|
|
|
&& moves[i].oldLeft == oldLeverLeft
|
|
|
|
&& moves[i].oldRight == oldLeverRight) {
|
|
|
|
move = &moves[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!move)
|
|
|
|
error("Unable to find move with old levers l:%d r:%d p:%d", oldLeverLeft, oldLeverRight, oldPosition);
|
|
|
|
|
|
|
|
const NewPosition *position = 0;
|
|
|
|
for (uint i = 0; i < ARRAYSIZE(move->p); i++)
|
|
|
|
if (move->p[i].newLeft == newLeverLeft
|
|
|
|
&& move->p[i].newRight == newLeverRight) {
|
|
|
|
position = &move->p[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!position)
|
|
|
|
error("Unable to find position with levers l:%d r:%d", newLeverLeft, newLeverRight);
|
|
|
|
|
|
|
|
_vm->_sound->play(789, 50);
|
|
|
|
_drawForVarHelper(35, position->movieStart, position->movieEnd);
|
|
|
|
|
|
|
|
if (position->newBallPosition != oldPosition) {
|
|
|
|
uint16 sound;
|
|
|
|
if (position->newBallPosition == 0) {
|
|
|
|
sound = 792;
|
|
|
|
} else if (position->newBallPosition == 1 || position->newBallPosition == 4) {
|
|
|
|
sound = 790;
|
|
|
|
} else {
|
|
|
|
sound = 791;
|
|
|
|
}
|
|
|
|
|
|
|
|
_vm->_sound->play(sound, 50);
|
|
|
|
_drawForVarHelper(35, position->movieBallStart, position->movieBallEnd);
|
|
|
|
}
|
|
|
|
|
|
|
|
_vm->_state->setBallPosition(position->newBallPosition);
|
|
|
|
_vm->_state->setBallFrame(_vm->_state->getVar(35));
|
|
|
|
}
|
|
|
|
|
2012-01-26 13:11:43 +01:00
|
|
|
void Puzzles::journalSaavedro(int16 move) {
|
|
|
|
uint16 chapter = _vm->_state->getJournalSaavedroChapter();
|
|
|
|
int16 page = _vm->_state->getJournalSaavedroPageInChapter();
|
|
|
|
|
|
|
|
if (!_journalSaavedroHasChapter(chapter))
|
|
|
|
chapter = _journalSaavedroNextChapter(chapter, true);
|
|
|
|
|
|
|
|
if (move > 0) {
|
|
|
|
// Go to the next available page
|
|
|
|
int16 pageCount = _journalSaavedroPageCount(chapter);
|
|
|
|
page++;
|
|
|
|
|
|
|
|
if (page == pageCount) {
|
|
|
|
chapter = _journalSaavedroNextChapter(chapter, true);
|
|
|
|
page = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
_vm->_state->setJournalSaavedroChapter(chapter);
|
|
|
|
_vm->_state->setJournalSaavedroPageInChapter(page);
|
|
|
|
} else if (move < 0) {
|
|
|
|
// Go to the previous available page
|
|
|
|
page--;
|
|
|
|
|
|
|
|
if (page < 0) {
|
|
|
|
chapter = _journalSaavedroNextChapter(chapter, false);
|
|
|
|
page = _journalSaavedroPageCount(chapter) - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
_vm->_state->setJournalSaavedroChapter(chapter);
|
|
|
|
_vm->_state->setJournalSaavedroPageInChapter(page);
|
|
|
|
} else {
|
|
|
|
// Display current page
|
|
|
|
int16 chapterStartNode = _journalSaavedroGetNode(chapter);
|
|
|
|
int16 closed = 0;
|
|
|
|
int16 opened = 0;
|
|
|
|
int16 lastPage = 0;
|
|
|
|
|
|
|
|
if (chapter > 0) {
|
|
|
|
opened = 1;
|
|
|
|
if (chapter == 21)
|
|
|
|
lastPage = 2;
|
|
|
|
else
|
|
|
|
lastPage = 1;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
closed = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16 nodeRight;
|
|
|
|
uint16 nodeLeft;
|
|
|
|
if (page || !chapter) {
|
|
|
|
nodeRight = chapterStartNode + page;
|
|
|
|
nodeLeft = chapterStartNode + page;
|
|
|
|
} else {
|
|
|
|
nodeRight = chapterStartNode + page;
|
|
|
|
uint16 chapterLeft = _journalSaavedroNextChapter(chapter, false);
|
|
|
|
if (chapterLeft > 0)
|
|
|
|
nodeLeft = _journalSaavedroGetNode(chapterLeft + 1);
|
|
|
|
else
|
|
|
|
nodeLeft = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
_vm->_state->setJournalSaavedroClosed(closed);
|
|
|
|
_vm->_state->setJournalSaavedroOpen(opened);
|
|
|
|
_vm->_state->setJournalSaavedroLastPage(lastPage);
|
|
|
|
|
|
|
|
// TODO: Draw nodeLeft on the left part of the screen
|
|
|
|
_vm->loadNodeFrame(nodeRight);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16 Puzzles::_journalSaavedroGetNode(uint16 chapter) {
|
|
|
|
const DirectorySubEntry *desc = _vm->getFileDescription(0, 1200, 0, DirectorySubEntry::kNumMetadata);
|
|
|
|
|
|
|
|
if (!desc)
|
|
|
|
error("Node 1200 does not exist");
|
|
|
|
|
|
|
|
return desc->getMiscData(chapter) + 199;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16 Puzzles::_journalSaavedroPageCount(uint16 chapter) {
|
|
|
|
uint16 chapterStartNode = _journalSaavedroGetNode(chapter);
|
|
|
|
if (chapter != 21)
|
|
|
|
return _journalSaavedroGetNode(chapter + 1) - chapterStartNode;
|
|
|
|
else
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Puzzles::_journalSaavedroHasChapter(uint16 chapter) {
|
|
|
|
return _vm->_state->getVar(285 + chapter) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16 Puzzles::_journalSaavedroNextChapter(uint16 chapter, bool forward) {
|
|
|
|
do {
|
|
|
|
if (forward)
|
|
|
|
chapter++;
|
|
|
|
else
|
|
|
|
chapter--;
|
|
|
|
} while (!_journalSaavedroHasChapter(chapter));
|
|
|
|
|
|
|
|
return chapter;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Puzzles::journalAtrus(uint16 node, uint16 var) {
|
|
|
|
uint numPages = 0;
|
|
|
|
|
|
|
|
while (_vm->getFileDescription(0, node++, 0, DirectorySubEntry::kFrame))
|
|
|
|
numPages++;
|
|
|
|
|
|
|
|
_vm->_state->setVar(var, numPages - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Puzzles::mainMenu(uint16 action) {
|
|
|
|
_vm->setMenuAction(action);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Puzzles::saveLoadMenu(uint16 action, uint16 item) {
|
|
|
|
switch (action) {
|
|
|
|
case 0:
|
|
|
|
_vm->_menu->loadMenuOpen();
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
_vm->_menu->loadMenuSelect(item);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
_vm->_menu->loadMenuLoad();
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
_vm->_menu->saveMenuOpen();
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
_vm->_menu->saveMenuSelect(item);
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
_vm->_menu->saveMenuSave();
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
_vm->_menu->loadMenuChangePage();
|
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
_vm->_menu->saveMenuChangePage();
|
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
_vm->_menu->saveLoadErase();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
warning("Save load menu action %d for item %d is not implemented", action, item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void copySurfaceRect(Graphics::Surface *dest, const Common::Point &destPoint, const Graphics::Surface *src) {
|
|
|
|
for (uint16 i = 0; i < src->h; i++)
|
|
|
|
memcpy(dest->getBasePtr(destPoint.x, i + destPoint.y), src->getBasePtr(0, i), src->pitch);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Puzzles::projectorLoadBitmap(uint16 bitmap) {
|
|
|
|
assert(_vm->_projectorBackground == 0 && "Previous background not yet used.");
|
|
|
|
|
|
|
|
// This surface is freed by the destructor of the movie that uses it
|
|
|
|
_vm->_projectorBackground = new Graphics::Surface();
|
|
|
|
_vm->_projectorBackground->create(1024, 1024, Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
|
|
|
|
|
|
|
|
const DirectorySubEntry *movieDesc = _vm->getFileDescription(0, bitmap, 0, DirectorySubEntry::kStillMovie);
|
|
|
|
|
|
|
|
if (!movieDesc)
|
|
|
|
error("Movie %d does not exist", bitmap);
|
|
|
|
|
|
|
|
// Rebuild the complete background image from the frames of the bink movie
|
|
|
|
Common::MemoryReadStream *movieStream = movieDesc->getData();
|
|
|
|
Video::SeekableBinkDecoder bink;
|
|
|
|
bink.loadStream(movieStream, Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
|
|
|
|
|
|
|
|
for (uint i = 0; i < 1024; i += 256)
|
|
|
|
for (uint j = 0; j < 1024; j += 256) {
|
|
|
|
const Graphics::Surface *frame = bink.decodeNextFrame();
|
|
|
|
copySurfaceRect(_vm->_projectorBackground, Common::Point(j, i), frame);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Puzzles::projectorAddSpotItem(uint16 bitmap, uint16 x, uint16 y) {
|
|
|
|
assert(_vm->_projectorBackground != 0 && "Projector background already used.");
|
|
|
|
|
|
|
|
const DirectorySubEntry *movieDesc = _vm->getFileDescription(0, bitmap, 0, DirectorySubEntry::kStillMovie);
|
|
|
|
|
|
|
|
if (!movieDesc)
|
|
|
|
error("Movie %d does not exist", bitmap);
|
|
|
|
|
|
|
|
// Rebuild the complete background image from the frames of the bink movie
|
|
|
|
Common::MemoryReadStream *movieStream = movieDesc->getData();
|
|
|
|
Video::SeekableBinkDecoder bink;
|
|
|
|
bink.loadStream(movieStream, Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
|
|
|
|
|
|
|
|
const Graphics::Surface *frame = bink.decodeNextFrame();
|
|
|
|
copySurfaceRect(_vm->_projectorBackground, Common::Point(x, y), frame);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Puzzles::projectorUpdateCoordinates() {
|
|
|
|
int16 x = CLIP<int16>(_vm->_state->getProjectorX(), 840, 9400);
|
|
|
|
int16 y = CLIP<int16>(_vm->_state->getProjectorY(), 840, 9400);
|
|
|
|
int16 zoom = CLIP<int16>(_vm->_state->getProjectorZoom(), 1280, 5120);
|
|
|
|
int16 blur = CLIP<int16>(_vm->_state->getProjectorBlur(), 400, 2470);
|
|
|
|
|
|
|
|
int16 halfZoom = zoom / 2;
|
|
|
|
if (x - halfZoom < 0)
|
|
|
|
x = halfZoom;
|
|
|
|
if (x + halfZoom > 10240)
|
|
|
|
x = 10240 - halfZoom;
|
|
|
|
if (y - halfZoom < 0)
|
|
|
|
y = halfZoom;
|
|
|
|
if (y + halfZoom > 10240)
|
|
|
|
y = 10240 - halfZoom;
|
|
|
|
|
|
|
|
int16 angleXOffset = _vm->_state->getProjectorAngleXOffset();
|
|
|
|
int16 angleYOffset = _vm->_state->getProjectorAngleYOffset();
|
|
|
|
int16 angleZoomOffset = _vm->_state->getProjectorAngleZoomOffset();
|
|
|
|
int16 angleBlurOffset = _vm->_state->getProjectorAngleBlurOffset();
|
|
|
|
|
|
|
|
int16 angleX = (angleXOffset + 200 * (5 * x - 4200) / 8560) % 1000;
|
2012-01-28 20:38:55 +01:00
|
|
|
int16 angleY = (angleYOffset + 200 * (5 * y - 4200) / 8560) % 1000;
|
|
|
|
int16 angleZoom = (angleZoomOffset + 200 * (5 * zoom - 6400) / 3840) % 1000;
|
|
|
|
int16 angleBlur = (angleBlurOffset + 200 * (5 * blur - 2000) / 2070) % 1000;
|
2012-01-26 13:11:43 +01:00
|
|
|
|
2012-01-28 20:38:55 +01:00
|
|
|
_vm->_state->setProjectorAngleX(angleX);
|
2012-01-26 13:11:43 +01:00
|
|
|
_vm->_state->setProjectorAngleY(angleY);
|
|
|
|
_vm->_state->setProjectorAngleZoom(angleZoom);
|
|
|
|
_vm->_state->setProjectorAngleBlur(angleBlur);
|
|
|
|
|
2012-01-28 20:38:55 +01:00
|
|
|
_vm->_state->setProjectorX(x);
|
2012-01-26 13:11:43 +01:00
|
|
|
_vm->_state->setProjectorY(y);
|
|
|
|
_vm->_state->setProjectorZoom(zoom);
|
|
|
|
_vm->_state->setProjectorBlur(blur);
|
|
|
|
}
|
|
|
|
|
2012-01-28 20:38:55 +01:00
|
|
|
|
|
|
|
|
2012-01-26 13:11:43 +01:00
|
|
|
} /* namespace Myst3 */
|