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/riven.h"
25 #include "mohawk/riven_card.h"
26 #include "mohawk/riven_graphics.h"
27 #include "mohawk/riven_scripts.h"
28 #include "mohawk/riven_sound.h"
29 #include "mohawk/riven_stack.h"
30 #include "mohawk/riven_stacks/aspit.h"
31 #include "mohawk/riven_video.h"
32 #include "common/memstream.h"
33 
34 #include "common/debug-channels.h"
35 #include "common/stream.h"
36 #include "common/system.h"
37 
38 namespace Mohawk {
39 
printTabs(byte tabs)40 static void printTabs(byte tabs) {
41 	for (byte i = 0; i < tabs; i++)
42 		debugN("\t");
43 }
44 
RivenScriptManager(MohawkEngine_Riven * vm)45 RivenScriptManager::RivenScriptManager(MohawkEngine_Riven *vm) :
46 		_vm(vm),
47 		_runningQueuedScripts(false),
48 		_stoppingAllScripts(false) {
49 
50 	_storedMovieOpcode.time = 0;
51 	_storedMovieOpcode.slot = 0;
52 }
53 
~RivenScriptManager()54 RivenScriptManager::~RivenScriptManager() {
55 	clearStoredMovieOpcode();
56 }
57 
readScript(Common::ReadStream * stream)58 RivenScriptPtr RivenScriptManager::readScript(Common::ReadStream *stream) {
59 	RivenScriptPtr script = RivenScriptPtr(new RivenScript());
60 
61 	uint16 commandCount = stream->readUint16BE();
62 
63 	for (uint16 i = 0; i < commandCount; i++) {
64 		RivenCommandPtr command = readCommand(stream);
65 		script->addCommand(command);
66 	}
67 
68 	return script;
69 }
70 
readCommand(Common::ReadStream * stream)71 RivenCommandPtr RivenScriptManager::readCommand(Common::ReadStream *stream) {
72 	RivenCommandType type = (RivenCommandType) stream->readUint16BE();
73 
74 	switch (type) {
75 		case kRivenCommandSwitch:
76 			return RivenCommandPtr(RivenSwitchCommand::createFromStream(_vm, stream));
77 		case kRivenCommandChangeStack:
78 			return RivenCommandPtr(RivenStackChangeCommand::createFromStream(_vm, stream));
79 		default:
80 			return RivenCommandPtr(RivenSimpleCommand::createFromStream(_vm, type, stream));
81 	}
82 }
83 
readScripts(Common::ReadStream * stream)84 RivenScriptList RivenScriptManager::readScripts(Common::ReadStream *stream) {
85 	RivenScriptList scriptList;
86 
87 	uint16 scriptCount = stream->readUint16BE();
88 	for (uint16 i = 0; i < scriptCount; i++) {
89 		RivenTypedScript script;
90 		script.type = stream->readUint16BE();
91 		script.script = readScript(stream);
92 		scriptList.push_back(script);
93 	}
94 
95 	return scriptList;
96 }
97 
stopAllScripts()98 void RivenScriptManager::stopAllScripts() {
99 	_stoppingAllScripts = true;
100 }
101 
setStoredMovieOpcode(const StoredMovieOpcode & op)102 void RivenScriptManager::setStoredMovieOpcode(const StoredMovieOpcode &op) {
103 	clearStoredMovieOpcode();
104 	_storedMovieOpcode.script = op.script;
105 	_storedMovieOpcode.slot = op.slot;
106 	_storedMovieOpcode.time = op.time;
107 }
108 
runStoredMovieOpcode()109 void RivenScriptManager::runStoredMovieOpcode() {
110 	if (_storedMovieOpcode.script) {
111 		runScript(_storedMovieOpcode.script, false);
112 		clearStoredMovieOpcode();
113 	}
114 }
115 
clearStoredMovieOpcode()116 void RivenScriptManager::clearStoredMovieOpcode() {
117 	_storedMovieOpcode.script = RivenScriptPtr();
118 	_storedMovieOpcode.time = 0;
119 	_storedMovieOpcode.slot = 0;
120 }
121 
runScript(const RivenScriptPtr & script,bool queue)122 void RivenScriptManager::runScript(const RivenScriptPtr &script, bool queue) {
123 	if (!script || script->empty()) {
124 		return;
125 	}
126 
127 	if (!queue) {
128 		script->run(this);
129 	} else {
130 		_queue.push_back(script);
131 	}
132 }
133 
hasQueuedScripts() const134 bool RivenScriptManager::hasQueuedScripts() const {
135 	return !_queue.empty();
136 }
137 
runQueuedScripts()138 void RivenScriptManager::runQueuedScripts() {
139 	_runningQueuedScripts = true;
140 
141 	for (uint i = 0; i < _queue.size(); i++) {
142 		_queue[i]->run(this);
143 	}
144 
145 	_queue.clear();
146 
147 	_stoppingAllScripts = false; // Once the queue is empty, all scripts have been stopped
148 	_runningQueuedScripts = false;
149 }
150 
createScriptFromData(uint commandCount,...)151 RivenScriptPtr RivenScriptManager::createScriptFromData(uint commandCount, ...) {
152 	va_list args;
153 	va_start(args, commandCount);
154 
155 	// Build a script from the variadic arguments
156 	Common::MemoryWriteStreamDynamic writeStream = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
157 	writeStream.writeUint16BE((uint16)commandCount);
158 
159 	for (uint i = 0; i < commandCount; i++) {
160 		uint16 command = va_arg(args, int);
161 		writeStream.writeUint16BE(command);
162 
163 		if (command == kRivenCommandSwitch) {
164 			// The switch command has a different format that is not implemented
165 			error("Cannot create a Switch command from data");
166 		}
167 
168 		uint16 argumentCount = va_arg(args, int);
169 		writeStream.writeUint16BE(argumentCount);
170 
171 		for (uint j = 0; j < commandCount; j++) {
172 			uint16 argument = va_arg(args, int);
173 			writeStream.writeUint16BE(argument);
174 		}
175 	}
176 
177 	va_end(args);
178 
179 	Common::MemoryReadStream readStream = Common::MemoryReadStream(writeStream.getData(), writeStream.size());
180 	return readScript(&readStream);
181 }
182 
readScriptFromData(uint16 * data,uint16 size)183 RivenScriptPtr RivenScriptManager::readScriptFromData(uint16 *data, uint16 size) {
184 	// Script data is expected to be in big endian
185 	for (uint i = 0; i < size; i++) {
186 		data[i] = TO_BE_16(data[i]);
187 	}
188 
189 	Common::MemoryReadStream patchStream((const byte *)(data), size * sizeof(uint16));
190 	return _vm->_scriptMan->readScript(&patchStream);
191 }
192 
createScriptWithCommand(RivenCommand * command)193 RivenScriptPtr RivenScriptManager::createScriptWithCommand(RivenCommand *command) {
194 	assert(command);
195 
196 	RivenScriptPtr script = RivenScriptPtr(new RivenScript());
197 	script->addCommand(RivenCommandPtr(command));
198 	return script;
199 }
200 
runningQueuedScripts() const201 bool RivenScriptManager::runningQueuedScripts() const {
202 	return _runningQueuedScripts;
203 }
204 
stoppingAllScripts() const205 bool RivenScriptManager::stoppingAllScripts() const {
206 	return _stoppingAllScripts;
207 }
208 
RivenScript()209 RivenScript::RivenScript() {
210 }
211 
~RivenScript()212 RivenScript::~RivenScript() {
213 }
214 
dumpScript(byte tabs)215 void RivenScript::dumpScript(byte tabs) {
216 	for (uint16 i = 0; i < _commands.size(); i++) {
217 		_commands[i]->dump(tabs);
218 	}
219 }
220 
run(RivenScriptManager * scriptManager)221 void RivenScript::run(RivenScriptManager *scriptManager) {
222 	for (uint i = 0; i < _commands.size(); i++) {
223 		if (scriptManager->stoppingAllScripts()) {
224 			return;
225 		}
226 
227 		_commands[i]->execute();
228 	}
229 }
230 
addCommand(RivenCommandPtr command)231 void RivenScript::addCommand(RivenCommandPtr command) {
232 	_commands.push_back(command);
233 }
234 
empty() const235 bool RivenScript::empty() const {
236 	return _commands.empty();
237 }
238 
operator +=(const RivenScript & other)239 RivenScript &RivenScript::operator+=(const RivenScript &other) {
240 	_commands.push_back(other._commands);
241 	return *this;
242 }
243 
getTypeName(uint16 type)244 const char *RivenScript::getTypeName(uint16 type) {
245 	static const char *names[] = {
246 		"MouseDown",
247 		"MouseDrag",
248 		"MouseUp",
249 		"MouseEnter",
250 		"MouseInside",
251 		"MouseLeave",
252 		"CardLoad",
253 		"CardLeave",
254 		"CardUnknown",
255 		"CardEnter",
256 		"CardUpdate"
257 	};
258 
259 	assert(type < ARRAYSIZE(names));
260 	return names[type];
261 }
262 
applyCardPatches(MohawkEngine_Riven * vm,uint32 cardGlobalId,uint16 scriptType,uint16 hotspotId)263 void RivenScript::applyCardPatches(MohawkEngine_Riven *vm, uint32 cardGlobalId, uint16 scriptType, uint16 hotspotId) {
264 	bool shouldApplyPatches = false;
265 
266 	// On Prison Island when pressing the dome viewer switch to close the dome,
267 	// the game schedules an ambient sound change using kRivenCommandStoreMovieOpcode
268 	// but does not play the associated video in a blocking way. The stored opcode
269 	// is not immediately used, stays in memory and may be triggered by some
270 	// other action. (Bug #9958)
271 	// We replace kRivenCommandStoreMovieOpcode by kRivenCommandActivateSLST
272 	// to make the ambient sound change happen immediately.
273 	//
274 	// Script before patch:
275 	// playMovieBlocking(3); // Dome closing
276 	// playMovie(4);         // Dome spinning up
277 	// activatePLST(2);      // Dome closed
278 	// playMovieBlocking(4); // Dome spinning up
279 	// storeMovieOpcode(1, 0, 0, 40, 2); // Schedule ambient sound change to "dome spinning"
280 	//                                      after movie 1 finishes blocking playback
281 	// playMovie(1);         // Dome spinning
282 	//
283 	// Script after patch:
284 	// playMovieBlocking(3); // Dome closing
285 	// playMovie(4);         // Dome spinning up
286 	// activatePLST(2);      // Dome closed
287 	// playMovieBlocking(4); // Dome spinning up
288 	// activateSLST(2);      // Ambient sound change to "dome spinning"
289 	// playMovie(1);         // Dome spinning
290 	if (cardGlobalId == 0x1AC1 && scriptType == kCardEnterScript) {
291 		shouldApplyPatches = true;
292 		for (uint i = 0; i < _commands.size(); i++) {
293 			if (_commands[i]->getType() == kRivenCommandStoreMovieOpcode) {
294 				RivenSimpleCommand::ArgumentArray arguments;
295 				arguments.push_back(2);
296 				_commands[i] = RivenCommandPtr(new RivenSimpleCommand(vm, kRivenCommandActivateSLST, arguments));
297 				debugC(kRivenDebugPatches, "Applied immediate ambient sound patch to card %x", cardGlobalId);
298 				break;
299 			}
300 		}
301 	}
302 
303 	// On Jungle Island when entering the submarine from the dock beside the main walkway,
304 	// the sound of the hatch closing does not play (Bug #9972).
305 	// This happens only in the CD version of the game.
306 	//
307 	// Script before patch:
308 	// transition(16);
309 	// switchCard(534);
310 	//
311 	// Script after patch:
312 	// transition(16);
313 	// switchCard(534);
314 	// playSound(112, 256, 0);
315 	if (cardGlobalId == 0x2E900 && scriptType == kMouseDownScript && hotspotId == 3
316 			&& !(vm->getFeatures() & GF_DVD)) {
317 		shouldApplyPatches = true;
318 		RivenSimpleCommand::ArgumentArray arguments;
319 		arguments.push_back(112);
320 		arguments.push_back(256);
321 		arguments.push_back(0);
322 		_commands.push_back(RivenCommandPtr(new RivenSimpleCommand(vm, kRivenCommandPlaySound, arguments)));
323 		debugC(kRivenDebugPatches, "Applied missing closing sound patch to card %x", cardGlobalId);
324 	}
325 
326 	// Second part of the patch to fix the invalid card change when entering Gehn's office
327 	// The first part is in the card patches.
328 	if (cardGlobalId == 0x2E76 && scriptType == kCardUpdateScript && !(vm->getFeatures() & GF_DVD)) {
329 		shouldApplyPatches = true;
330 
331 		for (uint i = 0; i < _commands.size(); i++) {
332 			int transitionIndex = -1;
333 			if (_commands[i]->getType() == kRivenCommandTransition) {
334 				transitionIndex = i;
335 			}
336 			if (transitionIndex >= 0) {
337 				_commands.remove_at(transitionIndex + 1);
338 				_commands.remove_at(transitionIndex);
339 
340 				RivenSimpleCommand::ArgumentArray arguments;
341 				arguments.push_back(6);
342 				_commands.push_back(RivenCommandPtr(new RivenSimpleCommand(vm, kRivenCommandActivatePLST, arguments)));
343 			}
344 		}
345 
346 		debugC(kRivenDebugPatches, "Applied invalid card change during screen update (2/2) to card %x", cardGlobalId);
347 	}
348 
349 	// First part of the patch to fix the invalid steam sounds
350 	// when looking at the Boiler island bridge from Temple island.
351 	// The second part is in the card patches.
352 	if (cardGlobalId == 0x22118 && scriptType == kCardLoadScript) {
353 		shouldApplyPatches = true;
354 
355 		// Remove all the activateSLST calls.
356 		// Fixed calls will be added back in the second part of the patch.
357 		for (uint i = 0; i < _commands.size(); i++) {
358 			if (_commands[i]->getType() == kRivenCommandActivateSLST) {
359 				_commands.remove_at(i);
360 				break;
361 			}
362 		}
363 
364 		debugC(kRivenDebugPatches, "Applied incorrect steam sounds (1/2) to card %x", cardGlobalId);
365 	}
366 
367 	// Override the main menu new game script to call an external command.
368 	// This way we can reset all the state when starting a new game while a game is already started.
369 	if (cardGlobalId == 0xE2E && scriptType == kMouseDownScript && hotspotId == 16
370 			&& (vm->getFeatures() & GF_25TH)) {
371 		shouldApplyPatches = true;
372 		_commands.clear();
373 
374 		RivenSimpleCommand::ArgumentArray arguments;
375 		arguments.push_back(RivenStacks::ASpit::kExternalNewGame);
376 		arguments.push_back(0);
377 		_commands.push_back(RivenCommandPtr(new RivenSimpleCommand(vm, kRivenCommandRunExternal, arguments)));
378 		debugC(kRivenDebugPatches, "Applied override new game script patch to card %x", cardGlobalId);
379 	}
380 
381 	if (shouldApplyPatches) {
382 		for (uint i = 0; i < _commands.size(); i++) {
383 			_commands[i]->applyCardPatches(cardGlobalId, scriptType, hotspotId);
384 		}
385 	}
386 }
387 
operator +=(RivenScriptPtr & lhs,const RivenScriptPtr & rhs)388 RivenScriptPtr &operator+=(RivenScriptPtr &lhs, const RivenScriptPtr &rhs) {
389 	if (rhs) {
390 		*lhs += *rhs;
391 	}
392 	return lhs;
393 }
394 
RivenCommand(MohawkEngine_Riven * vm)395 RivenCommand::RivenCommand(MohawkEngine_Riven *vm) :
396 		_vm(vm) {
397 
398 }
399 
~RivenCommand()400 RivenCommand::~RivenCommand() {
401 
402 }
403 
RivenSimpleCommand(MohawkEngine_Riven * vm,RivenCommandType type,const ArgumentArray & arguments)404 RivenSimpleCommand::RivenSimpleCommand(MohawkEngine_Riven *vm, RivenCommandType type, const ArgumentArray &arguments) :
405 		RivenCommand(vm),
406 		_type(type),
407 		_arguments(arguments) {
408 	setupOpcodes();
409 }
410 
~RivenSimpleCommand()411 RivenSimpleCommand::~RivenSimpleCommand() {
412 }
413 
createFromStream(MohawkEngine_Riven * vm,RivenCommandType type,Common::ReadStream * stream)414 RivenSimpleCommand *RivenSimpleCommand::createFromStream(MohawkEngine_Riven *vm, RivenCommandType type, Common::ReadStream *stream) {
415 	uint16 argc = stream->readUint16BE();
416 
417 	Common::Array<uint16> arguments;
418 	arguments.resize(argc);
419 
420 	for (uint16 i = 0; i < argc; i++) {
421 		arguments[i] = stream->readUint16BE();
422 	}
423 
424 	return new RivenSimpleCommand(vm, type, arguments);
425 }
426 
427 #define OPCODE(x) { &RivenSimpleCommand::x, #x }
428 
setupOpcodes()429 void RivenSimpleCommand::setupOpcodes() {
430 	static const RivenOpcode riven_opcodes[] = {
431 		// 0x00 (0 decimal)
432 		OPCODE(empty),
433 		OPCODE(drawBitmap),
434 		OPCODE(switchCard),
435 		OPCODE(playScriptSLST),
436 		// 0x04 (4 decimal)
437 		OPCODE(playSound),
438 		OPCODE(empty),						// Empty
439 		OPCODE(empty),						// Complex animation (not used)
440 		OPCODE(setVariable),
441 		// 0x08 (8 decimal)
442 		OPCODE(empty),                      // Not a SimpleCommand
443 		OPCODE(enableHotspot),
444 		OPCODE(disableHotspot),
445 		OPCODE(empty),						// Empty
446 		// 0x0C (12 decimal)
447 		OPCODE(stopSound),
448 		OPCODE(changeCursor),
449 		OPCODE(delay),
450 		OPCODE(empty),						// Empty
451 		// 0x10 (16 decimal)
452 		OPCODE(empty),						// Empty
453 		OPCODE(runExternalCommand),
454 		OPCODE(transition),
455 		OPCODE(refreshCard),
456 		// 0x14 (20 decimal)
457 		OPCODE(beginScreenUpdate),
458 		OPCODE(applyScreenUpdate),
459 		OPCODE(empty),						// Empty
460 		OPCODE(empty),						// Empty
461 		// 0x18 (24 decimal)
462 		OPCODE(incrementVariable),
463 		OPCODE(empty),						// Empty
464 		OPCODE(empty),						// Empty
465 		OPCODE(empty),                      // Not a SimpleCommand
466 		// 0x1C (28 decimal)
467 		OPCODE(disableMovie),
468 		OPCODE(disableAllMovies),
469 		OPCODE(empty),						// Set movie rate (not used)
470 		OPCODE(enableMovie),
471 		// 0x20 (32 decimal)
472 		OPCODE(playMovieBlocking),
473 		OPCODE(playMovie),
474 		OPCODE(stopMovie),
475 		OPCODE(empty),						// Start a water effect (not used)
476 		// 0x24 (36 decimal)
477 		OPCODE(unk_36),						// Unknown
478 		OPCODE(fadeAmbientSounds),
479 		OPCODE(storeMovieOpcode),
480 		OPCODE(activatePLST),
481 		// 0x28 (40 decimal)
482 		OPCODE(activateSLST),
483 		OPCODE(activateMLSTAndPlay),
484 		OPCODE(empty),						// Empty
485 		OPCODE(activateBLST),
486 		// 0x2C (44 decimal)
487 		OPCODE(activateFLST),
488 		OPCODE(zipMode),
489 		OPCODE(activateMLST),
490 		OPCODE(empty)                       // Activate an SLST with a volume parameter (not used)
491 	};
492 
493 	_opcodes = riven_opcodes;
494 }
495 
496 ////////////////////////////////
497 // Opcodes
498 ////////////////////////////////
499 
500 // Command 1: draw tBMP resource (tbmp_id, left, top, right, bottom, u0, u1, u2, u3)
drawBitmap(uint16 op,const ArgumentArray & args)501 void RivenSimpleCommand::drawBitmap(uint16 op, const ArgumentArray &args) {
502 	if (args.size() < 5) // Copy the image to the whole screen, ignoring the rest of the parameters
503 		_vm->_gfx->copyImageToScreen(args[0], 0, 0, 608, 392);
504 	else          // Copy the image to a certain part of the screen
505 		_vm->_gfx->copyImageToScreen(args[0], args[1], args[2], args[3], args[4]);
506 }
507 
508 // Command 2: go to card (card id)
switchCard(uint16 op,const ArgumentArray & args)509 void RivenSimpleCommand::switchCard(uint16 op, const ArgumentArray &args) {
510 	_vm->changeToCard(args[0]);
511 }
512 
513 // Command 3: play an SLST from the script
playScriptSLST(uint16 op,const ArgumentArray & args)514 void RivenSimpleCommand::playScriptSLST(uint16 op, const ArgumentArray &args) {
515 	int offset = 0, j = 0;
516 	uint16 soundCount = args[offset++];
517 
518 	SLSTRecord slstRecord;
519 	slstRecord.index = 0;		// not set by the scripts, so we set it to 0
520 	slstRecord.soundIds.resize(soundCount);
521 
522 	for (j = 0; j < soundCount; j++)
523 		slstRecord.soundIds[j] = args[offset++];
524 	slstRecord.fadeFlags = args[offset++];
525 	slstRecord.loop = args[offset++];
526 	slstRecord.globalVolume = args[offset++];
527 	slstRecord.u0 = args[offset++];
528 	slstRecord.suspend = args[offset++];
529 
530 	slstRecord.volumes.resize(soundCount);
531 	slstRecord.balances.resize(soundCount);
532 	slstRecord.u2.resize(soundCount);
533 
534 	for (j = 0; j < soundCount; j++)
535 		slstRecord.volumes[j] = args[offset++];
536 
537 	for (j = 0; j < soundCount; j++)
538 		slstRecord.balances[j] = args[offset++];	// negative = left, 0 = center, positive = right
539 
540 	for (j = 0; j < soundCount; j++)
541 		slstRecord.u2[j] = args[offset++];			// Unknown
542 
543 	// Play the requested sound list
544 	_vm->_sound->playSLST(slstRecord);
545 }
546 
547 // Command 4: play local tWAV resource (twav_id, volume, block)
playSound(uint16 op,const ArgumentArray & args)548 void RivenSimpleCommand::playSound(uint16 op, const ArgumentArray &args) {
549 	uint16 volume = args[1];
550 	bool playOnDraw = args[2] == 1;
551 
552 	_vm->_sound->playSound(args[0], volume, playOnDraw);
553 }
554 
555 // Command 7: set variable value (variable, value)
setVariable(uint16 op,const ArgumentArray & args)556 void RivenSimpleCommand::setVariable(uint16 op, const ArgumentArray &args) {
557 	_vm->getStackVar(args[0]) = args[1];
558 }
559 
560 // Command 9: enable hotspot (blst_id)
enableHotspot(uint16 op,const ArgumentArray & args)561 void RivenSimpleCommand::enableHotspot(uint16 op, const ArgumentArray &args) {
562 	RivenHotspot *hotspot = _vm->getCard()->getHotspotByBlstId(args[0]);
563 	if (hotspot) {
564 		hotspot->enable(true);
565 	}
566 }
567 
568 // Command 10: disable hotspot (blst_id)
disableHotspot(uint16 op,const ArgumentArray & args)569 void RivenSimpleCommand::disableHotspot(uint16 op, const ArgumentArray &args) {
570 	RivenHotspot *hotspot = _vm->getCard()->getHotspotByBlstId(args[0]);
571 	if (hotspot) {
572 		hotspot->enable(false);
573 	}
574 }
575 
576 // Command 12: stop sounds (flags)
stopSound(uint16 op,const ArgumentArray & args)577 void RivenSimpleCommand::stopSound(uint16 op, const ArgumentArray &args) {
578 	// WORKAROUND: The Play Riven/Visit Riven/Start New Game buttons
579 	// in the main menu call this function to stop ambient sounds
580 	// after the change stack call to Temple Island. However, this
581 	// would cause all ambient sounds not to play. An alternative
582 	// fix would be to stop all scripts on a stack change, but this
583 	// does fine for now.
584 	if (_vm->getStack()->getId() == kStackTspit && (_vm->getStack()->getCurrentCardGlobalId() == 0x6e9a ||
585 			_vm->getStack()->getCurrentCardGlobalId() == 0xfeeb))
586 		return;
587 
588 	// The argument is a bitflag for the setting.
589 	// bit 0 is normal sound stopping
590 	// bit 1 is ambient sound stopping
591 	// Having no flags set means clear all
592 	if (args[0] & 2 || args[0] == 0)
593 		_vm->_sound->stopAllSLST();
594 
595 	if (args[0] & 1 || args[0] == 0)
596 		_vm->_sound->stopSound();
597 }
598 
599 // Command 13: set mouse cursor (cursor_id)
changeCursor(uint16 op,const ArgumentArray & args)600 void RivenSimpleCommand::changeCursor(uint16 op, const ArgumentArray &args) {
601 	_vm->_cursor->setCursor(args[0]);
602 }
603 
604 // Command 14: pause script execution (delay in ms, u1)
delay(uint16 op,const ArgumentArray & args)605 void RivenSimpleCommand::delay(uint16 op, const ArgumentArray &args) {
606 	if (args[0] > 0)
607 		_vm->delay(args[0]);
608 }
609 
610 // Command 17: call external command
runExternalCommand(uint16 op,const ArgumentArray & args)611 void RivenSimpleCommand::runExternalCommand(uint16 op, const ArgumentArray &args) {
612 	uint16 commandNameid = args[0];
613 	uint16 argumentCount = args[1];
614 
615 	Common::Array<uint16> commandArgs(argumentCount ? &args[2] : nullptr, argumentCount);
616 
617 	_vm->getStack()->runCommand(commandNameid, commandArgs);
618 }
619 
620 // Command 18: transition
621 // Note that this opcode has 1 or 5 parameters, depending on args.size()
622 // Parameter 0: transition type
623 // Parameters 1-4: transition rectangle
transition(uint16 op,const ArgumentArray & args)624 void RivenSimpleCommand::transition(uint16 op, const ArgumentArray &args) {
625 	if (args.size() == 1)
626 		_vm->_gfx->scheduleTransition((RivenTransition) args[0]);
627 	else
628 		_vm->_gfx->scheduleTransition((RivenTransition) args[0], Common::Rect(args[1], args[2], args[3], args[4]));
629 }
630 
631 // Command 19: reload card
refreshCard(uint16 op,const ArgumentArray & args)632 void RivenSimpleCommand::refreshCard(uint16 op, const ArgumentArray &args) {
633 	_vm->getCard()->enter(false);
634 }
635 
636 // Command 20: begin screen update
beginScreenUpdate(uint16 op,const ArgumentArray & args)637 void RivenSimpleCommand::beginScreenUpdate(uint16 op, const ArgumentArray &args) {
638 	_vm->_gfx->beginScreenUpdate();
639 }
640 
641 // Command 21: apply screen update
applyScreenUpdate(uint16 op,const ArgumentArray & args)642 void RivenSimpleCommand::applyScreenUpdate(uint16 op, const ArgumentArray &args) {
643 	_vm->_gfx->applyScreenUpdate();
644 }
645 
646 // Command 24: increment variable (variable, value)
incrementVariable(uint16 op,const ArgumentArray & args)647 void RivenSimpleCommand::incrementVariable(uint16 op, const ArgumentArray &args) {
648 	_vm->getStackVar(args[0]) += args[1];
649 }
650 
651 // Command 28: disable a movie
disableMovie(uint16 op,const ArgumentArray & args)652 void RivenSimpleCommand::disableMovie(uint16 op, const ArgumentArray &args) {
653 	RivenVideo *video = _vm->_video->openSlot(args[0]);
654 	if (video)
655 		video->disable();
656 }
657 
658 // Command 29: disable all movies
disableAllMovies(uint16 op,const ArgumentArray & args)659 void RivenSimpleCommand::disableAllMovies(uint16 op, const ArgumentArray &args) {
660 	_vm->_video->disableAllMovies();
661 }
662 
663 // Command 31: enable a movie
enableMovie(uint16 op,const ArgumentArray & args)664 void RivenSimpleCommand::enableMovie(uint16 op, const ArgumentArray &args) {
665 	RivenVideo *video = _vm->_video->openSlot(args[0]);
666 	video->enable();
667 }
668 
669 // Command 32: play foreground movie - blocking (movie_id)
playMovieBlocking(uint16 op,const ArgumentArray & args)670 void RivenSimpleCommand::playMovieBlocking(uint16 op, const ArgumentArray &args) {
671 	RivenVideo *video = _vm->_video->openSlot(args[0]);
672 	video->setLooping(false);
673 	video->enable();
674 	video->playBlocking();
675 }
676 
677 // Command 33: play background movie - nonblocking (movie_id)
playMovie(uint16 op,const ArgumentArray & args)678 void RivenSimpleCommand::playMovie(uint16 op, const ArgumentArray &args) {
679 	RivenVideo *video = _vm->_video->openSlot(args[0]);
680 	video->enable();
681 	video->play();
682 }
683 
684 // Command 34: stop a movie
stopMovie(uint16 op,const ArgumentArray & args)685 void RivenSimpleCommand::stopMovie(uint16 op, const ArgumentArray &args) {
686 	RivenVideo *video = _vm->_video->openSlot(args[0]);
687 	video->stop();
688 }
689 
690 // Command 36: unknown
unk_36(uint16 op,const ArgumentArray & args)691 void RivenSimpleCommand::unk_36(uint16 op, const ArgumentArray &args) {
692 }
693 
694 // Command 37: fade ambient sounds
fadeAmbientSounds(uint16 op,const ArgumentArray & args)695 void RivenSimpleCommand::fadeAmbientSounds(uint16 op, const ArgumentArray &args) {
696 	// Similar to stopSound(), but does fading
697 	_vm->_sound->stopAllSLST(true);
698 }
699 
700 // Command 38: Store an opcode for use when playing a movie (movie id, time high, time low, opcode, arguments...)
storeMovieOpcode(uint16 op,const ArgumentArray & args)701 void RivenSimpleCommand::storeMovieOpcode(uint16 op, const ArgumentArray &args) {
702 	// This opcode is used to delay an opcode's usage based on the elapsed
703 	// time of a specified movie. However, every use in the game is for
704 	// delaying an activateSLST opcode.
705 
706 	uint32 delayTime = (args[1] << 16) + args[2];
707 
708 	// Store the script
709 	RivenScriptManager::StoredMovieOpcode storedOp;
710 	storedOp.script = _vm->_scriptMan->createScriptFromData(1, args[3], 1, args[4]);
711 	storedOp.time = delayTime;
712 	storedOp.slot = args[0];
713 
714 	// Store the opcode for later
715 	_vm->_scriptMan->setStoredMovieOpcode(storedOp);
716 }
717 
718 // Command 39: activate PLST record (card picture lists)
activatePLST(uint16 op,const ArgumentArray & args)719 void RivenSimpleCommand::activatePLST(uint16 op, const ArgumentArray &args) {
720 	_vm->_activatedPLST = true;
721 
722 	RivenCard::Picture picture = _vm->getCard()->getPicture(args[0]);
723 	_vm->_gfx->copyImageToScreen(picture.id, picture.rect.left, picture.rect.top, picture.rect.right, picture.rect.bottom);
724 }
725 
726 // Command 40: activate SLST record (card ambient sound lists)
activateSLST(uint16 op,const ArgumentArray & args)727 void RivenSimpleCommand::activateSLST(uint16 op, const ArgumentArray &args) {
728 	_vm->_activatedSLST = true;
729 
730 	SLSTRecord slstRecord = _vm->getCard()->getSound(args[0]);
731 	_vm->_sound->playSLST(slstRecord);
732 }
733 
734 // Command 41: activate MLST record and play
activateMLSTAndPlay(uint16 op,const ArgumentArray & args)735 void RivenSimpleCommand::activateMLSTAndPlay(uint16 op, const ArgumentArray &args) {
736 	MLSTRecord mlstRecord = _vm->getCard()->getMovie(args[0]);
737 	activateMLST(mlstRecord);
738 
739 	RivenVideo *video = _vm->_video->openSlot(mlstRecord.playbackSlot);
740 	video->enable();
741 	video->play();
742 }
743 
744 // Command 43: activate BLST record (card hotspot enabling lists)
activateBLST(uint16 op,const ArgumentArray & args)745 void RivenSimpleCommand::activateBLST(uint16 op, const ArgumentArray &args) {
746 	_vm->getCard()->activateHotspotEnableRecord(args[0]);
747 }
748 
749 // Command 44: activate FLST record (information on which SFXE resource this card should use)
activateFLST(uint16 op,const ArgumentArray & args)750 void RivenSimpleCommand::activateFLST(uint16 op, const ArgumentArray &args) {
751 	_vm->getCard()->activateWaterEffect(args[0]);
752 }
753 
754 // Command 45: do zip mode
zipMode(uint16 op,const ArgumentArray & args)755 void RivenSimpleCommand::zipMode(uint16 op, const ArgumentArray &args) {
756 	assert(_vm->getCard() && _vm->getCard()->getCurHotspot());
757 
758 	// Check the ZIPS records to see if we have a match to the hotspot name
759 	Common::String hotspotName = _vm->getCard()->getCurHotspot()->getName();
760 
761 	for (uint16 i = 0; i < _vm->_zipModeData.size(); i++)
762 		if (_vm->_zipModeData[i].name == hotspotName) {
763 			_vm->changeToCard(_vm->_zipModeData[i].id);
764 			return;
765 		}
766 }
767 
768 // Command 46: activate MLST record (movie lists)
activateMLST(uint16 op,const ArgumentArray & args)769 void RivenSimpleCommand::activateMLST(uint16 op, const ArgumentArray &args) {
770 	MLSTRecord mlstRecord = _vm->getCard()->getMovie(args[0]);
771 	activateMLST(mlstRecord);
772 }
773 
activateMLST(const MLSTRecord & mlstRecord) const774 void RivenSimpleCommand::activateMLST(const MLSTRecord &mlstRecord) const {
775 	RivenVideo *ptr = _vm->_video->openSlot(mlstRecord.playbackSlot);
776 	ptr->load(mlstRecord.movieID);
777 	ptr->moveTo(mlstRecord.left, mlstRecord.top);
778 	ptr->setLooping(mlstRecord.loop != 0);
779 	ptr->setVolume(mlstRecord.volume);
780 }
781 
describe() const782 Common::String RivenSimpleCommand::describe() const {
783 	Common::String desc;
784 
785 	if (_type == kRivenCommandSwitch) { // Use the variable name
786 		Common::String varName = _vm->getStack()->getName(kVariableNames, _arguments[0]);
787 		desc = Common::String::format("%s = %d", varName.c_str(), _arguments[1]);
788 	} else if (_type == kRivenCommandRunExternal) { // Use the external command name
789 		Common::String externalCommandName = _vm->getStack()->getName(kExternalCommandNames, _arguments[0]);
790 		desc = Common::String::format("%s(", externalCommandName.c_str());
791 		uint16 varCount = _arguments[1];
792 		for (uint16 j = 0; j < varCount; j++) {
793 			desc += Common::String::format("%d", _arguments[2 + j]);
794 			if (j != varCount - 1)
795 				desc += ", ";
796 		}
797 		desc += ")";
798 	} else if (_type == kRivenCommandIncrementVariable) { // Use the variable name
799 		Common::String varName = _vm->getStack()->getName(kVariableNames, _arguments[0]);
800 		desc = Common::String::format("%s += %d", varName.c_str(), _arguments[1]);
801 	} else if (_type == kRivenCommandSetVariable) { // Use the variable name
802 		Common::String varName = _vm->getStack()->getName(kVariableNames, _arguments[0]);
803 		desc = Common::String::format("%s = %d", varName.c_str(), _arguments[1]);
804 	} else {
805 		desc = Common::String::format("%s(", _opcodes[_type].desc);
806 		for (uint16 j = 0; j < _arguments.size(); j++) {
807 			desc += Common::String::format("%d", _arguments[j]);
808 			if (j != _arguments.size() - 1)
809 				desc += ", ";
810 		}
811 		desc += ")";
812 	}
813 
814 	return desc;
815 }
816 
dump(byte tabs)817 void RivenSimpleCommand::dump(byte tabs) {
818 	printTabs(tabs);
819 	debugN("%s;\n", describe().c_str());
820 }
821 
execute()822 void RivenSimpleCommand::execute() {
823 	if (DebugMan.isDebugChannelEnabled(kRivenDebugScript)) {
824 		debugC(kRivenDebugScript, "Running opcode: %s", describe().c_str());
825 	}
826 
827 	(this->*(_opcodes[_type].proc)) (_type, _arguments);
828 }
829 
getType() const830 RivenCommandType RivenSimpleCommand::getType() const {
831 	return _type;
832 }
833 
RivenSwitchCommand(MohawkEngine_Riven * vm)834 RivenSwitchCommand::RivenSwitchCommand(MohawkEngine_Riven *vm) :
835 		RivenCommand(vm),
836 		_variableId(0) {
837 
838 }
839 
~RivenSwitchCommand()840 RivenSwitchCommand::~RivenSwitchCommand() {
841 
842 }
843 
createFromStream(MohawkEngine_Riven * vm,Common::ReadStream * stream)844 RivenSwitchCommand *RivenSwitchCommand::createFromStream(MohawkEngine_Riven *vm, Common::ReadStream *stream) {
845 	RivenSwitchCommand *command = new RivenSwitchCommand(vm);
846 
847 	if (stream->readUint16BE() != 2) {
848 		// This value is not used in the original engine
849 		warning("if-then-else unknown value is not 2");
850 	}
851 
852 	// variable to check against
853 	command->_variableId = stream->readUint16BE();
854 
855 	// number of logic blocks
856 	uint16 logicBlockCount = stream->readUint16BE();
857 	command->_branches.resize(logicBlockCount);
858 
859 	for (uint16 i = 0; i < logicBlockCount; i++) {
860 		Branch &branch = command->_branches[i];
861 
862 		// Value for this logic block
863 		branch.value = stream->readUint16BE();
864 		branch.script = vm->_scriptMan->readScript(stream);
865 	}
866 
867 	return command;
868 }
869 
dump(byte tabs)870 void RivenSwitchCommand::dump(byte tabs) {
871 	Common::String varName = _vm->getStack()->getName(kVariableNames, _variableId);
872 	printTabs(tabs); debugN("switch (%s) {\n", varName.c_str());
873 	for (uint16 j = 0; j < _branches.size(); j++) {
874 		printTabs(tabs + 1);
875 		if (_branches[j].value == 0xFFFF)
876 			debugN("default:\n");
877 		else
878 			debugN("case %d:\n", _branches[j].value);
879 		_branches[j].script->dumpScript(tabs + 2);
880 		printTabs(tabs + 2); debugN("break;\n");
881 	}
882 	printTabs(tabs); debugN("}\n");
883 }
884 
execute()885 void RivenSwitchCommand::execute() {
886 	if (DebugMan.isDebugChannelEnabled(kRivenDebugScript)) {
887 		Common::String varName = _vm->getStack()->getName(kVariableNames, _variableId);
888 		debugC(kRivenDebugScript, "Running opcode: switch(%s)", varName.c_str());
889 	}
890 
891 	// Get the switch variable value
892 	uint32 value = _vm->getStackVar(_variableId);
893 
894 	// Look for a case matching the value
895 	for (uint i = 0; i < _branches.size(); i++) {
896 		if  (_branches[i].value == value) {
897 			_vm->_scriptMan->runScript(_branches[i].script, false);
898 			return;
899 		}
900 	}
901 
902 	// Look for the default case if any
903 	for (uint i = 0; i < _branches.size(); i++) {
904 		if  (_branches[i].value == 0Xffff) {
905 			_vm->_scriptMan->runScript(_branches[i].script, false);
906 			return;
907 		}
908 	}
909 }
910 
getType() const911 RivenCommandType RivenSwitchCommand::getType() const {
912 	return kRivenCommandSwitch;
913 }
914 
applyCardPatches(uint32 globalId,int scriptType,uint16 hotspotId)915 void RivenSwitchCommand::applyCardPatches(uint32 globalId, int scriptType, uint16 hotspotId) {
916 	for (uint i = 0; i < _branches.size(); i++) {
917 		_branches[i].script->applyCardPatches(_vm, globalId, scriptType, hotspotId);
918 	}
919 }
920 
RivenStackChangeCommand(MohawkEngine_Riven * vm,uint16 stackId,uint32 globalCardId,bool byStackId,bool byStackCardId)921 RivenStackChangeCommand::RivenStackChangeCommand(MohawkEngine_Riven *vm, uint16 stackId, uint32 globalCardId,
922                                                  bool byStackId, bool byStackCardId) :
923 		RivenCommand(vm),
924 		_stackId(stackId),
925 		_cardId(globalCardId),
926 		_byStackId(byStackId),
927 		_byStackCardId(byStackCardId) {
928 
929 }
930 
~RivenStackChangeCommand()931 RivenStackChangeCommand::~RivenStackChangeCommand() {
932 
933 }
934 
createFromStream(MohawkEngine_Riven * vm,Common::ReadStream * stream)935 RivenStackChangeCommand *RivenStackChangeCommand::createFromStream(MohawkEngine_Riven *vm, Common::ReadStream *stream) {
936 	/* argumentsSize = */ stream->readUint16BE();
937 	uint16 stackId = stream->readUint16BE();
938 	uint32 globalCardId = stream->readUint32BE();
939 
940 	return new RivenStackChangeCommand(vm, stackId, globalCardId, false, false);
941 }
942 
execute()943 void RivenStackChangeCommand::execute() {
944 	debugC(kRivenDebugScript, "Running opcode: changeStack(%d, %d)", _stackId, _cardId);
945 
946 	uint16 stackID;
947 	if (_byStackId) {
948 		stackID = _stackId;
949 	} else {
950 		Common::String stackName = _vm->getStack()->getName(kStackNames, _stackId);
951 
952 		stackID = RivenStacks::getId(stackName.c_str());
953 		if (stackID == kStackUnknown) {
954 			error ("'%s' is not a stack name!", stackName.c_str());
955 		}
956 	}
957 
958 	_vm->changeToStack(stackID);
959 
960 	uint16 cardID;
961 	if (_byStackCardId) {
962 		cardID = _cardId;
963 	} else {
964 		cardID = _vm->getStack()->getCardStackId(_cardId);
965 	}
966 
967 	_vm->changeToCard(cardID);
968 }
969 
dump(byte tabs)970 void RivenStackChangeCommand::dump(byte tabs) {
971 	printTabs(tabs);
972 	debugN("changeStack(%d, %d);\n", _stackId, _cardId);
973 }
974 
getType() const975 RivenCommandType RivenStackChangeCommand::getType() const {
976 	return kRivenCommandChangeStack;
977 }
978 
RivenTimerCommand(MohawkEngine_Riven * vm,const Common::SharedPtr<RivenStack::TimerProc> & timerProc)979 RivenTimerCommand::RivenTimerCommand(MohawkEngine_Riven *vm, const Common::SharedPtr<RivenStack::TimerProc> &timerProc) :
980 	RivenCommand(vm),
981 	_timerProc(timerProc) {
982 
983 }
984 
execute()985 void RivenTimerCommand::execute() {
986 	(*_timerProc)();
987 }
988 
dump(byte tabs)989 void RivenTimerCommand::dump(byte tabs) {
990 	printTabs(tabs);
991 	debugN("doTimer();\n");
992 }
993 
getType() const994 RivenCommandType RivenTimerCommand::getType() const {
995 	return kRivenCommandTimer;
996 }
997 
998 } // End of namespace Mohawk
999