1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
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 "mohawk/cursors.h"
24 #include "mohawk/myst.h"
25 #include "mohawk/myst_areas.h"
26 #include "mohawk/myst_card.h"
27 #include "mohawk/myst_graphics.h"
28 #include "mohawk/myst_state.h"
29 #include "mohawk/myst_sound.h"
30 #include "mohawk/video.h"
31 #include "mohawk/myst_stacks/mechanical.h"
32 
33 #include "common/events.h"
34 #include "common/system.h"
35 
36 namespace Mohawk {
37 namespace MystStacks {
38 
Mechanical(MohawkEngine_Myst * vm)39 Mechanical::Mechanical(MohawkEngine_Myst *vm) :
40 		MystScriptParser(vm, kMechanicalStack),
41 		_state(vm->_gameState->_mechanical) {
42 	setupOpcodes();
43 
44 	_elevatorGoingMiddle = false;
45 	_elevatorPosition = 0;
46 	_elevatorGoingDown = 0;
47 	_elevatorRotationSpeed = 0;
48 	_elevatorRotationGearPosition = 0;
49 	_elevatorRotationSoundId = 0;
50 	_elevatorRotationLeverMoving = false;
51 	_elevatorTooLate = false;
52 	_elevatorInCabin = false;
53 	_elevatorTopCounter = 0;
54 	_elevatorNextTime = 0;
55 
56 	_crystalLit = 0;
57 
58 	_mystStaircaseState = false;
59 	_fortressDirection = kSouth;
60 	_gearsWereRunning = false;
61 
62 	_fortressRotationShortMovieWorkaround = false;
63 	_fortressRotationShortMovieCount = 0;
64 	_fortressRotationShortMovieLast = 0;
65 
66 	_fortressRotationRunning = false;
67 	_fortressRotationSpeed = 0;
68 	_fortressRotationBrake = 0;
69 	_fortressRotationGears = nullptr;
70 
71 	_fortressSimulationRunning = false;
72 	_fortressSimulationInit = false;
73 	_fortressSimulationSpeed = 0;
74 	_fortressSimulationBrake = 0;
75 	_fortressSimulationStartSound1 = 0;
76 	_fortressSimulationStartSound2 = 0;
77 	_fortressSimulationHolo = nullptr;
78 	_fortressSimulationStartup = nullptr;
79 	_fortressSimulationHoloRate = 0;
80 
81 	_birdSinging = false;
82 	_birdCrankStartTime = 0;
83 	_birdSingEndTime = 0;
84 	_bird = nullptr;
85 
86 	_snakeBox = nullptr;
87 }
88 
~Mechanical()89 Mechanical::~Mechanical() {
90 }
91 
setupOpcodes()92 void Mechanical::setupOpcodes() {
93 	// "Stack-Specific" Opcodes
94 	REGISTER_OPCODE(100, Mechanical, o_throneEnablePassage);
95 	REGISTER_OPCODE(101, Mechanical, o_birdCrankStart);
96 	REGISTER_OPCODE(102, Mechanical, NOP);
97 	REGISTER_OPCODE(103, Mechanical, o_birdCrankStop);
98 	REGISTER_OPCODE(104, Mechanical, o_snakeBoxTrigger);
99 	REGISTER_OPCODE(105, Mechanical, o_fortressStaircaseMovie);
100 	REGISTER_OPCODE(106, Mechanical, o_elevatorRotationStart);
101 	REGISTER_OPCODE(107, Mechanical, o_elevatorRotationMove);
102 	REGISTER_OPCODE(108, Mechanical, o_elevatorRotationStop);
103 	REGISTER_OPCODE(109, Mechanical, o_fortressRotationSpeedStart);
104 	REGISTER_OPCODE(110, Mechanical, o_fortressRotationSpeedMove);
105 	REGISTER_OPCODE(111, Mechanical, o_fortressRotationSpeedStop);
106 	REGISTER_OPCODE(112, Mechanical, o_fortressRotationBrakeStart);
107 	REGISTER_OPCODE(113, Mechanical, o_fortressRotationBrakeMove);
108 	REGISTER_OPCODE(114, Mechanical, o_fortressRotationBrakeStop);
109 	REGISTER_OPCODE(115, Mechanical, o_fortressSimulationSpeedStart);
110 	REGISTER_OPCODE(116, Mechanical, o_fortressSimulationSpeedMove);
111 	REGISTER_OPCODE(117, Mechanical, o_fortressSimulationSpeedStop);
112 	REGISTER_OPCODE(118, Mechanical, o_fortressSimulationBrakeStart);
113 	REGISTER_OPCODE(119, Mechanical, o_fortressSimulationBrakeMove);
114 	REGISTER_OPCODE(120, Mechanical, o_fortressSimulationBrakeStop);
115 	REGISTER_OPCODE(121, Mechanical, o_elevatorWindowMovie);
116 	REGISTER_OPCODE(122, Mechanical, o_elevatorGoMiddle);
117 	REGISTER_OPCODE(123, Mechanical, o_elevatorTopMovie);
118 	REGISTER_OPCODE(124, Mechanical, o_fortressRotationSetPosition);
119 	REGISTER_OPCODE(125, Mechanical, o_mystStaircaseMovie);
120 	REGISTER_OPCODE(126, Mechanical, o_elevatorWaitTimeout);
121 	REGISTER_OPCODE(127, Mechanical, o_crystalEnterYellow);
122 	REGISTER_OPCODE(128, Mechanical, o_crystalLeaveYellow);
123 	REGISTER_OPCODE(129, Mechanical, o_crystalEnterGreen);
124 	REGISTER_OPCODE(130, Mechanical, o_crystalLeaveGreen);
125 	REGISTER_OPCODE(131, Mechanical, o_crystalEnterRed);
126 	REGISTER_OPCODE(132, Mechanical, o_crystalLeaveRed);
127 
128 	// "Init" Opcodes
129 	REGISTER_OPCODE(200, Mechanical, o_throne_init);
130 	REGISTER_OPCODE(201, Mechanical, o_fortressStaircase_init);
131 	REGISTER_OPCODE(202, Mechanical, o_bird_init);
132 	REGISTER_OPCODE(203, Mechanical, o_snakeBox_init);
133 	REGISTER_OPCODE(204, Mechanical, o_elevatorRotation_init);
134 	REGISTER_OPCODE(205, Mechanical, o_fortressRotation_init);
135 	REGISTER_OPCODE(206, Mechanical, o_fortressSimulation_init);
136 	REGISTER_OPCODE(209, Mechanical, o_fortressSimulationStartup_init);
137 
138 	// "Exit" Opcodes
139 	REGISTER_OPCODE(300, Mechanical, NOP);
140 }
141 
disablePersistentScripts()142 void Mechanical::disablePersistentScripts() {
143 	_fortressSimulationRunning = false;
144 	_elevatorRotationLeverMoving = false;
145 	_birdSinging = false;
146 	_fortressRotationRunning = false;
147 }
148 
runPersistentScripts()149 void Mechanical::runPersistentScripts() {
150 	if (_birdSinging)
151 		birdSing_run();
152 
153 	if (_elevatorRotationLeverMoving)
154 		elevatorRotation_run();
155 
156 	if (_elevatorGoingMiddle)
157 		elevatorGoMiddle_run();
158 
159 	if (_fortressRotationRunning)
160 		fortressRotation_run();
161 
162 	if (_fortressSimulationRunning)
163 		fortressSimulation_run();
164 }
165 
getVar(uint16 var)166 uint16 Mechanical::getVar(uint16 var) {
167 	switch(var) {
168 	case 0: // Achenar's Secret Panel State
169 		return _state.achenarPanelState;
170 	case 1: // Sirrus's Secret Panel State
171 		return _state.sirrusPanelState;
172 	case 2: // Achenar's Secret Room Crate Lid Open and Blue Page Present
173 		if (_state.achenarCrateOpened) {
174 			if (_globals.bluePagesInBook & 4 || _globals.heldPage == kBlueMechanicalPage)
175 				return 2;
176 			else
177 				return 3;
178 		} else {
179 			return _globals.bluePagesInBook & 4 || _globals.heldPage == kBlueMechanicalPage;
180 		}
181 	case 3: // Achenar's Secret Room Crate State
182 		return _state.achenarCrateOpened;
183 	case 4: // Myst Book Room Staircase State
184 		return _mystStaircaseState;
185 	case 5: // Fortress Position
186 		return _fortressDirection;
187 	case 6: // Fortress Position - Big Cog Visible Through Doorway
188 		return _fortressDirection == kSouth;
189 	case 7: // Fortress Elevator Open
190 		if (_state.elevatorRotation == 4)
191 			return 1; // Open
192 		else
193 			return 0; // Closed
194 	case 10: // Fortress Staircase State
195 		return _state.staircaseState;
196 	case 11: // Fortress Elevator Rotation Position
197 		return _state.elevatorRotation;
198 	case 12: // Fortress Elevator Rotation Cog Position
199 		return 5 - (uint16)(_elevatorRotationGearPosition + 0.5) % 6;
200 	case 13: // Elevator position
201 		return _elevatorPosition;
202 	case 14: // Elevator going down when at top
203 		if (_elevatorGoingDown && _elevatorTooLate)
204 			return 2;
205 		else
206 			return _elevatorGoingDown;
207 	case 15: // Code Lock Execute Button Script
208 		if (_mystStaircaseState)
209 			return 0;
210 		else if (_state.codeShape[0] == 2 && _state.codeShape[1] == 8
211 				&& _state.codeShape[2] == 5 && _state.codeShape[3] == 1)
212 			return 1;
213 		else
214 			return 2;
215 	case 16: // Code Lock Shape #1 - Left
216 	case 17: // Code Lock Shape #2
217 	case 18: // Code Lock Shape #3
218 	case 19: // Code Lock Shape #4 - Right
219 		return _state.codeShape[var - 16];
220 	case 20: // Crystal Lit Flag - Yellow
221 		return _crystalLit == 3;
222 	case 21: // Crystal Lit Flag - Green
223 		return _crystalLit == 1;
224 	case 22: // Crystal Lit Flag - Red
225 		return _crystalLit == 2;
226 	case 102: // Red page
227 		return !(_globals.redPagesInBook & 4) && (_globals.heldPage != kRedMechanicalPage);
228 	case 103: // Blue page
229 		return !(_globals.bluePagesInBook & 4) && (_globals.heldPage != kBlueMechanicalPage);
230 	default:
231 		return MystScriptParser::getVar(var);
232 	}
233 }
234 
toggleVar(uint16 var)235 void Mechanical::toggleVar(uint16 var) {
236 	switch(var) {
237 	case 0: // Achenar's Secret Panel State
238 		_state.achenarPanelState ^= 1;
239 		break;
240 	case 1: // Sirrus's Secret Panel State
241 		_state.sirrusPanelState ^= 1;
242 		break;
243 	case 3: // Achenar's Secret Room Crate State
244 		_state.achenarCrateOpened ^= 1;
245 		break;
246 	case 4: // Myst Book Room Staircase State
247 		_mystStaircaseState ^= 1;
248 		break;
249 	case 10: // Fortress Staircase State
250 		_state.staircaseState ^= 1;
251 		break;
252 	case 16: // Code Lock Shape #1 - Left
253 	case 17: // Code Lock Shape #2
254 	case 18: // Code Lock Shape #3
255 	case 19: // Code Lock Shape #4 - Right
256 		_state.codeShape[var - 16] = (_state.codeShape[var - 16] + 1) % 10;
257 		break;
258 	case 23: // Elevator player is in cabin
259 		_elevatorInCabin = false;
260 		break;
261 	case 102: // Red page
262 		if (!(_globals.redPagesInBook & 4)) {
263 			if (_globals.heldPage == kRedMechanicalPage)
264 				_globals.heldPage = kNoPage;
265 			else
266 				_globals.heldPage = kRedMechanicalPage;
267 		}
268 		break;
269 	case 103: // Blue page
270 		if (!(_globals.bluePagesInBook & 4)) {
271 			if (_globals.heldPage == kBlueMechanicalPage)
272 				_globals.heldPage = kNoPage;
273 			else
274 				_globals.heldPage = kBlueMechanicalPage;
275 		}
276 		break;
277 	default:
278 		MystScriptParser::toggleVar(var);
279 		break;
280 	}
281 }
282 
setVarValue(uint16 var,uint16 value)283 bool Mechanical::setVarValue(uint16 var, uint16 value) {
284 	bool refresh = false;
285 
286 	switch (var) {
287 	case 13:
288 		_elevatorPosition = value;
289 		break;
290 	case 14: // Elevator going down when at top
291 		_elevatorGoingDown = value;
292 		break;
293 	default:
294 		refresh = MystScriptParser::setVarValue(var, value);
295 		break;
296 	}
297 
298 	return refresh;
299 }
300 
o_throneEnablePassage(uint16 var,const ArgumentsArray & args)301 void Mechanical::o_throneEnablePassage(uint16 var, const ArgumentsArray &args) {
302 	_vm->getCard()->getResource<MystArea>(args[0])->setEnabled(getVar(var));
303 }
304 
o_birdCrankStart(uint16 var,const ArgumentsArray & args)305 void Mechanical::o_birdCrankStart(uint16 var, const ArgumentsArray &args) {
306 	MystAreaDrag *crank = getInvokingResource<MystAreaDrag>();
307 
308 	uint16 crankSoundId = crank->getList2(0);
309 	_vm->_sound->playEffect(crankSoundId, true);
310 
311 	_birdSingEndTime = 0;
312 	_birdCrankStartTime = _vm->getTotalPlayTime();
313 
314 	MystAreaVideo *crankMovie = static_cast<MystAreaVideo *>(crank->getSubResource(0));
315 	crankMovie->playMovie();
316 }
317 
o_birdCrankStop(uint16 var,const ArgumentsArray & args)318 void Mechanical::o_birdCrankStop(uint16 var, const ArgumentsArray &args) {
319 	MystAreaDrag *crank = getInvokingResource<MystAreaDrag>();
320 
321 	MystAreaVideo *crankMovie = static_cast<MystAreaVideo *>(crank->getSubResource(0));
322 	crankMovie->pauseMovie(true);
323 
324 	uint16 crankSoundId = crank->getList2(1);
325 	_vm->_sound->playEffect(crankSoundId);
326 
327 	_birdSingEndTime = 2 * _vm->getTotalPlayTime() - _birdCrankStartTime;
328 	_birdSinging = true;
329 
330 	_bird->playMovie();
331 }
332 
o_snakeBoxTrigger(uint16 var,const ArgumentsArray & args)333 void Mechanical::o_snakeBoxTrigger(uint16 var, const ArgumentsArray &args) {
334 	// Used on Mechanical Card 6043 (Weapons Rack with Snake Box)
335 	_snakeBox->playMovie();
336 }
337 
o_fortressStaircaseMovie(uint16 var,const ArgumentsArray & args)338 void Mechanical::o_fortressStaircaseMovie(uint16 var, const ArgumentsArray &args) {
339 	VideoEntryPtr staircase = _vm->playMovie("hhstairs", kMechanicalStack);
340 	staircase->moveTo(174, 222);
341 
342 	if (_state.staircaseState) {
343 		staircase->setBounds(Audio::Timestamp(0, 840, 600), Audio::Timestamp(0, 1680, 600));
344 	} else {
345 		staircase->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 840, 600));
346 	}
347 
348 	_vm->waitUntilMovieEnds(staircase);
349 }
350 
o_elevatorRotationStart(uint16 var,const ArgumentsArray & args)351 void Mechanical::o_elevatorRotationStart(uint16 var, const ArgumentsArray &args) {
352 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
353 	lever->drawFrame(0);
354 
355 	_elevatorRotationLeverMoving = true;
356 	_elevatorRotationSpeed = 0;
357 
358 	_vm->_sound->stopBackground();
359 
360 	_vm->_cursor->setCursor(700);
361 }
362 
o_elevatorRotationMove(uint16 var,const ArgumentsArray & args)363 void Mechanical::o_elevatorRotationMove(uint16 var, const ArgumentsArray &args) {
364 	const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos();
365 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
366 
367 	// Make the handle follow the mouse
368 	int16 maxStep = lever->getNumFrames() - 1;
369 	Common::Rect rect = lever->getRect();
370 	int16 step = ((rect.bottom - mouse.y) * lever->getNumFrames()) / rect.height();
371 	step = CLIP<int16>(step, 0, maxStep);
372 
373 	_elevatorRotationSpeed = step * 0.1f;
374 
375 	// Draw current frame
376 	lever->drawFrame(step);
377 }
378 
o_elevatorRotationStop(uint16 var,const ArgumentsArray & args)379 void Mechanical::o_elevatorRotationStop(uint16 var, const ArgumentsArray &args) {
380 	const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos();
381 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
382 
383 	// Get current lever frame
384 	int16 maxStep = lever->getNumFrames() - 1;
385 	Common::Rect rect = lever->getRect();
386 	int16 step = ((rect.bottom - mouse.y) * lever->getNumFrames()) / rect.height();
387 	step = CLIP<int16>(step, 0, maxStep);
388 
389 	// Release lever
390 	for (int i = step; i >= 0; i--) {
391 		lever->drawFrame(i);
392 		_vm->doFrame();
393 	}
394 
395 	// Stop persistent script
396 	_elevatorRotationLeverMoving = false;
397 
398 	float speed = _elevatorRotationSpeed * 10;
399 
400 	if (speed > 0) {
401 
402 		// Decrease speed
403 		while (speed > 2) {
404 			speed -= 0.5f;
405 
406 			_elevatorRotationGearPosition += speed * 0.1f;
407 
408 			if (_elevatorRotationGearPosition > 12)
409 				break;
410 
411 			_vm->getCard()->redrawArea(12);
412 			_vm->wait(100);
413 		}
414 
415 		// Increment position
416 		_state.elevatorRotation = (_state.elevatorRotation + 1) % 10;
417 
418 		_vm->_sound->playEffect(_elevatorRotationSoundId);
419 		_vm->getCard()->redrawArea(11);
420 	}
421 
422 	_vm->refreshCursor();
423 }
424 
o_fortressRotationSpeedStart(uint16 var,const ArgumentsArray & args)425 void Mechanical::o_fortressRotationSpeedStart(uint16 var, const ArgumentsArray &args) {
426 	_vm->_cursor->setCursor(700);
427 
428 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
429 	lever->drawFrame(0);
430 }
431 
o_fortressRotationSpeedMove(uint16 var,const ArgumentsArray & args)432 void Mechanical::o_fortressRotationSpeedMove(uint16 var, const ArgumentsArray &args) {
433 	const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos();
434 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
435 
436 	// Make the handle follow the mouse
437 	int16 maxStep = lever->getNumFrames() - 1;
438 	Common::Rect rect = lever->getRect();
439 	int16 step = ((rect.top + 65 - mouse.y) * lever->getNumFrames()) / 65;
440 	step = CLIP<int16>(step, 0, maxStep);
441 
442 	_fortressRotationSpeed = step;
443 
444 	// Draw current frame
445 	lever->drawFrame(step);
446 }
447 
o_fortressRotationSpeedStop(uint16 var,const ArgumentsArray & args)448 void Mechanical::o_fortressRotationSpeedStop(uint16 var, const ArgumentsArray &args) {
449 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
450 
451 	// Release lever
452 	for (int i = _fortressRotationSpeed; i >= 0; i--) {
453 		lever->drawFrame(i);
454 		_vm->doFrame();
455 	}
456 
457 	_fortressRotationSpeed = 0;
458 
459 	_vm->refreshCursor();
460 }
461 
o_fortressRotationBrakeStart(uint16 var,const ArgumentsArray & args)462 void Mechanical::o_fortressRotationBrakeStart(uint16 var, const ArgumentsArray &args) {
463 	_vm->_cursor->setCursor(700);
464 
465 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
466 	lever->drawFrame(_fortressRotationBrake);
467 }
468 
o_fortressRotationBrakeMove(uint16 var,const ArgumentsArray & args)469 void Mechanical::o_fortressRotationBrakeMove(uint16 var, const ArgumentsArray &args) {
470 	const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos();
471 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
472 
473 	// Make the handle follow the mouse
474 	int16 maxStep = lever->getNumFrames() - 1;
475 	Common::Rect rect = lever->getRect();
476 	int16 step = ((rect.top + 65 - mouse.y) * lever->getNumFrames()) / 65;
477 	step = CLIP<int16>(step, 0, maxStep);
478 
479 	_fortressRotationBrake = step;
480 
481 	// Draw current frame
482 	lever->drawFrame(step);
483 }
484 
o_fortressRotationBrakeStop(uint16 var,const ArgumentsArray & args)485 void Mechanical::o_fortressRotationBrakeStop(uint16 var, const ArgumentsArray &args) {
486 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
487 	lever->drawFrame(_fortressRotationBrake);
488 
489 	_vm->refreshCursor();
490 }
491 
o_fortressSimulationSpeedStart(uint16 var,const ArgumentsArray & args)492 void Mechanical::o_fortressSimulationSpeedStart(uint16 var, const ArgumentsArray &args) {
493 	_vm->_cursor->setCursor(700);
494 
495 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
496 	lever->drawFrame(0);
497 }
498 
o_fortressSimulationSpeedMove(uint16 var,const ArgumentsArray & args)499 void Mechanical::o_fortressSimulationSpeedMove(uint16 var, const ArgumentsArray &args) {
500 	const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos();
501 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
502 
503 	// Make the handle follow the mouse
504 	int16 maxStep = lever->getNumFrames() - 1;
505 	Common::Rect rect = lever->getRect();
506 	int16 step = ((rect.bottom - mouse.y) * lever->getNumFrames()) / rect.height();
507 	step = CLIP<int16>(step, 0, maxStep);
508 
509 	_fortressSimulationSpeed = step;
510 
511 	// Draw current frame
512 	lever->drawFrame(step);
513 }
514 
o_fortressSimulationSpeedStop(uint16 var,const ArgumentsArray & args)515 void Mechanical::o_fortressSimulationSpeedStop(uint16 var, const ArgumentsArray &args) {
516 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
517 
518 	// Release lever
519 	for (int i = _fortressSimulationSpeed; i >= 0; i--) {
520 		lever->drawFrame(i);
521 		_vm->doFrame();
522 	}
523 
524 	_fortressSimulationSpeed = 0;
525 
526 	_vm->refreshCursor();
527 }
528 
o_fortressSimulationBrakeStart(uint16 var,const ArgumentsArray & args)529 void Mechanical::o_fortressSimulationBrakeStart(uint16 var, const ArgumentsArray &args) {
530 	_vm->_cursor->setCursor(700);
531 
532 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
533 	lever->drawFrame(_fortressSimulationBrake);
534 }
535 
o_fortressSimulationBrakeMove(uint16 var,const ArgumentsArray & args)536 void Mechanical::o_fortressSimulationBrakeMove(uint16 var, const ArgumentsArray &args) {
537 	const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos();
538 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
539 
540 	// Make the handle follow the mouse
541 	int16 maxStep = lever->getNumFrames() - 1;
542 	Common::Rect rect = lever->getRect();
543 	int16 step = ((rect.bottom - mouse.y) * lever->getNumFrames()) / rect.height();
544 	step = CLIP<int16>(step, 0, maxStep);
545 
546 	_fortressSimulationBrake = step;
547 
548 	// Draw current frame
549 	lever->drawFrame(step);
550 }
551 
o_fortressSimulationBrakeStop(uint16 var,const ArgumentsArray & args)552 void Mechanical::o_fortressSimulationBrakeStop(uint16 var, const ArgumentsArray &args) {
553 	MystVideoInfo *lever = getInvokingResource<MystVideoInfo>();
554 	lever->drawFrame(_fortressSimulationBrake);
555 
556 	_vm->refreshCursor();
557 }
558 
o_elevatorWindowMovie(uint16 var,const ArgumentsArray & args)559 void Mechanical::o_elevatorWindowMovie(uint16 var, const ArgumentsArray &args) {
560 	uint16 startTime = args[0];
561 	uint16 endTime = args[1];
562 
563 	VideoEntryPtr window = _vm->playMovie("ewindow", kMechanicalStack);
564 	window->moveTo(253, 0);
565 	window->setBounds(Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600));
566 
567 	_vm->waitUntilMovieEnds(window);
568 }
569 
o_elevatorGoMiddle(uint16 var,const ArgumentsArray & args)570 void Mechanical::o_elevatorGoMiddle(uint16 var, const ArgumentsArray &args) {
571 	_elevatorTooLate = false;
572 	_elevatorTopCounter = 5;
573 	_elevatorGoingMiddle = true;
574 	_elevatorInCabin = true;
575 	_elevatorNextTime = _vm->getTotalPlayTime() + 1000;
576 }
577 
elevatorGoMiddle_run()578 void Mechanical::elevatorGoMiddle_run() {
579 	uint32 time = _vm->getTotalPlayTime();
580 	if (_elevatorNextTime < time) {
581 		_elevatorNextTime = time + 1000;
582 		_elevatorTopCounter--;
583 
584 		if (_elevatorTopCounter > 0) {
585 			// Draw button pressed
586 			if (_elevatorInCabin) {
587 				_vm->_gfx->copyImageSectionToScreen(6332, Common::Rect(0, 35, 51, 63), Common::Rect(10, 137, 61, 165));
588 			}
589 
590 			// Blip
591 			_vm->playSoundBlocking(14120);
592 
593 			// Restore button
594 			if (_elevatorInCabin) {
595 				_vm->_gfx->copyBackBufferToScreen(Common::Rect(10, 137, 61, 165));
596 			 }
597 		} else {
598 			_elevatorTooLate = true;
599 			_elevatorGoingMiddle = false;
600 
601 			if (_elevatorInCabin) {
602 
603 				// Elevator going to middle animation
604 				_vm->_cursor->hideCursor();
605 				_vm->playSoundBlocking(11120);
606 				_vm->_gfx->copyImageToBackBuffer(6118, Common::Rect(544, 333));
607 				_vm->_sound->playEffect(12120);
608 				_vm->_gfx->runTransition(kTransitionSlideToLeft, Common::Rect(177, 0, 370, 333), 25, 0);
609 				_vm->playSoundBlocking(13120);
610 				_vm->_sound->playEffect(8120);
611 				_vm->_gfx->copyImageToBackBuffer(6327, Common::Rect(544, 333));
612 				_vm->wait(500);
613 				_vm->_sound->playEffect(9120);
614 				static uint16 moviePos[2] = { 3540, 5380 };
615 				o_elevatorWindowMovie(0, ArgumentsArray(moviePos, ARRAYSIZE(moviePos)));
616 				_vm->_gfx->copyBackBufferToScreen(Common::Rect(544, 333));
617 				_vm->_sound->playEffect(10120);
618 				_vm->_cursor->showCursor();
619 
620 				_elevatorPosition = 1;
621 
622 				_vm->changeToCard(6327, kTransitionRightToLeft);
623 			}
624 		}
625 	}
626 }
627 
o_elevatorTopMovie(uint16 var,const ArgumentsArray & args)628 void Mechanical::o_elevatorTopMovie(uint16 var, const ArgumentsArray &args) {
629 	uint16 startTime = args[0];
630 	uint16 endTime = args[1];
631 
632 	VideoEntryPtr window = _vm->playMovie("hcelev", kMechanicalStack);
633 	window->moveTo(206, 38);
634 	window->setBounds(Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600));
635 
636 	_vm->waitUntilMovieEnds(window);
637 }
638 
o_fortressRotationSetPosition(uint16 var,const ArgumentsArray & args)639 void Mechanical::o_fortressRotationSetPosition(uint16 var, const ArgumentsArray &args) {
640 	// The fortress direction is already set in fortressRotation_run() so we don't do it here
641 	// Stop the gears video so that it does not play while the elevator is going up
642 	_fortressRotationGears->getVideo()->stop();
643 }
644 
o_mystStaircaseMovie(uint16 var,const ArgumentsArray & args)645 void Mechanical::o_mystStaircaseMovie(uint16 var, const ArgumentsArray &args) {
646 	_vm->playMovieBlocking("sstairs", kMechanicalStack, 199, 108);
647 }
648 
o_elevatorWaitTimeout(uint16 var,const ArgumentsArray & args)649 void Mechanical::o_elevatorWaitTimeout(uint16 var, const ArgumentsArray &args) {
650 	// Wait while the elevator times out
651 	while (_elevatorGoingMiddle) {
652 		runPersistentScripts();
653 		_vm->doFrame();
654 	}
655 }
656 
o_crystalEnterYellow(uint16 var,const ArgumentsArray & args)657 void Mechanical::o_crystalEnterYellow(uint16 var, const ArgumentsArray &args) {
658 	_crystalLit = 3;
659 	_vm->getCard()->redrawArea(20);
660 }
661 
o_crystalEnterGreen(uint16 var,const ArgumentsArray & args)662 void Mechanical::o_crystalEnterGreen(uint16 var, const ArgumentsArray &args) {
663 	_crystalLit = 1;
664 	_vm->getCard()->redrawArea(21);
665 }
666 
o_crystalEnterRed(uint16 var,const ArgumentsArray & args)667 void Mechanical::o_crystalEnterRed(uint16 var, const ArgumentsArray &args) {
668 	_crystalLit = 2;
669 	_vm->getCard()->redrawArea(22);
670 }
671 
o_crystalLeaveYellow(uint16 var,const ArgumentsArray & args)672 void Mechanical::o_crystalLeaveYellow(uint16 var, const ArgumentsArray &args) {
673 	_crystalLit = 0;
674 	_vm->getCard()->redrawArea(20);
675 }
676 
o_crystalLeaveGreen(uint16 var,const ArgumentsArray & args)677 void Mechanical::o_crystalLeaveGreen(uint16 var, const ArgumentsArray &args) {
678 	_crystalLit = 0;
679 	_vm->getCard()->redrawArea(21);
680 }
681 
o_crystalLeaveRed(uint16 var,const ArgumentsArray & args)682 void Mechanical::o_crystalLeaveRed(uint16 var, const ArgumentsArray &args) {
683 	_crystalLit = 0;
684 	_vm->getCard()->redrawArea(22);
685 }
686 
o_throne_init(uint16 var,const ArgumentsArray & args)687 void Mechanical::o_throne_init(uint16 var, const ArgumentsArray &args) {
688 	// Used on Card 6238 (Sirrus' Throne) and Card 6027 (Achenar's Throne)
689 	getInvokingResource<MystArea>()->setEnabled(getVar(var));
690 }
691 
o_fortressStaircase_init(uint16 var,const ArgumentsArray & args)692 void Mechanical::o_fortressStaircase_init(uint16 var, const ArgumentsArray &args) {
693 	_vm->getCard()->getResource<MystArea>(args[0])->setEnabled(!_state.staircaseState);
694 	_vm->getCard()->getResource<MystArea>(args[1])->setEnabled(!_state.staircaseState);
695 	_vm->getCard()->getResource<MystArea>(args[2])->setEnabled(_state.staircaseState);
696 }
697 
birdSing_run()698 void Mechanical::birdSing_run() {
699 	// Used for Card 6220 (Sirrus' Mechanical Bird)
700 	uint32 time = _vm->getTotalPlayTime();
701 	if (_birdSingEndTime < time) {
702 		_bird->pauseMovie(true);
703 		_vm->_sound->stopEffect();
704 		_birdSinging = false;
705 	}
706 }
707 
o_bird_init(uint16 var,const ArgumentsArray & args)708 void Mechanical::o_bird_init(uint16 var, const ArgumentsArray &args) {
709 	_birdSinging = false;
710 	_birdSingEndTime = 0;
711 	_bird = getInvokingResource<MystAreaVideo>();
712 }
713 
o_snakeBox_init(uint16 var,const ArgumentsArray & args)714 void Mechanical::o_snakeBox_init(uint16 var, const ArgumentsArray &args) {
715 	_snakeBox = getInvokingResource<MystAreaVideo>();
716 }
717 
elevatorRotation_run()718 void Mechanical::elevatorRotation_run() {
719 	_vm->getCard()->redrawArea(12);
720 
721 	_elevatorRotationGearPosition += _elevatorRotationSpeed;
722 
723 	if (_elevatorRotationGearPosition > 12) {
724 		uint16 position = (uint16)_elevatorRotationGearPosition;
725 		_elevatorRotationGearPosition = _elevatorRotationGearPosition - position + position % 6;
726 
727 		_state.elevatorRotation = (_state.elevatorRotation + 1) % 10;
728 
729 		_vm->_sound->playEffect(_elevatorRotationSoundId);
730 		_vm->getCard()->redrawArea(11);
731 		_vm->wait(100);
732 	}
733 }
734 
o_elevatorRotation_init(uint16 var,const ArgumentsArray & args)735 void Mechanical::o_elevatorRotation_init(uint16 var, const ArgumentsArray &args) {
736 	_elevatorRotationSoundId = args[0];
737 	_elevatorRotationGearPosition = 0;
738 	_elevatorRotationLeverMoving = false;
739 }
740 
fortressRotation_run()741 void Mechanical::fortressRotation_run() {
742 	VideoEntryPtr gears = _fortressRotationGears->getVideo();
743 
744 	double oldRate = gears->getRate().toDouble();
745 	uint32 moviePosition = Audio::Timestamp(gears->getTime(), 600).totalNumberOfFrames();
746 
747 	// Myst ME short movie workaround, explained in o_fortressRotation_init
748 	if (_fortressRotationShortMovieWorkaround) {
749 		// Detect if we just looped
750 		if (ABS<int32>(_fortressRotationShortMovieLast - 3680) < 50
751 				&& ABS<int32>(moviePosition) < 50) {
752 			_fortressRotationShortMovieCount++;
753 		}
754 
755 		_fortressRotationShortMovieLast = moviePosition;
756 
757 		// Simulate longer movie
758 		moviePosition += 3600 * _fortressRotationShortMovieCount;
759 	}
760 
761 	int32 positionInQuarter = 900 - (moviePosition + 900) % 1800;
762 
763 	// Are the gears moving?
764 	if (oldRate >= 0.1 || ABS<int32>(positionInQuarter) >= 30 || _fortressRotationBrake) {
765 
766 		double newRate = oldRate;
767 		if (_fortressRotationBrake && (double)_fortressRotationBrake * 0.2 > oldRate) {
768 			newRate += 0.1;
769 		}
770 
771 		// Don't let the gears get stuck between two fortress positions
772 		if (ABS<double>(oldRate) <= 0.05) {
773 			if (oldRate <= 0.0) {
774 				newRate += oldRate;
775 			} else {
776 				newRate -= oldRate;
777 			}
778 		} else {
779 			if (oldRate <= 0.0) {
780 				newRate += 0.05;
781 			} else {
782 				newRate -= 0.05;
783 			}
784 		}
785 
786 		// Adjust speed accordingly to acceleration lever
787 		newRate +=  (double) (positionInQuarter / 1500.0)
788 				* (double) (9 - _fortressRotationSpeed) / 9.0;
789 
790 		newRate = CLIP<double>(newRate, -2.5, 2.5);
791 
792 		gears->setRate(Common::Rational((int)(newRate * 1000.0), 1000));
793 
794 		_gearsWereRunning = true;
795 	} else if (_gearsWereRunning) {
796 		// The fortress has stopped. Set its new position
797 		_fortressDirection = (moviePosition + 900) / 1800 % 4;
798 
799 		gears->setRate(0);
800 
801 		if (!_fortressRotationShortMovieWorkaround) {
802 			gears->seek(Audio::Timestamp(0, 1800 * _fortressDirection, 600));
803 		} else {
804 			gears->seek(Audio::Timestamp(0, 1800 * (_fortressDirection % 2), 600));
805 		}
806 
807 		_vm->playSoundBlocking(_fortressRotationSounds[_fortressDirection]);
808 
809 		_gearsWereRunning = false;
810 	}
811 }
812 
o_fortressRotation_init(uint16 var,const ArgumentsArray & args)813 void Mechanical::o_fortressRotation_init(uint16 var, const ArgumentsArray &args) {
814 	_fortressRotationGears = getInvokingResource<MystAreaVideo>();
815 
816 	VideoEntryPtr gears = _fortressRotationGears->playMovie();
817 	gears->setLooping(true);
818 
819 	// WORKAROUND for the tower rotation bug in Myst ME.
820 	// The original engine only allowed to visit two out of the three small islands,
821 	// preventing the game from being fully completable.
822 	// The fortress rotation is computed from the current position in the movie
823 	// hcgears.mov. The version of this movie that shipped with the ME edition is
824 	// too short to allow to visit all the islands.
825 	// ScummVM simulates a longer movie by counting the number of times the movie
826 	// looped and adding that time to the current movie position.
827 	// Hence allowing the fortress position to be properly computed.
828 	uint32 movieDuration = gears->getDuration().convertToFramerate(600).totalNumberOfFrames();
829 	_fortressRotationShortMovieWorkaround = movieDuration == 3680;
830 
831 	if (!_fortressRotationShortMovieWorkaround) {
832 		gears->seek(Audio::Timestamp(0, 1800 * _fortressDirection, 600));
833 	} else {
834 		_fortressRotationShortMovieLast = 1800 * (_fortressDirection % 2);
835 		_fortressRotationShortMovieCount = _fortressDirection >= 2 ? 1 : 0;
836 		gears->seek(Audio::Timestamp(0, _fortressRotationShortMovieLast, 600));
837 	}
838 
839 	gears->setRate(0);
840 
841 	_fortressRotationSounds[0] = args[0];
842 	_fortressRotationSounds[1] = args[1];
843 	_fortressRotationSounds[2] = args[2];
844 	_fortressRotationSounds[3] = args[3];
845 
846 	_fortressRotationBrake = 0;
847 
848 	_fortressRotationRunning = true;
849 	_gearsWereRunning = false;
850 }
851 
fortressSimulation_run()852 void Mechanical::fortressSimulation_run() {
853 	if (_fortressSimulationInit) {
854 		// Init sequence
855 		_vm->_sound->playBackground(_fortressSimulationStartSound1, 65535);
856 		_vm->wait(5000, true);
857 
858 		VideoEntryPtr startup = _fortressSimulationStartup->playMovie();
859 		_vm->playSoundBlocking(_fortressSimulationStartSound2);
860 		_vm->_sound->playBackground(_fortressSimulationStartSound1, 65535);
861 		_vm->waitUntilMovieEnds(startup);
862 		_vm->_sound->stopBackground();
863 		_vm->_sound->playEffect(_fortressSimulationStartSound2);
864 
865 
866 		Common::Rect src = Common::Rect(0, 0, 176, 176);
867 		Common::Rect dst = Common::Rect(187, 3, 363, 179);
868 		_vm->_gfx->copyImageSectionToBackBuffer(6046, src, dst);
869 		_vm->_gfx->copyBackBufferToScreen(dst);
870 
871 		_fortressSimulationStartup->pauseMovie(true);
872 		VideoEntryPtr holo = _fortressSimulationHolo->playMovie();
873 		holo->setLooping(true);
874 		holo->setRate(0);
875 
876 		// HACK: Support negative rates with edit lists
877 		_fortressSimulationHoloRate = 0;
878 		// END HACK
879 
880 		_vm->_cursor->showCursor();
881 
882 		_fortressSimulationInit = false;
883 	} else {
884 		VideoEntryPtr holo = _fortressSimulationHolo->getVideo();
885 
886 		double oldRate = holo->getRate().toDouble();
887 
888 		// HACK: Support negative rates with edit lists
889 		oldRate = _fortressSimulationHoloRate;
890 		// END HACK
891 
892 		uint32 moviePosition = Audio::Timestamp(holo->getTime(), 600).totalNumberOfFrames();
893 
894 		int32 positionInQuarter = 900 - (moviePosition + 900) % 1800;
895 
896 		// Are the gears moving?
897 		if (oldRate >= 0.1 || ABS<int32>(positionInQuarter) >= 30 || _fortressSimulationBrake) {
898 
899 			double newRate = oldRate;
900 			if (_fortressSimulationBrake && (double)_fortressSimulationBrake * 0.2 > oldRate) {
901 				newRate += 0.1;
902 			}
903 
904 			// Don't let the gears get stuck between two fortress positions
905 			if (ABS<double>(oldRate) <= 0.05) {
906 				if (oldRate <= 0.0) {
907 					newRate += oldRate;
908 				} else {
909 					newRate -= oldRate;
910 				}
911 			} else {
912 				if (oldRate <= 0.0) {
913 					newRate += 0.05;
914 				} else {
915 					newRate -= 0.05;
916 				}
917 			}
918 
919 			// Adjust speed accordingly to acceleration lever
920 			newRate +=  (double) (positionInQuarter / 1500.0)
921 					* (double) (9 - _fortressSimulationSpeed) / 9.0;
922 
923 			newRate = CLIP<double>(newRate, -2.5, 2.5);
924 
925 			// HACK: Support negative rates with edit lists
926 
927 			// Our current QuickTime implementation does not support negative
928 			// playback rates for movies using edit lists.
929 			// The fortress rotation simulator movie this code handles is the
930 			// only movie in the game requiring that feature.
931 
932 			// This hack approximates the next frame to display when the rate
933 			// is negative, and seeks to it. It's not intended to be precise.
934 
935 			_fortressSimulationHoloRate = newRate;
936 
937 			if (_fortressSimulationHoloRate < 0) {
938 				double newMoviePosition = moviePosition + _fortressSimulationHoloRate * 10;
939 				holo->setRate(0);
940 				holo->seek(Audio::Timestamp(0, (uint)newMoviePosition, 600));
941 			} else {
942 				holo->setRate(Common::Rational((int)(newRate * 1000.0), 1000));
943 			}
944 			// END HACK
945 
946 			_gearsWereRunning = true;
947 		} else if (_gearsWereRunning) {
948 			// The fortress has stopped. Set its new position
949 			uint16 simulationPosition = (moviePosition + 900) / 1800 % 4;
950 
951 			holo->setRate(0);
952 
953 			// HACK: Support negative rates with edit lists
954 			_fortressSimulationHoloRate = 0;
955 			// END HACK
956 
957 			holo->seek(Audio::Timestamp(0, 1800 * simulationPosition, 600));
958 			_vm->playSoundBlocking(	_fortressRotationSounds[simulationPosition]);
959 
960 			_gearsWereRunning = false;
961 		}
962 	}
963 }
964 
o_fortressSimulation_init(uint16 var,const ArgumentsArray & args)965 void Mechanical::o_fortressSimulation_init(uint16 var, const ArgumentsArray &args) {
966 	_fortressSimulationHolo = getInvokingResource<MystAreaVideo>();
967 
968 	_fortressSimulationStartSound1 = args[0];
969 	_fortressSimulationStartSound2 = args[1];
970 
971 	_fortressRotationSounds[0] = args[2];
972 	_fortressRotationSounds[1] = args[3];
973 	_fortressRotationSounds[2] = args[4];
974 	_fortressRotationSounds[3] = args[5];
975 
976 	_fortressSimulationBrake = 0;
977 
978 	_fortressSimulationRunning = true;
979 	_gearsWereRunning = false;
980 	_fortressSimulationInit = true;
981 
982 	_vm->_cursor->hideCursor();
983 }
984 
o_fortressSimulationStartup_init(uint16 var,const ArgumentsArray & args)985 void Mechanical::o_fortressSimulationStartup_init(uint16 var, const ArgumentsArray &args) {
986 	_fortressSimulationStartup = getInvokingResource<MystAreaVideo>();
987 }
988 
989 } // End of namespace MystStacks
990 } // End of namespace Mohawk
991