scummvm/engines/myst3/puzzles.cpp

1622 lines
43 KiB
C++

/* 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 "engines/myst3/puzzles.h"
#include "engines/myst3/ambient.h"
#include "engines/myst3/menu.h"
#include "engines/myst3/myst3.h"
#include "engines/myst3/node.h"
#include "engines/myst3/state.h"
#include "engines/myst3/sound.h"
#include "common/config-manager.h"
namespace Myst3 {
Puzzles::Puzzles(Myst3Engine *vm) :
_vm(vm) {
}
Puzzles::~Puzzles() {
}
void Puzzles::run(uint16 id, uint16 arg0, uint16 arg1, uint16 arg2) {
switch (id) {
case 1:
leversBall(arg0);
break;
case 2:
tesla(arg0, arg1, arg2);
break;
case 3:
resonanceRingControl();
break;
case 4:
resonanceRingsLaunchBall();
break;
case 5:
resonanceRingsLights();
break;
case 6:
pinball(arg0);
break;
case 7:
weightDrag(arg0, arg1);
break;
case 8:
journalSaavedro(arg0);
break;
case 9:
journalAtrus(arg0, arg1);
break;
case 10:
symbolCodesInit(arg0, arg1, arg2);
break;
case 11:
symbolCodesClick(arg0);
break;
case 12:
railRoadSwitchs();
break;
case 13:
rollercoaster();
break;
case 14:
projectorLoadBitmap(arg0);
break;
case 15:
projectorAddSpotItem(arg0, arg1, arg2);
break;
case 16:
projectorUpdateCoordinates();
break;
case 17:
_vm->settingsLoadToVars();
break;
case 18:
_vm->settingsApplyFromVars();
break;
case 19:
settingsSave();
break;
case 20:
_vm->_menu->saveLoadAction(arg0, arg1);
break;
case 21:
mainMenu(arg0);
break;
case 22:
updateSoundScriptTimer();
break;
case 23:
_vm->loadNodeSubtitles(arg0);
break;
case 25:
checkCanSave(); // Xbox specific
break;
default:
warning("Puzzle %d is not implemented", id);
}
}
void Puzzles::_drawForVarHelper(int16 var, int32 startValue, int32 endValue) {
uint startTick = _vm->_state->getTickCount();
uint currentTick = startTick;
uint numValues = abs(endValue - startValue);
uint endTick = startTick + 2 * numValues;
int16 var2 = var;
if (var < 0)
var = -var;
if (var2 < 0)
var2 = -var2 + 1;
if (startTick < endTick) {
int currentValue = -9999;
while (1) {
int nextValue = (currentTick - startTick) / 2;
if (currentValue != nextValue) {
currentValue = nextValue;
int16 varValue;
if (endValue > startValue)
varValue = startValue + currentValue;
else
varValue = startValue - currentValue;
_vm->_state->setVar(var, varValue);
_vm->_state->setVar(var2, varValue);
}
_vm->processInput(false);
_vm->drawFrame();
currentTick = _vm->_state->getTickCount();
if (currentTick > endTick || _vm->shouldQuit())
break;
}
}
_vm->_state->setVar(var, endValue);
_vm->_state->setVar(var2, endValue);
}
void Puzzles::_drawXTicks(uint16 ticks) {
uint32 endTick = _vm->_state->getTickCount() + ticks;
while (_vm->_state->getTickCount() < endTick && !_vm->shouldQuit()) {
_vm->processInput(false);
_vm->drawFrame();
}
}
void Puzzles::leversBall(int16 var) {
struct NewPosition {
bool newLeft;
bool newRight;
uint16 newBallPosition;
uint16 movieStart;
uint16 movieEnd;
uint16 movieBallStart;
uint16 movieBallEnd;
};
struct Move {
int16 oldLeft;
int16 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 = nullptr;
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 = nullptr;
for (uint i = 0; i < ARRAYSIZE(move->p); i++)
if (move->p[i].newLeft == (newLeverLeft != 0) && move->p[i].newRight == (newLeverRight != 0)) {
position = &move->p[i];
break;
}
if (!position)
error("Unable to find position with levers l:%d r:%d", newLeverLeft, newLeverRight);
_vm->_sound->playEffect(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->playEffect(sound, 50);
if (position->movieBallStart != 0) {
_drawForVarHelper(35, position->movieBallStart, position->movieBallEnd);
}
}
_vm->_state->setBallPosition(position->newBallPosition);
_vm->_state->setBallFrame(_vm->_state->getVar(35));
}
void Puzzles::tesla(int16 movie, int16 var, int16 move) {
uint16 node = _vm->_state->getLocationNode();
int16 movieStart = 0;
switch (node) {
case 114:
movieStart = 0;
break;
case 116:
movieStart = 320;
break;
case 118:
movieStart = 240;
break;
case 120:
movieStart = 160;
break;
case 122:
movieStart = 80;
break;
}
_vm->_state->setTeslaMovieStart(movieStart);
uint16 position = movieStart + _vm->_state->getVar(var);
if (position > 400)
position -= 400;
_vm->_state->setVar(32, node % 100);
_vm->_state->setVar(33, node % 100 + 10000);
if (movie) {
_vm->_sound->playEffect(1243, 100);
_vm->_state->setMovieSynchronized(true);
_vm->playSimpleMovie(movie);
}
if (move) {
uint16 sound = _vm->_rnd->getRandomNumberRng(1244, 1245);
_vm->_sound->playEffect(sound, 100);
}
if (move > 0) {
_drawForVarHelper(var - 303, position + 1, position + 19);
position += 20;
} else if (move < 0) {
if (position == 1)
position = 401;
_drawForVarHelper(var - 303, position - 1, position - 19);
position -= 20;
}
if (position < 1)
position = 381;
else if (position > 400)
position = 1;
_vm->_state->setVar(var - 303, position);
int16 absPosition = position - movieStart;
if (absPosition < 1)
absPosition += 400;
_vm->_state->setVar(var, absPosition);
bool puzzleSolved = _vm->_state->getTeslaTopAligned() == 1
&& _vm->_state->getTeslaMiddleAligned() == 1
&& _vm->_state->getTeslaBottomAligned() == 1;
_vm->_state->setTeslaAllAligned(puzzleSolved);
}
void Puzzles::resonanceRingControl() {
static const uint16 frames[] = { 0, 24, 1, 5, 10, 15, 0, 0, 0 };
uint16 startPos = _vm->_state->getVar(29);
uint16 destPos = _vm->_state->getVar(27);
int16 startFrame = frames[startPos] - 27;
int16 destFrame = frames[destPos];
// Choose the shortest direction
for (int16 i = destFrame - startFrame; abs(i) > 14; i -= 27)
startFrame += 27;
// Play the movie, taking care of the limit case
if (destFrame >= startFrame) {
if (startFrame < 1) {
_drawForVarHelper(28, startFrame + 27, 27);
_drawForVarHelper(28, 1, destFrame);
return;
}
} else {
if (startFrame > 27) {
_drawForVarHelper(28, startFrame - 27, 1);
_drawForVarHelper(28, 27, destFrame);
return;
}
}
if (startFrame)
_drawForVarHelper(28, startFrame, destFrame);
}
void Puzzles::resonanceRingsLaunchBall() {
struct TrackFrames {
uint16 ringFrame;
uint16 var;
uint16 num;
uint16 shatterStartFrame;
uint16 shatterEndFrame;
};
static const TrackFrames tracks[] = {
{ 38, 436, 1, 182, 190 },
{ 74, 434, 2, 194, 214 },
{ 104, 437, 3, 215, 224 },
{ 138, 435, 4, 225, 234 },
{ 166, 438, 5, 235, 244 },
{ 0, 0, 0, 0, 0 }
};
struct LightFrames {
uint16 startFrame;
uint16 endFrame;
uint16 num;
};
static const LightFrames lights[] = {
{ 26, 44, 1 },
{ 66, 85, 2 },
{ 89, 118, 3 },
{ 126, 150, 4 },
{ 154, 180, 5 }
};
bool ballShattered = false;
bool lastIsOnLightButton = false;
int32 lightStatus = 0;
uint part = 0;
uint16 buttonVar = 0;
int32 ballMoviePlaying;
int32 boardMoviePlaying;
do {
_vm->processInput(false);
_vm->drawFrame();
ballMoviePlaying = _vm->_state->getVar(27);
boardMoviePlaying = _vm->_state->getVar(34);
if (ballMoviePlaying && tracks[part].ringFrame) {
int32 currentFrame = _vm->_state->getVar(30);
if (!ballShattered && currentFrame >= tracks[part].ringFrame) {
int32 value = _vm->_state->getVar(tracks[part].var);
if (value == tracks[part].num) {
// Correct ring order, go to next track part
part++;
} else {
// Incorrect ring order, shatter ball
ballShattered = true;
_vm->_sound->playEffect(1010, 50);
_vm->_state->setVar(28, tracks[part].shatterStartFrame);
_vm->_state->setVar(29, tracks[part].shatterEndFrame);
_vm->_state->setVar(31, tracks[part].shatterStartFrame);
}
}
}
bool isOnLightButton = false;
const LightFrames *frames = nullptr;
int32 currentLightFrame = _vm->_state->getVar(33);
// Look is the mini ball is on a light button
for (uint j = 0; j < ARRAYSIZE(lights); j++)
if (currentLightFrame >= lights[j].startFrame && currentLightFrame <= lights[j].endFrame) {
frames = &lights[j];
break;
}
// If ball on light button, turn it off
if (frames) {
for (uint j = 0; j < 5; j++) {
int32 ringValue = _vm->_state->getVar(434 + j);
if (ringValue == frames->num)
_vm->_state->setVar(38 + j, true);
}
isOnLightButton = true;
buttonVar = 438 + frames->num;
}
// Restore previous light value
if (lastIsOnLightButton != isOnLightButton) {
lastIsOnLightButton = isOnLightButton;
if (isOnLightButton) {
lightStatus = _vm->_state->getVar(buttonVar);
_vm->_state->setVar(buttonVar, 0);
} else {
_vm->_state->setVar(buttonVar, lightStatus);
for (uint j = 0; j < 5; j++)
_vm->_state->setVar(38 + j, false);
}
_vm->_ambient->playCurrentNode(100, 2);
}
} while ((ballMoviePlaying || boardMoviePlaying) && !_vm->shouldQuit());
_vm->_state->setResonanceRingsSolved(!ballShattered);
}
void Puzzles::resonanceRingsLights() {
// Turn off all lights
for (uint i = 0; i < 5; i++)
_vm->_state->setVar(439 + i, false);
// For each button / ring value
for (uint i = 0; i < 5; i++) {
// For each light
for (uint j = 0; j < 5; j++) {
// Ring selector value
uint32 ringValue = _vm->_state->getVar(434 + j);
if (ringValue == i + 1) {
// Button state
uint32 buttonState = _vm->_state->getVar(43 + i);
if (buttonState) {
uint32 oldValue = _vm->_state->getVar(444 + i);
_vm->_state->setVar(439 + i, oldValue);
_vm->_state->setVar(38 + j, true);
} else {
_vm->_state->setVar(38 + j, false);
}
}
}
}
_vm->_ambient->playCurrentNode(100, 2);
}
void Puzzles::pinball(int16 var) {
static const byte remainingPegsFrames[] = { 2, 15, 25, 32 };
static const PegCombination leftPegs[] = {
{ 10101, { 0, 1, 0, 0, 0 }, { 0, 0, 0 }, 300 },
{ 10102, { 1, 1, 0, 0, 0 }, { 49, 0, 0 }, 310 },
{ 10103, { 0, 1, 0, 1, 0 }, { 200, 0, 0 }, 310 },
{ 10104, { 0, 1, 1, 0, 0 }, { 150, 0, 0 }, 305 },
{ 10105, { 0, 1, 0, 0, 1 }, { 250, 0, 0 }, 305 },
{ 10106, { 1, 1, 0, 1, 0 }, { 49, 205, 0 }, 310 },
{ 10107, { 1, 1, 1, 0, 0 }, { 49, 155, 0 }, 309 },
{ 10108, { 1, 1, 0, 0, 1 }, { 49, 253, 0 }, 310 },
{ 10109, { 0, 1, 1, 1, 0 }, { 150, 205, 0 }, 310 },
{ 10110, { 0, 1, 0, 1, 1 }, { 199, 254, 0 }, 309 },
{ 10111, { 0, 1, 1, 0, 1 }, { 150, 254, 0 }, 309 },
{ 10112, { 1, 1, 1, 1, 0 }, { 49, 155, 210 }, 315 },
{ 10113, { 1, 1, 0, 1, 1 }, { 49, 205, 260 }, 315 },
{ 10114, { 1, 1, 1, 0, 1 }, { 49, 155, 260 }, 315 },
{ 10115, { 0, 1, 1, 1, 1 }, { 150, 205, 260 }, 315 }
};
static const PegCombination rightPegs[] = {
{ 10201, { 0, 0, 0, 0, 0 }, { 0, 0, 0 }, 300 },
{ 10202, { 1, 0, 0, 0, 0 }, { 250, 0, 0 }, 305 },
{ 10203, { 0, 1, 0, 0, 0 }, { 200, 0, 0 }, 305 },
{ 10204, { 0, 0, 1, 0, 0 }, { 150, 0, 0 }, 305 },
{ 10205, { 0, 0, 0, 1, 0 }, { 100, 0, 0 }, 305 },
{ 10206, { 0, 0, 0, 0, 1 }, { 50, 0, 0 }, 305 },
{ 10207, { 1, 1, 0, 0, 0 }, { 200, 255, 0 }, 305 },
{ 10208, { 1, 0, 1, 0, 0 }, { 150, 255, 0 }, 310 },
{ 10209, { 1, 0, 0, 1, 0 }, { 100, 255, 0 }, 310 },
{ 10210, { 1, 0, 0, 0, 1 }, { 50, 255, 0 }, 310 },
{ 10211, { 0, 1, 1, 0, 0 }, { 150, 205, 0 }, 310 },
{ 10212, { 0, 1, 0, 1, 0 }, { 100, 205, 0 }, 310 },
{ 10213, { 0, 1, 0, 0, 1 }, { 50, 205, 0 }, 310 },
{ 10214, { 0, 0, 1, 1, 0 }, { 100, 155, 0 }, 310 },
{ 10215, { 0, 0, 1, 0, 1 }, { 50, 155, 0 }, 210 },
{ 10216, { 0, 0, 0, 1, 1 }, { 50, 105, 0 }, 310 },
{ 10217, { 1, 1, 1, 0, 0 }, { 150, 205, 260 }, 315 },
{ 10218, { 1, 1, 0, 1, 0 }, { 100, 205, 260 }, 315 },
{ 10219, { 1, 1, 0, 0, 1 }, { 50, 205, 260 }, 312 },
{ 10220, { 1, 0, 1, 1, 0 }, { 100, 155, 260 }, 314 },
{ 10221, { 1, 0, 1, 0, 1 }, { 50, 155, 259 }, 315 },
{ 10222, { 1, 0, 0, 1, 1 }, { 50, 105, 260 }, 315 },
{ 10223, { 0, 1, 1, 1, 0 }, { 100, 155, 210 }, 315 },
{ 10224, { 0, 1, 1, 0, 1 }, { 50, 155, 210 }, 315 },
{ 10225, { 0, 1, 0, 1, 1 }, { 50, 105, 210 }, 315 },
{ 10226, { 0, 0, 1, 1, 1 }, { 50, 105, 155 }, 315 }
};
struct BallJump {
int16 positionLeft;
int16 positionRight;
int16 filter;
int16 startFrame;
int16 endFrame;
int16 sound;
int16 targetLeftFrame;
int16 tragetRightFrame;
int16 type;
};
static const BallJump jumps[] = {
{ 0, 450, 1, 16, 28, 1021, 250, 550, 0 },
{ 0, 450, -1, 29, 41, 1021, 500, 550, 3 },
{ 0, 200, 0, 42, 57, 1023, 300, 500, 0 },
{ 0, 200, -1, 58, 74, 1023, 550, 500, 3 },
{ 0, 250, 1, 75, 90, 1023, 350, 550, 0 },
{ 0, 250, -1, 91, 106, 1023, 500, 550, 3 },
{ 0, 300, 0, 107, 119, 1021, 400, 500, 0 },
{ 0, 300, -1, 120, 132, 1021, 550, 500, 3 },
{ 0, 400, 0, 133, 165, 1022, 500, 500, 2 },
{ 0, 400, 1, 1039, 1071, 1022, 550, 500, 2 },
{ 0, 350, 0, 166, 198, 1022, 500, 550, 2 },
{ 0, 350, 1, 1072, 1109, 1022, 550, 550, 2 },
{ 250, 0, 1, 801, 815, 1021, 550, 450, 0 },
{ 250, 0, -1, 816, 827, 1021, 550, 500, 4 },
{ 300, 0, 0, 828, 845, 1023, 500, 200, 0 },
{ 300, 0, -1, 846, 858, 1023, 500, 550, 3 },
{ 350, 0, 1, 859, 876, 1023, 550, 250, 0 },
{ 350, 0, -1, 0, 0, 0, 550, 500, 1 },
{ 400, 0, 0, 893, 907, 1021, 500, 300, 0 },
{ 400, 0, 1, 1267, 1278, 1023, 500, 550, 3 },
{ 200, 0, 1, 908, 940, 1022, 500, 550, 2 },
{ 200, 0, 0, 974, 1006, 1022, 500, 500, 2 },
{ 450, 0, 1, 941, 973, 1022, 550, 550, 2 },
{ 450, 0, 0, 1007, 1038, 1022, 550, 500, 2 }
};
struct BallExpireFrames {
uint16 leftPosition;
uint16 rightPosition;
uint16 startFrame;
uint16 endFrame;
};
static const BallExpireFrames ballExpireFrames[] = {
{ 200, 200, 1105, 1131 },
{ 250, 250, 1132, 1158 },
{ 300, 300, 1159, 1185 },
{ 350, 350, 1186, 1212 },
{ 400, 400, 1213, 1239 },
{ 450, 450, 1240, 1266 }
};
// Toggle peg state
if (var > 0) {
int32 value = _vm->_state->getVar(var);
if (value) {
_vm->_state->setVar(var, 0);
} else {
_vm->_state->setVar(var, 1);
// Play the "peg clicks into spot sound"
if (!_vm->_sound->isPlaying(1024)) {
_vm->_sound->playEffect(1024, 100);
}
}
}
// Remaining pegs movie
uint32 pegs = _vm->_state->getPinballRemainingPegs();
uint32 frame = remainingPegsFrames[pegs];
_vm->_state->setVar(33, frame);
// Choose pegs movie according to peg combination
const PegCombination *leftComb = _pinballFindCombination(461, leftPegs, ARRAYSIZE(leftPegs));
if (!leftComb)
error("Unable to find correct left pegs combination");
_vm->_state->setVar(31, leftComb->movie - 10100);
const PegCombination *rightComb = _pinballFindCombination(466, rightPegs, ARRAYSIZE(rightPegs));
if (!rightComb)
error("Unable to find correct right pegs combination");
_vm->_state->setVar(32, rightComb->movie - 10200);
if (var >= 0)
return;
_vm->_state->setWaterEffectRunning(false);
// Remove the default panel movies
_vm->removeMovie(10116);
_vm->removeMovie(10227);
// Set up left panel movie with the correct combination
_vm->_state->setMoviePreloadToMemory(true);
_vm->_state->setMovieScriptDriven(true);
_vm->_state->setMovieNextFrameGetVar(31);
_vm->loadMovie(leftComb->movie, 1, false, true);
_vm->_state->setVar(31, 2);
// Set up right panel movie with the correct combination
_vm->_state->setMoviePreloadToMemory(true);
_vm->_state->setMovieScriptDriven(true);
_vm->_state->setMovieNextFrameGetVar(32);
_vm->loadMovie(rightComb->movie, 1, false, true);
_vm->_state->setVar(32, 2);
// Launch sound
_vm->_sound->playEffect(1021, 50);
_drawForVarHelper(-34, 2, 15);
_drawXTicks(30);
int32 leftSideFrame = 250;
int32 rightSideFrame = 500;
_vm->_state->setVar(34, 250);
_vm->_state->setVar(35, 500);
int32 leftPanelFrame = 2;
int32 rightPanelFrame = 2;
int32 ballOnLeftSide = 1;
int32 ballOnRightSide = 0;
int32 ballShouldExpire = 0;
int32 ballCrashed = 0;
int32 leftToRightJumpCountDown = 0;
int32 rightToLeftJumpCountdown = 0;
int32 ballJumpedFromLeftSide = 1;
int32 ballJumpedFromRightSide = 0;
int32 jumpType = -1;
while (1) {
_drawXTicks(1);
bool shouldRotate;
if (leftToRightJumpCountDown >= 3 || rightToLeftJumpCountdown >= 3) {
shouldRotate = false;
_vm->_sound->stopEffect(1025, 7);
} else {
shouldRotate = true;
_vm->_sound->playEffectLooping(1025, 50, ballOnLeftSide != 0 ? 150 : 210, 95);
}
if (ballOnLeftSide && shouldRotate) {
++leftSideFrame;
if (ballJumpedFromLeftSide) {
if (leftSideFrame >= 500)
leftSideFrame = 200;
} else {
if (leftSideFrame >= 800)
leftSideFrame = 500;
}
_vm->_state->setVar(34, leftSideFrame);
}
if (ballOnRightSide && shouldRotate) {
rightSideFrame++;
if (ballJumpedFromRightSide) {
if (rightSideFrame >= 500)
rightSideFrame = 200;
} else {
if (rightSideFrame >= 800)
rightSideFrame = 500;
}
_vm->_state->setVar(35, rightSideFrame);
}
if (ballOnLeftSide) {
leftPanelFrame++;
_vm->_state->setVar(31, leftPanelFrame);
for (uint i = 0; i < 3; i++) {
if (leftComb->pegFrames[i] == leftPanelFrame) {
_vm->_sound->playEffect(1027, 50);
leftToRightJumpCountDown = 5;
}
}
if (leftPanelFrame == leftComb->expireFrame) {
ballShouldExpire = 1;
ballOnLeftSide = 0;
}
}
if (ballOnRightSide) {
rightPanelFrame++;
_vm->_state->setVar(32, rightPanelFrame);
for (uint i = 0; i < 3; i++) {
if (rightComb->pegFrames[i] == rightPanelFrame) {
_vm->_sound->playEffect(1027, 50);
rightToLeftJumpCountdown = 5;
}
}
if (rightPanelFrame == rightComb->expireFrame) {
ballShouldExpire = 1;
ballOnRightSide = 0;
}
}
bool ballShouldJump = false;
if (leftToRightJumpCountDown) {
--leftToRightJumpCountDown;
if (!leftToRightJumpCountDown) {
ballOnLeftSide = 0;
ballShouldJump = true;
}
}
if (rightToLeftJumpCountdown) {
--rightToLeftJumpCountdown;
if (!rightToLeftJumpCountdown) {
ballOnRightSide = 0;
ballShouldJump = true;
}
}
if (ballShouldJump) {
_vm->_sound->stopEffect(1025, 7);
_drawXTicks(30);
int32 jumpPositionLeft = 50 * ((leftSideFrame + 25) / 50);
int32 jumpPositionRight = 50 * ((rightSideFrame + 25) / 50);
const BallJump *jump = nullptr;
for (uint i = 0; i < ARRAYSIZE(jumps); i++) {
int32 filter = jumps[i].filter;
if (filter != -1) {
if (filter) {
if (!(jumpPositionLeft % 100))
continue;
} else {
if (jumpPositionLeft % 100)
continue;
}
}
if (abs(jumps[i].positionRight - jumpPositionRight) < 10) {
ballOnRightSide = 0;
ballOnLeftSide = 1;
ballJumpedFromRightSide = 0;
ballJumpedFromLeftSide = 1;
jump = &jumps[i];
break;
}
}
for (uint i = 0; i < ARRAYSIZE(jumps); i++) {
int32 filter = jumps[i].filter;
if (filter != -1) {
if (filter) {
if (!(jumpPositionRight % 100))
continue;
} else {
if (jumpPositionRight % 100)
continue;
}
}
if (abs(jumps[i].positionLeft - jumpPositionLeft) < 10) {
ballOnLeftSide = 0;
ballOnRightSide = 1;
ballJumpedFromRightSide = 1;
ballJumpedFromLeftSide = 0;
jump = &jumps[i];
break;
}
}
if (!jump)
error("Bad orb jump combo %d %d", jumpPositionLeft, jumpPositionRight);
jumpType = jump->type;
int32 sound = jump->sound;
if (sound)
_vm->_sound->playEffect(sound, 50);
int32 jumpStartFrame = jump->startFrame;
if (jumpStartFrame)
_drawForVarHelper(-34, jumpStartFrame, jump->endFrame);
if (jumpType == 3) {
_drawXTicks(6);
_vm->_sound->playEffect(1028, 50);
} else if (jumpType == 1 || jumpType == 4) {
_vm->_state->setVar(26, jumpType);
_vm->_state->setWaterEffectRunning(true);
_vm->_sound->stopEffect(1025, 7);
return;
}
leftSideFrame = jump->targetLeftFrame;
rightSideFrame = jump->tragetRightFrame;
_vm->_state->setVar(34, leftSideFrame);
_vm->_state->setVar(35, rightSideFrame);
if (jumpType >= 2)
ballCrashed = 1;
_drawXTicks(30);
}
if (ballShouldExpire) {
leftSideFrame = 50 * ((leftSideFrame + 25) / 50);
rightSideFrame = 50 * ((rightSideFrame + 25) / 50);
if (leftSideFrame == 500)
leftSideFrame = 200;
if (rightSideFrame == 500)
rightSideFrame = 200;
_vm->_sound->stopEffect(1025, 7);
_vm->_sound->playEffectFadeInOut(1005, 65, 0, 0, 5, 60, 20);
_drawXTicks(55);
_vm->_sound->playEffect(1010, 50);
for (uint i = 0; i < ARRAYSIZE(ballExpireFrames); i++) {
if (ballJumpedFromLeftSide && ballExpireFrames[i].leftPosition == leftSideFrame) {
_drawForVarHelper(34, ballExpireFrames[i].startFrame, ballExpireFrames[i].endFrame);
break;
}
if (ballJumpedFromRightSide && ballExpireFrames[i].rightPosition == rightSideFrame) {
_drawForVarHelper(35, ballExpireFrames[i].startFrame, ballExpireFrames[i].endFrame);
break;
}
}
_drawXTicks(15);
break;
}
if (ballCrashed)
break;
}
if (ballCrashed || ballShouldExpire) {
if (leftSideFrame < 500)
leftSideFrame += 300;
if (rightSideFrame < 500)
rightSideFrame += 300;
int32 crashedLeftFrame = ((((leftSideFrame + 25) / 50) >> 4) & 1) != 0 ? 550 : 500;
int32 crashedRightFrame = ((((rightSideFrame + 25) / 50) >> 4) & 1) != 0 ? 550 : 500;
while (1) {
bool moviePlaying = false;
if ((leftComb->movie != 10101 || leftPanelFrame > 2)
&& leftPanelFrame != leftComb->expireFrame) {
if (leftToRightJumpCountDown) {
--leftToRightJumpCountDown;
}
if (!leftToRightJumpCountDown) {
_vm->_state->setVar(34, crashedLeftFrame);
crashedLeftFrame++;
}
_vm->_state->setVar(31, leftPanelFrame);
++leftPanelFrame;
leftSideFrame = leftPanelFrame;
for (uint i = 0; i < 3; i++) {
if (leftComb->pegFrames[i] == leftSideFrame) {
_vm->_sound->playEffect(1027, 50);
leftToRightJumpCountDown = 5;
}
}
moviePlaying = true;
}
if (!moviePlaying) {
if ((rightComb->movie != 10201 || rightPanelFrame > 2)
&& rightPanelFrame != rightComb->expireFrame) {
if (rightToLeftJumpCountdown) {
--rightToLeftJumpCountdown;
}
if (!rightToLeftJumpCountdown) {
_vm->_state->setVar(35, crashedRightFrame);
crashedRightFrame++;
}
_vm->_state->setVar(32, rightPanelFrame);
++rightPanelFrame;
rightSideFrame = rightPanelFrame;
for (uint i = 0; i < 3; i++) {
if (rightComb->pegFrames[i] == rightSideFrame) {
_vm->_sound->playEffect(1027, 50);
rightToLeftJumpCountdown = 5;
}
}
moviePlaying = true;
}
}
_drawXTicks(1);
if (!moviePlaying) {
_vm->_state->setVar(26, jumpType);
_vm->_state->setVar(93, 1);
_vm->_sound->stopEffect(1025, 7);
return;
}
_vm->_sound->playEffectLooping(1025, 50);
}
}
}
const Puzzles::PegCombination *Puzzles::_pinballFindCombination(uint16 var, const PegCombination pegs[], uint16 size) {
const PegCombination *combination = nullptr;
for (uint i = 0; i < size; i++) {
bool good = true;
for (uint j = 0; j < 5; j++) {
bool setPeg = _vm->_state->getVar(var + j);
bool targetPeg = pegs[i].pegs[j];
if (setPeg != targetPeg)
good = false;
}
if (good) {
combination = &pegs[i];
break;
}
}
return combination;
}
void Puzzles::weightDrag(uint16 var, uint16 movie) {
if (var >= 429 && var <= 432) {
movie = _vm->_state->getVar(var);
_vm->_state->setVar(var, 0);
var = movie;
}
uint16 sound = 0;
if (var) {
switch (var) {
case 423:
movie = 1022;
sound = 921;
break;
case 425:
movie = 1023;
sound = 921;
break;
case 424:
movie = 1024;
sound = 922;
break;
case 427:
movie = 1025;
sound = 922;
break;
case 426:
movie = 1020;
sound = 920;
break;
case 428:
movie = 1021;
sound = 920;
break;
default:
break;
}
_vm->_state->setDraggedWeight(var);
_vm->dragItem(var, movie, 1, 2, 26);
_vm->_sound->playEffect(sound, 25);
}
for (uint i = 0; i < 4; i++) {
int32 value = _vm->_state->getVar(429 + i);
uint16 frame = 0;
switch (value) {
case 423:
case 425:
frame = 2;
break;
case 424:
case 427:
frame = 3;
break;
case 426:
case 428:
frame = 1;
break;
default:
break;
}
_vm->_state->setVar(28 + i, frame);
_vm->_state->setVar(32 + i, frame != 0);
}
}
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 = _journalSaavedroLastPageLastChapterValue();
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 = 201;
}
_vm->_state->setJournalSaavedroClosed(closed);
_vm->_state->setJournalSaavedroOpen(opened);
_vm->_state->setJournalSaavedroLastPage(lastPage);
_vm->loadNodeFrame(nodeRight);
// Does the left page need to be loaded from a different node?
if (nodeLeft != nodeRight) {
ResourceDescription jpegDesc = _vm->getFileDescription("", nodeLeft, 0, Archive::kFrame);
if (!jpegDesc.isValid())
error("Frame %d does not exist", nodeLeft);
Graphics::Surface *bitmap = Myst3Engine::decodeJpeg(&jpegDesc);
// Copy the left half of the node to a new surface
Graphics::Surface *leftBitmap = new Graphics::Surface();
leftBitmap->create(bitmap->w / 2, bitmap->h, Texture::getRGBAPixelFormat());
for (int i = 0; i < bitmap->h; i++) {
memcpy(leftBitmap->getBasePtr(0, i), bitmap->getBasePtr(0, i), leftBitmap->w * 4);
}
bitmap->free();
delete bitmap;
// Create a spotitem covering the left half of the screen
// to display the left page
SpotItemFace *leftPage = _vm->addMenuSpotItem(999, 1, Common::Rect(0, 0, leftBitmap->w, leftBitmap->h));
leftPage->updateData(leftBitmap);
leftBitmap->free();
delete leftBitmap;
}
}
}
int16 Puzzles::_journalSaavedroLastPageLastChapterValue() {
// The scripts just expect different values ...
if (_vm->getPlatform() == Common::kPlatformXbox) {
return 0;
} else {
return 2;
}
}
uint16 Puzzles::_journalSaavedroGetNode(uint16 chapter) {
ResourceDescription desc = _vm->getFileDescription("", 1200, 0, Archive::kNumMetadata);
if (!desc.isValid())
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("", node++, 0, Archive::kFrame).isValid())
numPages++;
_vm->_state->setVar(var, numPages - 1);
}
void Puzzles::symbolCodesInit(uint16 var, uint16 posX, uint16 posY) {
struct Point {
uint16 x;
uint16 y;
};
struct CodeData {
uint16 node;
uint16 movie;
bool flag;
Point coords[20];
};
static const CodeData codes[] = {
{
144, 10144, 0,
{
{ 296, 120 }, { 312, 128 }, { 296, 144 }, { 296, 128 }, { 312, 120 },
{ 328, 120 }, { 312, 144 }, { 312, 128 }, { 296, 136 }, { 312, 144 },
{ 296, 160 }, { 296, 144 }, { 312, 136 }, { 328, 144 }, { 312, 160 },
{ 312, 144 }, { 296, 112 }, { 328, 120 }, { 296, 160 }, { 288, 120 }
}
}, {
244, 10244, 1,
{
{ 288, 16 }, { 336, 32 }, { 294, 72 }, { 280, 24 }, { 336, 16 },
{ 376, 24 }, { 336, 72 }, { 328, 32 }, { 288, 64 }, { 336, 80 },
{ 288, 120 }, { 280, 72 }, { 336, 64 }, { 384, 72 }, { 336, 120 },
{ 328, 80 }, { 288, 0 }, { 384, 24 }, { 288, 120 }, { 264, 24 }
}
}, {
148, 10148, 0,
{
{ 280, 24 }, { 304, 32 }, { 288, 48 }, { 280, 24 }, { 304, 24 },
{ 320, 32 }, { 304, 48 }, { 296, 32 }, { 288, 40 }, { 304, 48 },
{ 280, 64 }, { 280, 48 }, { 304, 48 }, { 320, 48 }, { 304, 64 },
{ 296, 48 }, { 280, 16 }, { 320, 24 }, { 280, 64 }, { 272, 24 }
}
}, {
248, 10248, 1,
{
{ 280, 48 }, { 320, 56 }, { 287, 88 }, { 272, 56 }, { 320, 48 },
{ 360, 56 }, { 328, 96 }, { 312, 56 }, { 288, 88 }, { 320, 96 },
{ 280, 128 }, { 271, 96 }, { 328, 88 }, { 360, 96 }, { 320, 128 },
{ 312, 96 }, { 280, 32 }, { 360, 48 }, { 280, 128 }, { 264, 48 }
}
}, {
348, 10348, 1,
{
{ 336, 24 }, { 376, 32 }, { 336, 80 }, { 328, 32 }, { 376, 24 },
{ 424, 32 }, { 384, 80 }, { 368, 40 }, { 336, 72 }, { 376, 80 },
{ 336, 120 }, { 328, 80 }, { 384, 72 }, { 424, 80 }, { 376, 120 },
{ 368, 80 }, { 328, 8 }, { 424, 32 }, { 328, 128 }, { 312, 32 }
}
}, {
448, 10448, 1,
{
{ 224, 32 }, { 264, 40 }, { 224, 80 }, { 208, 40 }, { 264, 32 },
{ 304, 40 }, { 270, 88 }, { 256, 40 }, { 224, 72 }, { 264, 88 },
{ 224, 128 }, { 208, 88 }, { 272, 72 }, { 312, 88 }, { 264, 128 },
{ 256, 88 }, { 216, 16 }, { 312, 40 }, { 216, 128 }, { 200, 40 }
}
}
};
uint16 node = _vm->_state->getLocationNode();
const CodeData *code = nullptr;
for (uint i = 0; i < ARRAYSIZE(codes); i++)
if (codes[i].node == node) {
code = &codes[i];
break;
}
if (!code)
error("Unable to find puzzle data for node %d", node);
int32 value = _vm->_state->getVar(var);
for (uint i = 0; i < 20; i++) {
if (code->flag || value & (1 << i)) {
_vm->_state->setMoviePreloadToMemory(true);
_vm->_state->setMovieScriptDriven(true);
_vm->_state->setMovieOverridePosition(true);
_vm->_state->setMovieOverridePosU(posX + code->coords[i].x);
_vm->_state->setMovieOverridePosV(posY + code->coords[i].y);
_vm->_state->setMovieConditionBit(i + 1);
_vm->loadMovie(code->movie + i * 1000, var, false, true);
}
}
}
void Puzzles::symbolCodesClick(int16 var) {
// Toggle clicked symbol element
if (var > 0) {
int32 value = _vm->_state->getVar(var);
value ^= 1 << _vm->_state->getHotspotActiveRect();
_vm->_state->setVar(var, value);
}
// Check puzzle with one symbol solution
static const SymbolCodeSolution smallSolution = { 330080, 53575, 241719, 116411 };
if (_vm->_state->getSymbolCode1AllSolved()) {
bool code2Solved = _symbolCodesCheckSolution(490, smallSolution);
_vm->_state->setSymbolCode2Solved(code2Solved);
}
// Check puzzle with 3 symbols solution
static const SymbolCodeSolution solutions[] = {
{ 208172, 131196, 252945, 788771 },
{ 431060, 418863, 558738, 653337 },
{ 472588, 199440, 155951, 597954 }
};
_vm->_state->setSymbolCode1CurrentSolved(false);
for (uint i = 1; i <= ARRAYSIZE(solutions); i++) {
int32 solutionsFound = _symbolCodesFound();
// Symbol already found, don't allow it another time
if (solutionsFound & (1 << i))
continue;
if (_symbolCodesCheckSolution(498, solutions[i - 1])) {
_vm->_state->setSymbolCode1TopSolved(i);
_vm->_state->setSymbolCode1CurrentSolved(true);
}
if (_symbolCodesCheckSolution(503, solutions[i - 1])) {
_vm->_state->setSymbolCode1LeftSolved(i);
_vm->_state->setSymbolCode1CurrentSolved(true);
}
if (_symbolCodesCheckSolution(508, solutions[i - 1])) {
_vm->_state->setSymbolCode1RightSolved(i);
_vm->_state->setSymbolCode1CurrentSolved(true);
}
}
bool allSolved = _symbolCodesFound() == 14;
_vm->_state->setSymbolCode1AllSolved(allSolved);
}
bool Puzzles::_symbolCodesCheckSolution(uint16 var, const SymbolCodeSolution &solution) {
bool solved = true;
for (uint i = 0; i < ARRAYSIZE(solution); i++) {
int32 value = _vm->_state->getVar(var + i);
if (value != solution[i]) {
solved = false;
break;
}
}
return solved;
}
int32 Puzzles::_symbolCodesFound() {
int32 top = _vm->_state->getSymbolCode1TopSolved();
int32 left = _vm->_state->getSymbolCode1LeftSolved();
int32 right = _vm->_state->getSymbolCode1RightSolved();
return (1 << top) | (1 << left) | (1 << right);
}
void Puzzles::railRoadSwitchs() {
uint16 index = _vm->_state->getHotspotActiveRect();
uint16 startFrame = _vm->_state->getVar(449 + index);
uint16 endFrame;
switch (startFrame) {
case 1:
endFrame = 4;
break;
case 4:
endFrame = 7;
break;
case 7:
endFrame = 10;
break;
case 10:
endFrame = 12;
break;
default:
error("Bad railroad switches start value %d", startFrame);
return;
}
_drawForVarHelper(28 + index, startFrame, endFrame);
if (endFrame == 12)
endFrame = 1;
_vm->_state->setVar(28 + index, endFrame);
_vm->_state->setVar(449 + index, endFrame);
}
void Puzzles::rollercoaster() {
static const uint8 map1[][8] = {
{ 3, 9, 9, 0, 7, 9, 9, 4 },
{ 2, 6, 0, 9, 9, 9, 1, 9 },
{ 6, 9, 4, 9, 2, 9, 0, 9 },
{ 4, 9, 6, 9, 0, 9, 2, 9 },
{ 9, 4, 7, 9, 1, 9, 9, 2 },
{ 2, 9, 0, 9, 7, 9, 9, 4 },
{ 6, 9, 9, 7, 9, 9, 0, 3 },
{ 4, 9, 7, 6, 0, 9, 3, 2 },
{ 9, 5, 9, 9, 6, 1, 4, 9 }
};
static const uint8 map2[][8] = {
{ 0, 0, 26, 57, 40, 0, 0, 0 },
{ 100, 0, 36, 67, 50, 41, 12, 0 },
{ 0, 0, 0, 0, 60, 51, 22, 0 },
{ 14, 25, 56, 87, 70, 0, 103, 0 },
{ 24, 35, 66, 97, 80, 71, 42, 13 },
{ 34, 0, 101, 0, 90, 81, 52, 23 },
{ 44, 55, 86, 0, 0, 0, 0, 0 },
{ 54, 65, 96, 0, 102, 0, 72, 43 },
{ 64, 0, 0, 0, 0, 0, 82, 53 }
};
int32 entryPoint = _vm->_state->getVar(26);
int32 movie = 0;
int32 exitPoint = 0;
if (_vm->_state->getVar(38 + entryPoint - 100)) {
_vm->_state->setVar(42, 0);
_vm->_state->setVar(26, 0);
return;
}
_vm->_state->setVar(38 + entryPoint - 100, 1);
switch (entryPoint) {
case 100:
_vm->_state->setVar(42, 0);
_vm->_state->setVar(26, 1);
return;
case 101:
movie = 12007;
exitPoint = 93;
break;
case 102:
movie = 14007;
exitPoint = 75;
break;
case 103:
movie = 16007;
exitPoint = 17;
break;
default:
_vm->_state->setVar(42, 0);
_vm->_state->setVar(26, 0);
return;
}
int32 recursion = 20;
while (1) {
int32 switchIndex = exitPoint / 10 - 1;
int32 switchFrame = _vm->_state->getVar(449 + switchIndex);
int32 switchPosition = 2 * (switchFrame - 1) / 3;
int32 direction = map1[switchIndex][(exitPoint % 10 - switchPosition) & 7];
if (direction != 9)
exitPoint = map2[switchIndex][(switchPosition + direction) & 7];
else
exitPoint = 0;
if (!recursion)
break;
recursion--;
if (exitPoint <= 0 || exitPoint >= 100) {
_vm->_state->setVar(42, exitPoint);
_vm->_state->setVar(26, movie);
return;
}
}
_vm->_state->setVar(42, 0);
_vm->_state->setVar(26, movie);
}
void Puzzles::mainMenu(uint16 action) {
_vm->setMenuAction(action);
}
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 == nullptr && "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, Texture::getRGBAPixelFormat());
ResourceDescription movieDesc = _vm->getFileDescription("", bitmap, 0, Archive::kStillMovie);
if (!movieDesc.isValid())
error("Movie %d does not exist", bitmap);
// Rebuild the complete background image from the frames of the bink movie
Common::SeekableReadStream *movieStream = movieDesc.getData();
Video::BinkDecoder bink;
bink.loadStream(movieStream);
bink.setOutputPixelFormat(Texture::getRGBAPixelFormat());
bink.start();
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 != nullptr && "Projector background already used.");
// Nothing to do if the spotitem is not enabled
if (!_vm->_state->getVar(26))
return;
ResourceDescription movieDesc = _vm->getFileDescription("", bitmap, 0, Archive::kStillMovie);
if (!movieDesc.isValid())
error("Movie %d does not exist", bitmap);
// Rebuild the complete background image from the frames of the bink movie
Common::SeekableReadStream *movieStream = movieDesc.getData();
Video::BinkDecoder bink;
bink.loadStream(movieStream);
bink.setOutputPixelFormat(Texture::getRGBAPixelFormat());
bink.start();
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;
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;
_vm->_state->setProjectorAngleX(angleX);
_vm->_state->setProjectorAngleY(angleY);
_vm->_state->setProjectorAngleZoom(angleZoom);
_vm->_state->setProjectorAngleBlur(angleBlur);
_vm->_state->setProjectorX(x);
_vm->_state->setProjectorY(y);
_vm->_state->setProjectorZoom(zoom);
_vm->_state->setProjectorBlur(blur);
}
void Puzzles::settingsSave() {
ConfMan.flushToDisk();
}
void Puzzles::updateSoundScriptTimer() {
int frequency = 15 * ConfMan.getInt("music_frequency") / 100;
if (_vm->_state->getSoundScriptsPaused()) {
_vm->_state->setSoundScriptsTimer(60 * (20 - frequency));
} else {
_vm->_state->setSoundScriptsTimer(60 * (frequency + 5));
}
}
void Puzzles::checkCanSave() {
// There is no reason to forbid saving games with ScummVM,
// since there is no notion of memory card, free blocks and such.
_vm->_state->setStateCanSave(true);
}
} // End of namespace Myst3