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