1 /* ResidualVM - A 3D game interpreter
2 *
3 * ResidualVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the AUTHORS
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "engines/myst3/puzzles.h"
24 #include "engines/myst3/ambient.h"
25 #include "engines/myst3/menu.h"
26 #include "engines/myst3/myst3.h"
27 #include "engines/myst3/node.h"
28 #include "engines/myst3/state.h"
29 #include "engines/myst3/sound.h"
30
31 #include "common/config-manager.h"
32
33 namespace Myst3 {
34
Puzzles(Myst3Engine * vm)35 Puzzles::Puzzles(Myst3Engine *vm) :
36 _vm(vm) {
37 }
38
~Puzzles()39 Puzzles::~Puzzles() {
40 }
41
run(uint16 id,uint16 arg0,uint16 arg1,uint16 arg2)42 void Puzzles::run(uint16 id, uint16 arg0, uint16 arg1, uint16 arg2) {
43 switch (id) {
44 case 1:
45 leversBall(arg0);
46 break;
47 case 2:
48 tesla(arg0, arg1, arg2);
49 break;
50 case 3:
51 resonanceRingControl();
52 break;
53 case 4:
54 resonanceRingsLaunchBall();
55 break;
56 case 5:
57 resonanceRingsLights();
58 break;
59 case 6:
60 pinball(arg0);
61 break;
62 case 7:
63 weightDrag(arg0, arg1);
64 break;
65 case 8:
66 journalSaavedro(arg0);
67 break;
68 case 9:
69 journalAtrus(arg0, arg1);
70 break;
71 case 10:
72 symbolCodesInit(arg0, arg1, arg2);
73 break;
74 case 11:
75 symbolCodesClick(arg0);
76 break;
77 case 12:
78 railRoadSwitchs();
79 break;
80 case 13:
81 rollercoaster();
82 break;
83 case 14:
84 projectorLoadBitmap(arg0);
85 break;
86 case 15:
87 projectorAddSpotItem(arg0, arg1, arg2);
88 break;
89 case 16:
90 projectorUpdateCoordinates();
91 break;
92 case 17:
93 _vm->settingsLoadToVars();
94 break;
95 case 18:
96 _vm->settingsApplyFromVars();
97 break;
98 case 19:
99 settingsSave();
100 break;
101 case 20:
102 _vm->_menu->saveLoadAction(arg0, arg1);
103 break;
104 case 21:
105 mainMenu(arg0);
106 break;
107 case 22:
108 updateSoundScriptTimer();
109 break;
110 case 23:
111 _vm->loadNodeSubtitles(arg0);
112 break;
113 case 25:
114 checkCanSave(); // Xbox specific
115 break;
116 default:
117 warning("Puzzle %d is not implemented", id);
118 }
119 }
120
_drawForVarHelper(int16 var,int32 startValue,int32 endValue)121 void Puzzles::_drawForVarHelper(int16 var, int32 startValue, int32 endValue) {
122 uint startTick = _vm->_state->getTickCount();
123 uint currentTick = startTick;
124 uint numValues = abs(endValue - startValue);
125 uint endTick = startTick + 2 * numValues;
126
127 int16 var2 = var;
128
129 if (var < 0)
130 var = -var;
131 if (var2 < 0)
132 var2 = -var2 + 1;
133
134 if (startTick < endTick) {
135 int currentValue = -9999;
136 while (1) {
137 int nextValue = (currentTick - startTick) / 2;
138 if (currentValue != nextValue) {
139 currentValue = nextValue;
140
141 int16 varValue;
142 if (endValue > startValue)
143 varValue = startValue + currentValue;
144 else
145 varValue = startValue - currentValue;
146
147 _vm->_state->setVar(var, varValue);
148 _vm->_state->setVar(var2, varValue);
149 }
150
151 _vm->processInput(false);
152 _vm->drawFrame();
153 currentTick = _vm->_state->getTickCount();
154
155 if (currentTick > endTick || _vm->shouldQuit())
156 break;
157 }
158 }
159
160 _vm->_state->setVar(var, endValue);
161 _vm->_state->setVar(var2, endValue);
162 }
163
_drawXTicks(uint16 ticks)164 void Puzzles::_drawXTicks(uint16 ticks) {
165 uint32 endTick = _vm->_state->getTickCount() + ticks;
166
167 while (_vm->_state->getTickCount() < endTick && !_vm->shouldQuit()) {
168 _vm->processInput(false);
169 _vm->drawFrame();
170 }
171 }
172
leversBall(int16 var)173 void Puzzles::leversBall(int16 var) {
174 struct NewPosition {
175 bool newLeft;
176 bool newRight;
177 uint16 newBallPosition;
178 uint16 movieStart;
179 uint16 movieEnd;
180 uint16 movieBallStart;
181 uint16 movieBallEnd;
182 };
183
184 struct Move {
185 int16 oldLeft;
186 int16 oldRight;
187 uint16 oldBallPosition;
188 NewPosition p[2];
189 };
190
191 static const Move moves[] = {
192 { 0, 1, 2, { { 1, 1, 2, 127, 147, 0, 0 }, { 0, 0, 0, 703, 735, 0, 0 } } },
193 { 0, 0, 4, { { 1, 0, 4, 43, 63, 0, 0 }, { 0, 1, 4, 64, 84, 0, 0 } } },
194 { 0, 0, 1, { { 1, 0, 1, 85, 105, 0, 0 }, { 0, 1, 1, 22, 42, 0, 0 } } },
195 { 1, 0, 4, { { 1, 1, 3, 514, 534, 169, 217 }, { 0, 0, 4, 577, 597, 0, 0 } } },
196 { 1, 0, 3, { { 1, 1, 3, 493, 513, 0, 0 }, { 0, 0, 4, 451, 471, 410, 450 } } },
197 { 1, 0, 1, { { 1, 1, 2, 472, 492, 312, 360 }, { 0, 0, 1, 598, 618, 0, 0 } } },
198 { 0, 1, 4, { { 1, 1, 3, 148, 168, 169, 217 }, { 0, 0, 4, 619, 639, 0, 0 } } },
199 { 0, 1, 2, { { 1, 1, 2, 127, 147, 0, 0 }, { 0, 0, 1, 1, 21, 271, 311 } } },
200 { 0, 1, 1, { { 1, 1, 2, 106, 126, 312, 360 }, { 0, 0, 1, 640, 660, 0, 0 } } },
201 { 1, 1, 3, { { 1, 0, 3, 661, 681, 0, 0 }, { 0, 1, 2, 535, 555, 218, 270 } } },
202 { 1, 1, 2, { { 1, 0, 3, 556, 575, 361, 409 }, { 0, 1, 2, 682, 702, 0, 0 } } },
203 { 0, 0, 0, { { 1, 0, 0, 757, 777, 0, 0 }, { 0, 1, 0, 736, 756, 0, 0 } } },
204 { 1, 0, 0, { { 1, 1, 0, 799, 819, 0, 0 }, { 0, 0, 0, 841, 861, 0, 0 } } },
205 { 0, 1, 0, { { 1, 1, 0, 778, 798, 0, 0 }, { 0, 0, 0, 820, 840, 0, 0 } } },
206 { 1, 1, 0, { { 1, 0, 0, 883, 903, 0, 0 }, { 0, 1, 0, 862, 882, 0, 0 } } },
207 { -1, 0, 0, { { 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0 } } }
208 };
209
210 uint16 oldPosition = _vm->_state->getBallPosition();
211 uint16 oldLeverLeft = _vm->_state->getBallLeverLeft();
212 uint16 oldLeverRight = _vm->_state->getBallLeverRight();
213
214 // Toggle lever position
215 _vm->_state->setVar(var, !_vm->_state->getVar(var));
216
217 uint16 newLeverLeft = _vm->_state->getBallLeverLeft();
218 uint16 newLeverRight = _vm->_state->getBallLeverRight();
219
220 const Move *move = nullptr;
221 for (uint i = _vm->_state->getBallDoorOpen() ? 0 : 1; i < ARRAYSIZE(moves); i++)
222 if (moves[i].oldBallPosition == oldPosition
223 && moves[i].oldLeft == oldLeverLeft
224 && moves[i].oldRight == oldLeverRight) {
225 move = &moves[i];
226 break;
227 }
228
229 if (!move)
230 error("Unable to find move with old levers l:%d r:%d p:%d", oldLeverLeft, oldLeverRight, oldPosition);
231
232 const NewPosition *position = nullptr;
233 for (uint i = 0; i < ARRAYSIZE(move->p); i++)
234 if (move->p[i].newLeft == newLeverLeft
235 && move->p[i].newRight == newLeverRight) {
236 position = &move->p[i];
237 break;
238 }
239
240 if (!position)
241 error("Unable to find position with levers l:%d r:%d", newLeverLeft, newLeverRight);
242
243 _vm->_sound->playEffect(789, 50);
244 _drawForVarHelper(35, position->movieStart, position->movieEnd);
245
246 if (position->newBallPosition != oldPosition) {
247 uint16 sound;
248 if (position->newBallPosition == 0) {
249 sound = 792;
250 } else if (position->newBallPosition == 1 || position->newBallPosition == 4) {
251 sound = 790;
252 } else {
253 sound = 791;
254 }
255
256 _vm->_sound->playEffect(sound, 50);
257
258 if (position->movieBallStart != 0) {
259 _drawForVarHelper(35, position->movieBallStart, position->movieBallEnd);
260 }
261 }
262
263 _vm->_state->setBallPosition(position->newBallPosition);
264 _vm->_state->setBallFrame(_vm->_state->getVar(35));
265 }
266
tesla(int16 movie,int16 var,int16 move)267 void Puzzles::tesla(int16 movie, int16 var, int16 move) {
268 uint16 node = _vm->_state->getLocationNode();
269
270 int16 movieStart = 0;
271 switch (node) {
272 case 114:
273 movieStart = 0;
274 break;
275 case 116:
276 movieStart = 320;
277 break;
278 case 118:
279 movieStart = 240;
280 break;
281 case 120:
282 movieStart = 160;
283 break;
284 case 122:
285 movieStart = 80;
286 break;
287 }
288
289 _vm->_state->setTeslaMovieStart(movieStart);
290
291 uint16 position = movieStart + _vm->_state->getVar(var);
292
293 if (position > 400)
294 position -= 400;
295
296 _vm->_state->setVar(32, node % 100);
297 _vm->_state->setVar(33, node % 100 + 10000);
298
299 if (movie) {
300 _vm->_sound->playEffect(1243, 100);
301 _vm->_state->setMovieSynchronized(true);
302 _vm->playSimpleMovie(movie);
303 }
304
305 if (move) {
306 uint16 sound = _vm->_rnd->getRandomNumberRng(1244, 1245);
307 _vm->_sound->playEffect(sound, 100);
308 }
309
310 if (move > 0) {
311 _drawForVarHelper(var - 303, position + 1, position + 19);
312 position += 20;
313 } else if (move < 0) {
314 if (position == 1)
315 position = 401;
316
317 _drawForVarHelper(var - 303, position - 1, position - 19);
318 position -= 20;
319 }
320
321 if (position < 1)
322 position = 381;
323 else if (position > 400)
324 position = 1;
325
326 _vm->_state->setVar(var - 303, position);
327
328 int16 absPosition = position - movieStart;
329
330 if (absPosition < 1)
331 absPosition += 400;
332
333 _vm->_state->setVar(var, absPosition);
334
335 bool puzzleSolved = _vm->_state->getTeslaTopAligned() == 1
336 && _vm->_state->getTeslaMiddleAligned() == 1
337 && _vm->_state->getTeslaBottomAligned() == 1;
338
339 _vm->_state->setTeslaAllAligned(puzzleSolved);
340 }
341
resonanceRingControl()342 void Puzzles::resonanceRingControl() {
343 static const uint16 frames[] = { 0, 24, 1, 5, 10, 15, 0, 0, 0 };
344
345 uint16 startPos = _vm->_state->getVar(29);
346 uint16 destPos = _vm->_state->getVar(27);
347
348 int16 startFrame = frames[startPos] - 27;
349 int16 destFrame = frames[destPos];
350
351 // Choose the shortest direction
352 for (int16 i = destFrame - startFrame; abs(i) > 14; i -= 27)
353 startFrame += 27;
354
355 // Play the movie, taking care of the limit case
356 if (destFrame >= startFrame) {
357 if (startFrame < 1) {
358 _drawForVarHelper(28, startFrame + 27, 27);
359 _drawForVarHelper(28, 1, destFrame);
360 return;
361 }
362 } else {
363 if (startFrame > 27) {
364 _drawForVarHelper(28, startFrame - 27, 1);
365 _drawForVarHelper(28, 27, destFrame);
366 return;
367 }
368 }
369 if (startFrame)
370 _drawForVarHelper(28, startFrame, destFrame);
371 }
372
resonanceRingsLaunchBall()373 void Puzzles::resonanceRingsLaunchBall() {
374 struct TrackFrames {
375 uint16 ringFrame;
376 uint16 var;
377 uint16 num;
378 uint16 shatterStartFrame;
379 uint16 shatterEndFrame;
380 };
381
382 static const TrackFrames tracks[] = {
383 { 38, 436, 1, 182, 190 },
384 { 74, 434, 2, 194, 214 },
385 { 104, 437, 3, 215, 224 },
386 { 138, 435, 4, 225, 234 },
387 { 166, 438, 5, 235, 244 },
388 { 0, 0, 0, 0, 0 }
389 };
390
391 struct LightFrames {
392 uint16 startFrame;
393 uint16 endFrame;
394 uint16 num;
395 };
396
397 static const LightFrames lights[] = {
398 { 26, 44, 1 },
399 { 66, 85, 2 },
400 { 89, 118, 3 },
401 { 126, 150, 4 },
402 { 154, 180, 5 }
403 };
404
405 bool ballShattered = false;
406 bool lastIsOnLightButton = false;
407 int32 lightStatus = 0;
408 uint part = 0;
409 uint16 buttonVar = 0;
410 int32 ballMoviePlaying;
411 int32 boardMoviePlaying;
412
413 do {
414 _vm->processInput(false);
415 _vm->drawFrame();
416
417 ballMoviePlaying = _vm->_state->getVar(27);
418 boardMoviePlaying = _vm->_state->getVar(34);
419
420 if (ballMoviePlaying && tracks[part].ringFrame) {
421 int32 currentFrame = _vm->_state->getVar(30);
422
423 if (!ballShattered && currentFrame >= tracks[part].ringFrame) {
424 int32 value = _vm->_state->getVar(tracks[part].var);
425
426 if (value == tracks[part].num) {
427 // Correct ring order, go to next track part
428 part++;
429 } else {
430 // Incorrect ring order, shatter ball
431 ballShattered = true;
432 _vm->_sound->playEffect(1010, 50);
433
434 _vm->_state->setVar(28, tracks[part].shatterStartFrame);
435 _vm->_state->setVar(29, tracks[part].shatterEndFrame);
436 _vm->_state->setVar(31, tracks[part].shatterStartFrame);
437 }
438 }
439 }
440
441 bool isOnLightButton = false;
442
443 const LightFrames *frames = 0;
444 int32 currentLightFrame = _vm->_state->getVar(33);
445
446 // Look is the mini ball is on a light button
447 for (uint j = 0; j < ARRAYSIZE(lights); j++)
448 if (currentLightFrame >= lights[j].startFrame && currentLightFrame <= lights[j].endFrame) {
449 frames = &lights[j];
450 break;
451 }
452
453 // If ball on light button, turn it off
454 if (frames) {
455 for (uint j = 0; j < 5; j++) {
456 int32 ringValue = _vm->_state->getVar(434 + j);
457 if (ringValue == frames->num)
458 _vm->_state->setVar(38 + j, true);
459 }
460
461 isOnLightButton = true;
462 buttonVar = 438 + frames->num;
463 }
464
465 // Restore previous light value
466 if (lastIsOnLightButton != isOnLightButton) {
467 lastIsOnLightButton = isOnLightButton;
468 if (isOnLightButton) {
469 lightStatus = _vm->_state->getVar(buttonVar);
470 _vm->_state->setVar(buttonVar, 0);
471 } else {
472 _vm->_state->setVar(buttonVar, lightStatus);
473
474 for (uint j = 0; j < 5; j++)
475 _vm->_state->setVar(38 + j, false);
476 }
477
478 _vm->_ambient->playCurrentNode(100, 2);
479 }
480 } while ((ballMoviePlaying || boardMoviePlaying) && !_vm->shouldQuit());
481
482 _vm->_state->setResonanceRingsSolved(!ballShattered);
483 }
484
resonanceRingsLights()485 void Puzzles::resonanceRingsLights() {
486 // Turn off all lights
487 for (uint i = 0; i < 5; i++)
488 _vm->_state->setVar(439 + i, false);
489
490 // For each button / ring value
491 for (uint i = 0; i < 5; i++) {
492 // For each light
493 for (uint j = 0; j < 5; j++) {
494 // Ring selector value
495 uint32 ringValue = _vm->_state->getVar(434 + j);
496 if (ringValue == i + 1) {
497 // Button state
498 uint32 buttonState = _vm->_state->getVar(43 + i);
499 if (buttonState) {
500 uint32 oldValue = _vm->_state->getVar(444 + i);
501 _vm->_state->setVar(439 + i, oldValue);
502 _vm->_state->setVar(38 + j, true);
503 } else {
504 _vm->_state->setVar(38 + j, false);
505 }
506 }
507 }
508 }
509
510 _vm->_ambient->playCurrentNode(100, 2);
511 }
512
pinball(int16 var)513 void Puzzles::pinball(int16 var) {
514 static const byte remainingPegsFrames[] = { 2, 15, 25, 32 };
515
516 static const PegCombination leftPegs[] = {
517 { 10101, { 0, 1, 0, 0, 0 }, { 0, 0, 0 }, 300 },
518 { 10102, { 1, 1, 0, 0, 0 }, { 49, 0, 0 }, 310 },
519 { 10103, { 0, 1, 0, 1, 0 }, { 200, 0, 0 }, 310 },
520 { 10104, { 0, 1, 1, 0, 0 }, { 150, 0, 0 }, 305 },
521 { 10105, { 0, 1, 0, 0, 1 }, { 250, 0, 0 }, 305 },
522 { 10106, { 1, 1, 0, 1, 0 }, { 49, 205, 0 }, 310 },
523 { 10107, { 1, 1, 1, 0, 0 }, { 49, 155, 0 }, 309 },
524 { 10108, { 1, 1, 0, 0, 1 }, { 49, 253, 0 }, 310 },
525 { 10109, { 0, 1, 1, 1, 0 }, { 150, 205, 0 }, 310 },
526 { 10110, { 0, 1, 0, 1, 1 }, { 199, 254, 0 }, 309 },
527 { 10111, { 0, 1, 1, 0, 1 }, { 150, 254, 0 }, 309 },
528 { 10112, { 1, 1, 1, 1, 0 }, { 49, 155, 210 }, 315 },
529 { 10113, { 1, 1, 0, 1, 1 }, { 49, 205, 260 }, 315 },
530 { 10114, { 1, 1, 1, 0, 1 }, { 49, 155, 260 }, 315 },
531 { 10115, { 0, 1, 1, 1, 1 }, { 150, 205, 260 }, 315 }
532 };
533
534 static const PegCombination rightPegs[] = {
535 { 10201, { 0, 0, 0, 0, 0 }, { 0, 0, 0 }, 300 },
536 { 10202, { 1, 0, 0, 0, 0 }, { 250, 0, 0 }, 305 },
537 { 10203, { 0, 1, 0, 0, 0 }, { 200, 0, 0 }, 305 },
538 { 10204, { 0, 0, 1, 0, 0 }, { 150, 0, 0 }, 305 },
539 { 10205, { 0, 0, 0, 1, 0 }, { 100, 0, 0 }, 305 },
540 { 10206, { 0, 0, 0, 0, 1 }, { 50, 0, 0 }, 305 },
541 { 10207, { 1, 1, 0, 0, 0 }, { 200, 255, 0 }, 305 },
542 { 10208, { 1, 0, 1, 0, 0 }, { 150, 255, 0 }, 310 },
543 { 10209, { 1, 0, 0, 1, 0 }, { 100, 255, 0 }, 310 },
544 { 10210, { 1, 0, 0, 0, 1 }, { 50, 255, 0 }, 310 },
545 { 10211, { 0, 1, 1, 0, 0 }, { 150, 205, 0 }, 310 },
546 { 10212, { 0, 1, 0, 1, 0 }, { 100, 205, 0 }, 310 },
547 { 10213, { 0, 1, 0, 0, 1 }, { 50, 205, 0 }, 310 },
548 { 10214, { 0, 0, 1, 1, 0 }, { 100, 155, 0 }, 310 },
549 { 10215, { 0, 0, 1, 0, 1 }, { 50, 155, 0 }, 210 },
550 { 10216, { 0, 0, 0, 1, 1 }, { 50, 105, 0 }, 310 },
551 { 10217, { 1, 1, 1, 0, 0 }, { 150, 205, 260 }, 315 },
552 { 10218, { 1, 1, 0, 1, 0 }, { 100, 205, 260 }, 315 },
553 { 10219, { 1, 1, 0, 0, 1 }, { 50, 205, 260 }, 312 },
554 { 10220, { 1, 0, 1, 1, 0 }, { 100, 155, 260 }, 314 },
555 { 10221, { 1, 0, 1, 0, 1 }, { 50, 155, 259 }, 315 },
556 { 10222, { 1, 0, 0, 1, 1 }, { 50, 105, 260 }, 315 },
557 { 10223, { 0, 1, 1, 1, 0 }, { 100, 155, 210 }, 315 },
558 { 10224, { 0, 1, 1, 0, 1 }, { 50, 155, 210 }, 315 },
559 { 10225, { 0, 1, 0, 1, 1 }, { 50, 105, 210 }, 315 },
560 { 10226, { 0, 0, 1, 1, 1 }, { 50, 105, 155 }, 315 }
561 };
562
563 struct BallJump {
564 int16 positionLeft;
565 int16 positionRight;
566 int16 filter;
567 int16 startFrame;
568 int16 endFrame;
569 int16 sound;
570 int16 targetLeftFrame;
571 int16 tragetRightFrame;
572 int16 type;
573 };
574
575 static const BallJump jumps[] = {
576 { 0, 450, 1, 16, 28, 1021, 250, 550, 0 },
577 { 0, 450, -1, 29, 41, 1021, 500, 550, 3 },
578 { 0, 200, 0, 42, 57, 1023, 300, 500, 0 },
579 { 0, 200, -1, 58, 74, 1023, 550, 500, 3 },
580 { 0, 250, 1, 75, 90, 1023, 350, 550, 0 },
581 { 0, 250, -1, 91, 106, 1023, 500, 550, 3 },
582 { 0, 300, 0, 107, 119, 1021, 400, 500, 0 },
583 { 0, 300, -1, 120, 132, 1021, 550, 500, 3 },
584 { 0, 400, 0, 133, 165, 1022, 500, 500, 2 },
585 { 0, 400, 1, 1039, 1071, 1022, 550, 500, 2 },
586 { 0, 350, 0, 166, 198, 1022, 500, 550, 2 },
587 { 0, 350, 1, 1072, 1109, 1022, 550, 550, 2 },
588 { 250, 0, 1, 801, 815, 1021, 550, 450, 0 },
589 { 250, 0, -1, 816, 827, 1021, 550, 500, 4 },
590 { 300, 0, 0, 828, 845, 1023, 500, 200, 0 },
591 { 300, 0, -1, 846, 858, 1023, 500, 550, 3 },
592 { 350, 0, 1, 859, 876, 1023, 550, 250, 0 },
593 { 350, 0, -1, 0, 0, 0, 550, 500, 1 },
594 { 400, 0, 0, 893, 907, 1021, 500, 300, 0 },
595 { 400, 0, 1, 1267, 1278, 1023, 500, 550, 3 },
596 { 200, 0, 1, 908, 940, 1022, 500, 550, 2 },
597 { 200, 0, 0, 974, 1006, 1022, 500, 500, 2 },
598 { 450, 0, 1, 941, 973, 1022, 550, 550, 2 },
599 { 450, 0, 0, 1007, 1038, 1022, 550, 500, 2 }
600 };
601
602 struct BallExpireFrames {
603 uint16 leftPosition;
604 uint16 rightPosition;
605 uint16 startFrame;
606 uint16 endFrame;
607 };
608
609 static const BallExpireFrames ballExpireFrames[] = {
610 { 200, 200, 1105, 1131 },
611 { 250, 250, 1132, 1158 },
612 { 300, 300, 1159, 1185 },
613 { 350, 350, 1186, 1212 },
614 { 400, 400, 1213, 1239 },
615 { 450, 450, 1240, 1266 }
616 };
617
618 // Toggle peg state
619 if (var > 0) {
620 int32 value = _vm->_state->getVar(var);
621 if (value) {
622 _vm->_state->setVar(var, 0);
623 } else {
624 _vm->_state->setVar(var, 1);
625
626 // Play the "peg clicks into spot sound"
627 if (!_vm->_sound->isPlaying(1024)) {
628 _vm->_sound->playEffect(1024, 100);
629 }
630 }
631 }
632
633 // Remaining pegs movie
634 uint32 pegs = _vm->_state->getPinballRemainingPegs();
635 uint32 frame = remainingPegsFrames[pegs];
636 _vm->_state->setVar(33, frame);
637
638 // Choose pegs movie according to peg combination
639 const PegCombination *leftComb = _pinballFindCombination(461, leftPegs, ARRAYSIZE(leftPegs));
640 if (!leftComb)
641 error("Unable to find correct left pegs combination");
642 _vm->_state->setVar(31, leftComb->movie - 10100);
643
644 const PegCombination *rightComb = _pinballFindCombination(466, rightPegs, ARRAYSIZE(rightPegs));
645 if (!rightComb)
646 error("Unable to find correct right pegs combination");
647 _vm->_state->setVar(32, rightComb->movie - 10200);
648
649 if (var >= 0)
650 return;
651
652 _vm->_state->setWaterEffectRunning(false);
653
654 // Remove the default panel movies
655 _vm->removeMovie(10116);
656 _vm->removeMovie(10227);
657
658 // Set up left panel movie with the correct combination
659 _vm->_state->setMoviePreloadToMemory(true);
660 _vm->_state->setMovieScriptDriven(true);
661 _vm->_state->setMovieNextFrameGetVar(31);
662 _vm->loadMovie(leftComb->movie, 1, false, true);
663 _vm->_state->setVar(31, 2);
664
665 // Set up right panel movie with the correct combination
666 _vm->_state->setMoviePreloadToMemory(true);
667 _vm->_state->setMovieScriptDriven(true);
668 _vm->_state->setMovieNextFrameGetVar(32);
669 _vm->loadMovie(rightComb->movie, 1, false, true);
670 _vm->_state->setVar(32, 2);
671
672 // Launch sound
673 _vm->_sound->playEffect(1021, 50);
674 _drawForVarHelper(-34, 2, 15);
675 _drawXTicks(30);
676
677 int32 leftSideFrame = 250;
678 int32 rightSideFrame = 500;
679 _vm->_state->setVar(34, 250);
680 _vm->_state->setVar(35, 500);
681 int32 leftPanelFrame = 2;
682 int32 rightPanelFrame = 2;
683 int32 ballOnLeftSide = 1;
684 int32 ballOnRightSide = 0;
685 int32 ballShouldExpire = 0;
686 int32 ballCrashed = 0;
687 int32 leftToRightJumpCountDown = 0;
688 int32 rightToLeftJumpCountdown = 0;
689 int32 ballJumpedFromLeftSide = 1;
690 int32 ballJumpedFromRightSide = 0;
691 int32 jumpType = -1;
692
693 while (1) {
694 _drawXTicks(1);
695
696 bool shouldRotate;
697 if (leftToRightJumpCountDown >= 3 || rightToLeftJumpCountdown >= 3) {
698 shouldRotate = false;
699 _vm->_sound->stopEffect(1025, 7);
700 } else {
701 shouldRotate = true;
702 _vm->_sound->playEffectLooping(1025, 50, ballOnLeftSide != 0 ? 150 : 210, 95);
703 }
704
705 if (ballOnLeftSide && shouldRotate) {
706 ++leftSideFrame;
707
708 if (ballJumpedFromLeftSide) {
709 if (leftSideFrame >= 500)
710 leftSideFrame = 200;
711 } else {
712 if (leftSideFrame >= 800)
713 leftSideFrame = 500;
714 }
715
716 _vm->_state->setVar(34, leftSideFrame);
717 }
718
719 if (ballOnRightSide && shouldRotate) {
720 rightSideFrame++;
721
722 if (ballJumpedFromRightSide) {
723 if (rightSideFrame >= 500)
724 rightSideFrame = 200;
725 } else {
726 if (rightSideFrame >= 800)
727 rightSideFrame = 500;
728 }
729
730 _vm->_state->setVar(35, rightSideFrame);
731 }
732
733 if (ballOnLeftSide) {
734 leftPanelFrame++;
735 _vm->_state->setVar(31, leftPanelFrame);
736
737 for (uint i = 0; i < 3; i++) {
738 if (leftComb->pegFrames[i] == leftPanelFrame) {
739 _vm->_sound->playEffect(1027, 50);
740 leftToRightJumpCountDown = 5;
741 }
742 }
743
744 if (leftPanelFrame == leftComb->expireFrame) {
745 ballShouldExpire = 1;
746 ballOnLeftSide = 0;
747 }
748 }
749
750 if (ballOnRightSide) {
751 rightPanelFrame++;
752 _vm->_state->setVar(32, rightPanelFrame);
753
754 for (uint i = 0; i < 3; i++) {
755 if (rightComb->pegFrames[i] == rightPanelFrame) {
756 _vm->_sound->playEffect(1027, 50);
757 rightToLeftJumpCountdown = 5;
758 }
759 }
760
761 if (rightPanelFrame == rightComb->expireFrame) {
762 ballShouldExpire = 1;
763 ballOnRightSide = 0;
764 }
765 }
766
767 bool ballShouldJump = false;
768 if (leftToRightJumpCountDown) {
769 --leftToRightJumpCountDown;
770 if (!leftToRightJumpCountDown) {
771 ballOnLeftSide = 0;
772 ballShouldJump = true;
773 }
774 }
775
776 if (rightToLeftJumpCountdown) {
777 --rightToLeftJumpCountdown;
778 if (!rightToLeftJumpCountdown) {
779 ballOnRightSide = 0;
780 ballShouldJump = true;
781 }
782 }
783
784 if (ballShouldJump) {
785 _vm->_sound->stopEffect(1025, 7);
786 _drawXTicks(30);
787
788 int32 jumpPositionLeft = 50 * ((leftSideFrame + 25) / 50);
789 int32 jumpPositionRight = 50 * ((rightSideFrame + 25) / 50);
790
791 const BallJump *jump = 0;
792
793 for (uint i = 0; i < ARRAYSIZE(jumps); i++) {
794 int32 filter = jumps[i].filter;
795 if (filter != -1) {
796 if (filter) {
797 if (!(jumpPositionLeft % 100))
798 continue;
799 } else {
800 if (jumpPositionLeft % 100)
801 continue;
802 }
803 }
804
805 if (abs(jumps[i].positionRight - jumpPositionRight) < 10) {
806 ballOnRightSide = 0;
807 ballOnLeftSide = 1;
808 ballJumpedFromRightSide = 0;
809 ballJumpedFromLeftSide = 1;
810 jump = &jumps[i];
811 break;
812 }
813 }
814
815 for (uint i = 0; i < ARRAYSIZE(jumps); i++) {
816 int32 filter = jumps[i].filter;
817 if (filter != -1) {
818 if (filter) {
819 if (!(jumpPositionRight % 100))
820 continue;
821 } else {
822 if (jumpPositionRight % 100)
823 continue;
824 }
825 }
826
827 if (abs(jumps[i].positionLeft - jumpPositionLeft) < 10) {
828 ballOnLeftSide = 0;
829 ballOnRightSide = 1;
830 ballJumpedFromRightSide = 1;
831 ballJumpedFromLeftSide = 0;
832 jump = &jumps[i];
833 break;
834 }
835 }
836
837 if (!jump)
838 error("Bad orb jump combo %d %d", jumpPositionLeft, jumpPositionRight);
839
840 jumpType = jump->type;
841
842 int32 sound = jump->sound;
843 if (sound)
844 _vm->_sound->playEffect(sound, 50);
845
846 int32 jumpStartFrame = jump->startFrame;
847 if (jumpStartFrame)
848 _drawForVarHelper(-34, jumpStartFrame, jump->endFrame);
849
850 if (jumpType == 3) {
851 _drawXTicks(6);
852 _vm->_sound->playEffect(1028, 50);
853 } else if (jumpType == 1 || jumpType == 4) {
854 _vm->_state->setVar(26, jumpType);
855 _vm->_state->setWaterEffectRunning(true);
856 _vm->_sound->stopEffect(1025, 7);
857 return;
858 }
859
860 leftSideFrame = jump->targetLeftFrame;
861 rightSideFrame = jump->tragetRightFrame;
862 _vm->_state->setVar(34, leftSideFrame);
863 _vm->_state->setVar(35, rightSideFrame);
864
865 if (jumpType >= 2)
866 ballCrashed = 1;
867
868 _drawXTicks(30);
869 }
870
871 if (ballShouldExpire) {
872 leftSideFrame = 50 * ((leftSideFrame + 25) / 50);
873 rightSideFrame = 50 * ((rightSideFrame + 25) / 50);
874
875 if (leftSideFrame == 500)
876 leftSideFrame = 200;
877
878 if (rightSideFrame == 500)
879 rightSideFrame = 200;
880
881 _vm->_sound->stopEffect(1025, 7);
882 _vm->_sound->playEffectFadeInOut(1005, 65, 0, 0, 5, 60, 20);
883 _drawXTicks(55);
884 _vm->_sound->playEffect(1010, 50);
885
886 for (uint i = 0; i < ARRAYSIZE(ballExpireFrames); i++) {
887 if (ballJumpedFromLeftSide && ballExpireFrames[i].leftPosition == leftSideFrame) {
888 _drawForVarHelper(34, ballExpireFrames[i].startFrame, ballExpireFrames[i].endFrame);
889 break;
890 }
891
892 if (ballJumpedFromRightSide && ballExpireFrames[i].rightPosition == rightSideFrame) {
893 _drawForVarHelper(35, ballExpireFrames[i].startFrame, ballExpireFrames[i].endFrame);
894 break;
895 }
896 }
897
898 _drawXTicks(15);
899 break;
900 }
901
902 if (ballCrashed)
903 break;
904 }
905
906 if (ballCrashed || ballShouldExpire) {
907 if (leftSideFrame < 500)
908 leftSideFrame += 300;
909 if (rightSideFrame < 500)
910 rightSideFrame += 300;
911
912 int32 crashedLeftFrame = ((((leftSideFrame + 25) / 50) >> 4) & 1) != 0 ? 550 : 500;
913 int32 crashedRightFrame = ((((rightSideFrame + 25) / 50) >> 4) & 1) != 0 ? 550 : 500;
914
915 while (1) {
916 bool moviePlaying = false;
917 if ((leftComb->movie != 10101 || leftPanelFrame > 2)
918 && leftPanelFrame != leftComb->expireFrame) {
919
920 if (leftToRightJumpCountDown) {
921 --leftToRightJumpCountDown;
922 }
923 if (!leftToRightJumpCountDown) {
924 _vm->_state->setVar(34, crashedLeftFrame);
925 crashedLeftFrame++;
926 }
927
928 _vm->_state->setVar(31, leftPanelFrame);
929
930 ++leftPanelFrame;
931 leftSideFrame = leftPanelFrame;
932
933 for (uint i = 0; i < 3; i++) {
934 if (leftComb->pegFrames[i] == leftSideFrame) {
935 _vm->_sound->playEffect(1027, 50);
936 leftToRightJumpCountDown = 5;
937 }
938 }
939
940 moviePlaying = true;
941 }
942
943 if (!moviePlaying) {
944 if ((rightComb->movie != 10201 || rightPanelFrame > 2)
945 && rightPanelFrame != rightComb->expireFrame) {
946
947 if (rightToLeftJumpCountdown) {
948 --rightToLeftJumpCountdown;
949 }
950 if (!rightToLeftJumpCountdown) {
951 _vm->_state->setVar(35, crashedRightFrame);
952 crashedRightFrame++;
953 }
954
955 _vm->_state->setVar(32, rightPanelFrame);
956
957 ++rightPanelFrame;
958 rightSideFrame = rightPanelFrame;
959
960 for (uint i = 0; i < 3; i++) {
961 if (rightComb->pegFrames[i] == rightSideFrame) {
962 _vm->_sound->playEffect(1027, 50);
963 rightToLeftJumpCountdown = 5;
964 }
965 }
966
967 moviePlaying = true;
968 }
969 }
970
971 _drawXTicks(1);
972
973 if (!moviePlaying) {
974 _vm->_state->setVar(26, jumpType);
975 _vm->_state->setVar(93, 1);
976 _vm->_sound->stopEffect(1025, 7);
977 return;
978 }
979
980 _vm->_sound->playEffectLooping(1025, 50);
981 }
982 }
983 }
984
_pinballFindCombination(uint16 var,const PegCombination pegs[],uint16 size)985 const Puzzles::PegCombination *Puzzles::_pinballFindCombination(uint16 var, const PegCombination pegs[], uint16 size) {
986 const PegCombination *combination = 0;
987
988 for (uint i = 0; i < size; i++) {
989 bool good = true;
990 for (uint j = 0; j < 5; j++) {
991 bool setPeg = _vm->_state->getVar(var + j);
992 bool targetPeg = pegs[i].pegs[j];
993 if (setPeg != targetPeg)
994 good = false;
995 }
996
997 if (good) {
998 combination = &pegs[i];
999 break;
1000 }
1001 }
1002
1003 return combination;
1004 }
1005
weightDrag(uint16 var,uint16 movie)1006 void Puzzles::weightDrag(uint16 var, uint16 movie) {
1007 if (var >= 429 && var <= 432) {
1008 movie = _vm->_state->getVar(var);
1009 _vm->_state->setVar(var, 0);
1010 var = movie;
1011 }
1012
1013 uint16 sound = 0;
1014 if (var) {
1015 switch (var) {
1016 case 423:
1017 movie = 1022;
1018 sound = 921;
1019 break;
1020 case 425:
1021 movie = 1023;
1022 sound = 921;
1023 break;
1024 case 424:
1025 movie = 1024;
1026 sound = 922;
1027 break;
1028 case 427:
1029 movie = 1025;
1030 sound = 922;
1031 break;
1032 case 426:
1033 movie = 1020;
1034 sound = 920;
1035 break;
1036 case 428:
1037 movie = 1021;
1038 sound = 920;
1039 break;
1040 default:
1041 break;
1042 }
1043
1044 _vm->_state->setDraggedWeight(var);
1045 _vm->dragItem(var, movie, 1, 2, 26);
1046 _vm->_sound->playEffect(sound, 25);
1047 }
1048
1049 for (uint i = 0; i < 4; i++) {
1050 int32 value = _vm->_state->getVar(429 + i);
1051 uint16 frame = 0;
1052 switch (value) {
1053 case 423:
1054 case 425:
1055 frame = 2;
1056 break;
1057 case 424:
1058 case 427:
1059 frame = 3;
1060 break;
1061 case 426:
1062 case 428:
1063 frame = 1;
1064 break;
1065 default:
1066 break;
1067 }
1068 _vm->_state->setVar(28 + i, frame);
1069 _vm->_state->setVar(32 + i, frame != 0);
1070 }
1071 }
1072
journalSaavedro(int16 move)1073 void Puzzles::journalSaavedro(int16 move) {
1074 uint16 chapter = _vm->_state->getJournalSaavedroChapter();
1075 int16 page = _vm->_state->getJournalSaavedroPageInChapter();
1076
1077 if (!_journalSaavedroHasChapter(chapter))
1078 chapter = _journalSaavedroNextChapter(chapter, true);
1079
1080 if (move > 0) {
1081 // Go to the next available page
1082 int16 pageCount = _journalSaavedroPageCount(chapter);
1083 page++;
1084
1085 if (page == pageCount) {
1086 chapter = _journalSaavedroNextChapter(chapter, true);
1087 page = 0;
1088 }
1089
1090 _vm->_state->setJournalSaavedroChapter(chapter);
1091 _vm->_state->setJournalSaavedroPageInChapter(page);
1092 } else if (move < 0) {
1093 // Go to the previous available page
1094 page--;
1095
1096 if (page < 0) {
1097 chapter = _journalSaavedroNextChapter(chapter, false);
1098 page = _journalSaavedroPageCount(chapter) - 1;
1099 }
1100
1101 _vm->_state->setJournalSaavedroChapter(chapter);
1102 _vm->_state->setJournalSaavedroPageInChapter(page);
1103 } else {
1104 // Display current page
1105 int16 chapterStartNode = _journalSaavedroGetNode(chapter);
1106 int16 closed = 0;
1107 int16 opened = 0;
1108 int16 lastPage = 0;
1109
1110 if (chapter > 0) {
1111 opened = 1;
1112 if (chapter == 21)
1113 lastPage = _journalSaavedroLastPageLastChapterValue();
1114 else
1115 lastPage = 1;
1116
1117 } else {
1118 closed = 1;
1119 }
1120
1121 uint16 nodeRight;
1122 uint16 nodeLeft;
1123 if (page || !chapter) {
1124 nodeRight = chapterStartNode + page;
1125 nodeLeft = chapterStartNode + page;
1126 } else {
1127 nodeRight = chapterStartNode + page;
1128 uint16 chapterLeft = _journalSaavedroNextChapter(chapter, false);
1129 if (chapterLeft > 0)
1130 nodeLeft = _journalSaavedroGetNode(chapterLeft + 1);
1131 else
1132 nodeLeft = 201;
1133 }
1134
1135 _vm->_state->setJournalSaavedroClosed(closed);
1136 _vm->_state->setJournalSaavedroOpen(opened);
1137 _vm->_state->setJournalSaavedroLastPage(lastPage);
1138
1139 _vm->loadNodeFrame(nodeRight);
1140
1141 // Does the left page need to be loaded from a different node?
1142 if (nodeLeft != nodeRight) {
1143 ResourceDescription jpegDesc = _vm->getFileDescription("", nodeLeft, 0, Archive::kFrame);
1144
1145 if (!jpegDesc.isValid())
1146 error("Frame %d does not exist", nodeLeft);
1147
1148 Graphics::Surface *bitmap = Myst3Engine::decodeJpeg(&jpegDesc);
1149
1150 // Copy the left half of the node to a new surface
1151 Graphics::Surface *leftBitmap = new Graphics::Surface();
1152 leftBitmap->create(bitmap->w / 2, bitmap->h, Texture::getRGBAPixelFormat());
1153
1154 for (int i = 0; i < bitmap->h; i++) {
1155 memcpy(leftBitmap->getBasePtr(0, i),
1156 bitmap->getBasePtr(0, i),
1157 leftBitmap->w * 4);
1158 }
1159
1160 bitmap->free();
1161 delete bitmap;
1162
1163 // Create a spotitem covering the left half of the screen
1164 // to display the left page
1165 SpotItemFace *leftPage = _vm->addMenuSpotItem(999, 1, Common::Rect(0, 0, leftBitmap->w, leftBitmap->h));
1166
1167 leftPage->updateData(leftBitmap);
1168
1169 leftBitmap->free();
1170 delete leftBitmap;
1171 }
1172 }
1173 }
1174
_journalSaavedroLastPageLastChapterValue()1175 int16 Puzzles::_journalSaavedroLastPageLastChapterValue() {
1176 // The scripts just expect different values ...
1177 if (_vm->getPlatform() == Common::kPlatformXbox) {
1178 return 0;
1179 } else {
1180 return 2;
1181 }
1182 }
1183
_journalSaavedroGetNode(uint16 chapter)1184 uint16 Puzzles::_journalSaavedroGetNode(uint16 chapter) {
1185 ResourceDescription desc = _vm->getFileDescription("", 1200, 0, Archive::kNumMetadata);
1186
1187 if (!desc.isValid())
1188 error("Node 1200 does not exist");
1189
1190 return desc.getMiscData(chapter) + 199;
1191 }
1192
_journalSaavedroPageCount(uint16 chapter)1193 uint16 Puzzles::_journalSaavedroPageCount(uint16 chapter) {
1194 uint16 chapterStartNode = _journalSaavedroGetNode(chapter);
1195 if (chapter != 21)
1196 return _journalSaavedroGetNode(chapter + 1) - chapterStartNode;
1197 else
1198 return 1;
1199 }
1200
_journalSaavedroHasChapter(uint16 chapter)1201 bool Puzzles::_journalSaavedroHasChapter(uint16 chapter) {
1202 return _vm->_state->getVar(285 + chapter) != 0;
1203 }
1204
_journalSaavedroNextChapter(uint16 chapter,bool forward)1205 uint16 Puzzles::_journalSaavedroNextChapter(uint16 chapter, bool forward) {
1206 do {
1207 if (forward)
1208 chapter++;
1209 else
1210 chapter--;
1211 } while (!_journalSaavedroHasChapter(chapter));
1212
1213 return chapter;
1214 }
1215
journalAtrus(uint16 node,uint16 var)1216 void Puzzles::journalAtrus(uint16 node, uint16 var) {
1217 uint numPages = 0;
1218
1219 while (_vm->getFileDescription("", node++, 0, Archive::kFrame).isValid())
1220 numPages++;
1221
1222 _vm->_state->setVar(var, numPages - 1);
1223 }
1224
symbolCodesInit(uint16 var,uint16 posX,uint16 posY)1225 void Puzzles::symbolCodesInit(uint16 var, uint16 posX, uint16 posY) {
1226 struct Point {
1227 uint16 x;
1228 uint16 y;
1229 };
1230
1231 struct CodeData {
1232 uint16 node;
1233 uint16 movie;
1234 bool flag;
1235 Point coords[20];
1236 };
1237
1238 static const CodeData codes[] = {
1239 {
1240 144, 10144, 0,
1241 {
1242 { 296, 120 }, { 312, 128 }, { 296, 144 }, { 296, 128 }, { 312, 120 },
1243 { 328, 120 }, { 312, 144 }, { 312, 128 }, { 296, 136 }, { 312, 144 },
1244 { 296, 160 }, { 296, 144 }, { 312, 136 }, { 328, 144 }, { 312, 160 },
1245 { 312, 144 }, { 296, 112 }, { 328, 120 }, { 296, 160 }, { 288, 120 }
1246 }
1247 }, {
1248 244, 10244, 1,
1249 {
1250 { 288, 16 }, { 336, 32 }, { 294, 72 }, { 280, 24 }, { 336, 16 },
1251 { 376, 24 }, { 336, 72 }, { 328, 32 }, { 288, 64 }, { 336, 80 },
1252 { 288, 120 }, { 280, 72 }, { 336, 64 }, { 384, 72 }, { 336, 120 },
1253 { 328, 80 }, { 288, 0 }, { 384, 24 }, { 288, 120 }, { 264, 24 }
1254 }
1255 }, {
1256 148, 10148, 0,
1257 {
1258 { 280, 24 }, { 304, 32 }, { 288, 48 }, { 280, 24 }, { 304, 24 },
1259 { 320, 32 }, { 304, 48 }, { 296, 32 }, { 288, 40 }, { 304, 48 },
1260 { 280, 64 }, { 280, 48 }, { 304, 48 }, { 320, 48 }, { 304, 64 },
1261 { 296, 48 }, { 280, 16 }, { 320, 24 }, { 280, 64 }, { 272, 24 }
1262 }
1263 }, {
1264 248, 10248, 1,
1265 {
1266 { 280, 48 }, { 320, 56 }, { 287, 88 }, { 272, 56 }, { 320, 48 },
1267 { 360, 56 }, { 328, 96 }, { 312, 56 }, { 288, 88 }, { 320, 96 },
1268 { 280, 128 }, { 271, 96 }, { 328, 88 }, { 360, 96 }, { 320, 128 },
1269 { 312, 96 }, { 280, 32 }, { 360, 48 }, { 280, 128 }, { 264, 48 }
1270 }
1271 }, {
1272 348, 10348, 1,
1273 {
1274 { 336, 24 }, { 376, 32 }, { 336, 80 }, { 328, 32 }, { 376, 24 },
1275 { 424, 32 }, { 384, 80 }, { 368, 40 }, { 336, 72 }, { 376, 80 },
1276 { 336, 120 }, { 328, 80 }, { 384, 72 }, { 424, 80 }, { 376, 120 },
1277 { 368, 80 }, { 328, 8 }, { 424, 32 }, { 328, 128 }, { 312, 32 }
1278 }
1279 }, {
1280 448, 10448, 1,
1281 {
1282 { 224, 32 }, { 264, 40 }, { 224, 80 }, { 208, 40 }, { 264, 32 },
1283 { 304, 40 }, { 270, 88 }, { 256, 40 }, { 224, 72 }, { 264, 88 },
1284 { 224, 128 }, { 208, 88 }, { 272, 72 }, { 312, 88 }, { 264, 128 },
1285 { 256, 88 }, { 216, 16 }, { 312, 40 }, { 216, 128 }, { 200, 40 }
1286 }
1287 }
1288 };
1289
1290 uint16 node = _vm->_state->getLocationNode();
1291
1292 const CodeData *code = 0;
1293 for (uint i = 0; i < ARRAYSIZE(codes); i++)
1294 if (codes[i].node == node) {
1295 code = &codes[i];
1296 break;
1297 }
1298
1299 if (!code)
1300 error("Unable to find puzzle data for node %d", node);
1301
1302 int32 value = _vm->_state->getVar(var);
1303
1304 for (uint i = 0; i < 20; i++) {
1305 if (code->flag || value & (1 << i)) {
1306 _vm->_state->setMoviePreloadToMemory(true);
1307 _vm->_state->setMovieScriptDriven(true);
1308 _vm->_state->setMovieOverridePosition(true);
1309 _vm->_state->setMovieOverridePosU(posX + code->coords[i].x);
1310 _vm->_state->setMovieOverridePosV(posY + code->coords[i].y);
1311 _vm->_state->setMovieConditionBit(i + 1);
1312 _vm->loadMovie(code->movie + i * 1000, var, false, true);
1313 }
1314 }
1315 }
1316
symbolCodesClick(int16 var)1317 void Puzzles::symbolCodesClick(int16 var) {
1318 // Toggle clicked symbol element
1319 if (var > 0) {
1320 int32 value = _vm->_state->getVar(var);
1321 value ^= 1 << _vm->_state->getHotspotActiveRect();
1322 _vm->_state->setVar(var, value);
1323 }
1324
1325 // Check puzzle with one symbol solution
1326 static const SymbolCodeSolution smallSolution = { 330080, 53575, 241719, 116411 };
1327 if (_vm->_state->getSymbolCode1AllSolved()) {
1328 bool code2Solved = _symbolCodesCheckSolution(490, smallSolution);
1329 _vm->_state->setSymbolCode2Solved(code2Solved);
1330 }
1331
1332 // Check puzzle with 3 symbols solution
1333 static const SymbolCodeSolution solutions[] = {
1334 { 208172, 131196, 252945, 788771 },
1335 { 431060, 418863, 558738, 653337 },
1336 { 472588, 199440, 155951, 597954 }
1337 };
1338
1339
1340 _vm->_state->setSymbolCode1CurrentSolved(false);
1341
1342 for (uint i = 1; i <= ARRAYSIZE(solutions); i++) {
1343 int32 solutionsFound = _symbolCodesFound();
1344
1345 // Symbol already found, don't allow it another time
1346 if (solutionsFound & (1 << i))
1347 continue;
1348
1349 if (_symbolCodesCheckSolution(498, solutions[i - 1])) {
1350 _vm->_state->setSymbolCode1TopSolved(i);
1351 _vm->_state->setSymbolCode1CurrentSolved(true);
1352 }
1353
1354 if (_symbolCodesCheckSolution(503, solutions[i - 1])) {
1355 _vm->_state->setSymbolCode1LeftSolved(i);
1356 _vm->_state->setSymbolCode1CurrentSolved(true);
1357 }
1358
1359 if (_symbolCodesCheckSolution(508, solutions[i - 1])) {
1360 _vm->_state->setSymbolCode1RightSolved(i);
1361 _vm->_state->setSymbolCode1CurrentSolved(true);
1362 }
1363 }
1364
1365 bool allSolved = _symbolCodesFound() == 14;
1366 _vm->_state->setSymbolCode1AllSolved(allSolved);
1367 }
1368
_symbolCodesCheckSolution(uint16 var,const SymbolCodeSolution & solution)1369 bool Puzzles::_symbolCodesCheckSolution(uint16 var, const SymbolCodeSolution &solution) {
1370 bool solved = true;
1371
1372 for (uint i = 0; i < ARRAYSIZE(solution); i++) {
1373 int32 value = _vm->_state->getVar(var + i);
1374 if (value != solution[i]) {
1375 solved = false;
1376 break;
1377 }
1378 }
1379
1380 return solved;
1381 }
1382
_symbolCodesFound()1383 int32 Puzzles::_symbolCodesFound() {
1384 int32 top = _vm->_state->getSymbolCode1TopSolved();
1385 int32 left = _vm->_state->getSymbolCode1LeftSolved();
1386 int32 right = _vm->_state->getSymbolCode1RightSolved();
1387
1388 return (1 << top) | (1 << left) | (1 << right);
1389 }
1390
railRoadSwitchs()1391 void Puzzles::railRoadSwitchs() {
1392 uint16 index = _vm->_state->getHotspotActiveRect();
1393 uint16 startFrame = _vm->_state->getVar(449 + index);
1394 uint16 endFrame;
1395
1396 switch (startFrame) {
1397 case 1:
1398 endFrame = 4;
1399 break;
1400 case 4:
1401 endFrame = 7;
1402 break;
1403 case 7:
1404 endFrame = 10;
1405 break;
1406 case 10:
1407 endFrame = 12;
1408 break;
1409 default:
1410 error("Bad railroad switchs start value %d", startFrame);
1411 return;
1412 }
1413
1414 _drawForVarHelper(28 + index, startFrame, endFrame);
1415
1416 if (endFrame == 12)
1417 endFrame = 1;
1418
1419 _vm->_state->setVar(28 + index, endFrame);
1420 _vm->_state->setVar(449 + index, endFrame);
1421 }
1422
rollercoaster()1423 void Puzzles::rollercoaster() {
1424 static const uint8 map1[][8] = {
1425 { 3, 9, 9, 0, 7, 9, 9, 4 },
1426 { 2, 6, 0, 9, 9, 9, 1, 9 },
1427 { 6, 9, 4, 9, 2, 9, 0, 9 },
1428 { 4, 9, 6, 9, 0, 9, 2, 9 },
1429 { 9, 4, 7, 9, 1, 9, 9, 2 },
1430 { 2, 9, 0, 9, 7, 9, 9, 4 },
1431 { 6, 9, 9, 7, 9, 9, 0, 3 },
1432 { 4, 9, 7, 6, 0, 9, 3, 2 },
1433 { 9, 5, 9, 9, 6, 1, 4, 9 }
1434 };
1435
1436 static const uint8 map2[][8] = {
1437 { 0, 0, 26, 57, 40, 0, 0, 0 },
1438 { 100, 0, 36, 67, 50, 41, 12, 0 },
1439 { 0, 0, 0, 0, 60, 51, 22, 0 },
1440 { 14, 25, 56, 87, 70, 0, 103, 0 },
1441 { 24, 35, 66, 97, 80, 71, 42, 13 },
1442 { 34, 0, 101, 0, 90, 81, 52, 23 },
1443 { 44, 55, 86, 0, 0, 0, 0, 0 },
1444 { 54, 65, 96, 0, 102, 0, 72, 43 },
1445 { 64, 0, 0, 0, 0, 0, 82, 53 }
1446 };
1447
1448 int32 entryPoint = _vm->_state->getVar(26);
1449 int32 movie = 0;
1450 int32 exitPoint = 0;
1451
1452 if (_vm->_state->getVar(38 + entryPoint - 100)) {
1453 _vm->_state->setVar(42, 0);
1454 _vm->_state->setVar(26, 0);
1455 return;
1456 }
1457
1458 _vm->_state->setVar(38 + entryPoint - 100, 1);
1459
1460 switch (entryPoint) {
1461 case 100:
1462 _vm->_state->setVar(42, 0);
1463 _vm->_state->setVar(26, 1);
1464 return;
1465 case 101:
1466 movie = 12007;
1467 exitPoint = 93;
1468 break;
1469 case 102:
1470 movie = 14007;
1471 exitPoint = 75;
1472 break;
1473 case 103:
1474 movie = 16007;
1475 exitPoint = 17;
1476 break;
1477 default:
1478 _vm->_state->setVar(42, 0);
1479 _vm->_state->setVar(26, 0);
1480 return;
1481 }
1482
1483 int32 recursion = 20;
1484 while (1) {
1485 int32 switchIndex = exitPoint / 10 - 1;
1486 int32 switchFrame = _vm->_state->getVar(449 + switchIndex);
1487 int32 switchPosition = 2 * (switchFrame - 1) / 3;
1488
1489 int32 direction = map1[switchIndex][(exitPoint % 10 - switchPosition) & 7];
1490
1491 if (direction != 9)
1492 exitPoint = map2[switchIndex][(switchPosition + direction) & 7];
1493 else
1494 exitPoint = 0;
1495
1496 if (!recursion)
1497 break;
1498
1499 recursion--;
1500
1501 if (exitPoint <= 0 || exitPoint >= 100) {
1502 _vm->_state->setVar(42, exitPoint);
1503 _vm->_state->setVar(26, movie);
1504 return;
1505 }
1506 }
1507
1508 _vm->_state->setVar(42, 0);
1509 _vm->_state->setVar(26, movie);
1510 }
1511
mainMenu(uint16 action)1512 void Puzzles::mainMenu(uint16 action) {
1513 _vm->setMenuAction(action);
1514 }
1515
copySurfaceRect(Graphics::Surface * dest,const Common::Point & destPoint,const Graphics::Surface * src)1516 static void copySurfaceRect(Graphics::Surface *dest, const Common::Point &destPoint, const Graphics::Surface *src) {
1517 for (uint16 i = 0; i < src->h; i++)
1518 memcpy(dest->getBasePtr(destPoint.x, i + destPoint.y), src->getBasePtr(0, i), src->pitch);
1519 }
1520
projectorLoadBitmap(uint16 bitmap)1521 void Puzzles::projectorLoadBitmap(uint16 bitmap) {
1522 assert(_vm->_projectorBackground == 0 && "Previous background not yet used.");
1523
1524 // This surface is freed by the destructor of the movie that uses it
1525 _vm->_projectorBackground = new Graphics::Surface();
1526 _vm->_projectorBackground->create(1024, 1024, Texture::getRGBAPixelFormat());
1527
1528 ResourceDescription movieDesc = _vm->getFileDescription("", bitmap, 0, Archive::kStillMovie);
1529
1530 if (!movieDesc.isValid())
1531 error("Movie %d does not exist", bitmap);
1532
1533 // Rebuild the complete background image from the frames of the bink movie
1534 Common::SeekableReadStream *movieStream = movieDesc.getData();
1535 Video::BinkDecoder bink;
1536 bink.setDefaultHighColorFormat(Texture::getRGBAPixelFormat());
1537 bink.loadStream(movieStream);
1538 bink.start();
1539
1540 for (uint i = 0; i < 1024; i += 256) {
1541 for (uint j = 0; j < 1024; j += 256) {
1542 const Graphics::Surface *frame = bink.decodeNextFrame();
1543 copySurfaceRect(_vm->_projectorBackground, Common::Point(j, i), frame);
1544 }
1545 }
1546 }
1547
projectorAddSpotItem(uint16 bitmap,uint16 x,uint16 y)1548 void Puzzles::projectorAddSpotItem(uint16 bitmap, uint16 x, uint16 y) {
1549 assert(_vm->_projectorBackground != 0 && "Projector background already used.");
1550
1551 // Nothing to do if the spotitem is not enabled
1552 if (!_vm->_state->getVar(26))
1553 return;
1554
1555 ResourceDescription movieDesc = _vm->getFileDescription("", bitmap, 0, Archive::kStillMovie);
1556
1557 if (!movieDesc.isValid())
1558 error("Movie %d does not exist", bitmap);
1559
1560 // Rebuild the complete background image from the frames of the bink movie
1561 Common::SeekableReadStream *movieStream = movieDesc.getData();
1562 Video::BinkDecoder bink;
1563 bink.setDefaultHighColorFormat(Texture::getRGBAPixelFormat());
1564 bink.loadStream(movieStream);
1565 bink.start();
1566
1567 const Graphics::Surface *frame = bink.decodeNextFrame();
1568 copySurfaceRect(_vm->_projectorBackground, Common::Point(x, y), frame);
1569 }
1570
projectorUpdateCoordinates()1571 void Puzzles::projectorUpdateCoordinates() {
1572 int16 x = CLIP<int16>(_vm->_state->getProjectorX(), 840, 9400);
1573 int16 y = CLIP<int16>(_vm->_state->getProjectorY(), 840, 9400);
1574 int16 zoom = CLIP<int16>(_vm->_state->getProjectorZoom(), 1280, 5120);
1575 int16 blur = CLIP<int16>(_vm->_state->getProjectorBlur(), 400, 2470);
1576
1577 int16 halfZoom = zoom / 2;
1578 if (x - halfZoom < 0)
1579 x = halfZoom;
1580 if (x + halfZoom > 10240)
1581 x = 10240 - halfZoom;
1582 if (y - halfZoom < 0)
1583 y = halfZoom;
1584 if (y + halfZoom > 10240)
1585 y = 10240 - halfZoom;
1586
1587 int16 angleXOffset = _vm->_state->getProjectorAngleXOffset();
1588 int16 angleYOffset = _vm->_state->getProjectorAngleYOffset();
1589 int16 angleZoomOffset = _vm->_state->getProjectorAngleZoomOffset();
1590 int16 angleBlurOffset = _vm->_state->getProjectorAngleBlurOffset();
1591
1592 int16 angleX = (angleXOffset + 200 * (5 * x - 4200) / 8560) % 1000;
1593 int16 angleY = (angleYOffset + 200 * (5 * y - 4200) / 8560) % 1000;
1594 int16 angleZoom = (angleZoomOffset + 200 * (5 * zoom - 6400) / 3840) % 1000;
1595 int16 angleBlur = (angleBlurOffset + 200 * (5 * blur - 2000) / 2070) % 1000;
1596
1597 _vm->_state->setProjectorAngleX(angleX);
1598 _vm->_state->setProjectorAngleY(angleY);
1599 _vm->_state->setProjectorAngleZoom(angleZoom);
1600 _vm->_state->setProjectorAngleBlur(angleBlur);
1601
1602 _vm->_state->setProjectorX(x);
1603 _vm->_state->setProjectorY(y);
1604 _vm->_state->setProjectorZoom(zoom);
1605 _vm->_state->setProjectorBlur(blur);
1606 }
1607
settingsSave()1608 void Puzzles::settingsSave() {
1609 ConfMan.flushToDisk();
1610 }
1611
updateSoundScriptTimer()1612 void Puzzles::updateSoundScriptTimer() {
1613 int frequency = 15 * ConfMan.getInt("music_frequency") / 100;
1614 if (_vm->_state->getSoundScriptsPaused()) {
1615 _vm->_state->setSoundScriptsTimer(60 * (20 - frequency));
1616 } else {
1617 _vm->_state->setSoundScriptsTimer(60 * (frequency + 5));
1618 }
1619 }
1620
checkCanSave()1621 void Puzzles::checkCanSave() {
1622 // There is no reason to forbid saving games with ScummVM,
1623 // since there is no notion of memory card, free blocks and such.
1624 _vm->_state->setStateCanSave(true);
1625 }
1626
1627 } // End of namespace Myst3
1628