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 // Scripting module script function component
24 
25 #include "saga/saga.h"
26 
27 #include "saga/gfx.h"
28 #include "saga/actor.h"
29 #include "saga/animation.h"
30 #include "saga/console.h"
31 #include "saga/events.h"
32 #include "saga/font.h"
33 #include "saga/interface.h"
34 #include "saga/music.h"
35 #include "saga/itedata.h"
36 #include "saga/puzzle.h"
37 #include "saga/render.h"
38 #include "saga/sound.h"
39 #include "saga/sndres.h"
40 #include "saga/resource.h"
41 
42 #include "saga/script.h"
43 #include "saga/objectmap.h"
44 
45 #include "saga/scene.h"
46 #include "saga/isomap.h"
47 
48 #include "common/config-manager.h"
49 
50 namespace Saga {
51 
setupITEScriptFuncList()52 void Script::setupITEScriptFuncList() {
53 	static const ScriptFunctionDescription ITEScriptFunctionsList[ITE_SCRIPT_FUNCTION_MAX] = {
54 		OPCODE(sfPutString),
55 		OPCODE(sfWait),
56 		OPCODE(sfTakeObject),
57 		OPCODE(sfIsCarried),
58 		OPCODE(sfStatusBar),
59 		OPCODE(sfMainMode),
60 		OPCODE(sfScriptWalkTo),
61 		OPCODE(sfScriptDoAction),
62 		OPCODE(sfSetActorFacing),
63 		OPCODE(sfStartBgdAnim),
64 		OPCODE(sfStopBgdAnim),
65 		OPCODE(sfLockUser),
66 		OPCODE(sfPreDialog),
67 		OPCODE(sfKillActorThreads),
68 		OPCODE(sfFaceTowards),
69 		OPCODE(sfSetFollower),
70 		OPCODE(sfScriptGotoScene),
71 		OPCODE(sfSetObjImage),
72 		OPCODE(sfSetObjName),
73 		OPCODE(sfGetObjImage),
74 		OPCODE(sfGetNumber),
75 		OPCODE(sfScriptOpenDoor),
76 		OPCODE(sfScriptCloseDoor),
77 		OPCODE(sfSetBgdAnimSpeed),
78 		OPCODE(sfCycleColors),
79 		OPCODE(sfDoCenterActor),
80 		OPCODE(sfStartBgdAnimSpeed),
81 		OPCODE(sfScriptWalkToAsync),
82 		OPCODE(sfEnableZone),
83 		OPCODE(sfSetActorState),
84 		OPCODE(sfScriptMoveTo),
85 		OPCODE(sfSceneEq),
86 		OPCODE(sfDropObject),
87 		OPCODE(sfFinishBgdAnim),
88 		OPCODE(sfSwapActors),
89 		OPCODE(sfSimulSpeech),
90 		OPCODE(sfScriptWalk),
91 		OPCODE(sfCycleFrames),
92 		OPCODE(sfSetFrame),
93 		OPCODE(sfSetPortrait),
94 		OPCODE(sfSetProtagPortrait),
95 		OPCODE(sfChainBgdAnim),
96 		OPCODE(sfScriptSpecialWalk),
97 		OPCODE(sfPlaceActor),
98 		OPCODE(sfCheckUserInterrupt),
99 		OPCODE(sfScriptWalkRelative),
100 		OPCODE(sfScriptMoveRelative),
101 		OPCODE(sfSimulSpeech2),
102 		OPCODE(sfPlacard),
103 		OPCODE(sfPlacardOff),
104 		OPCODE(sfSetProtagState),
105 		OPCODE(sfResumeBgdAnim),
106 		OPCODE(sfThrowActor),
107 		OPCODE(sfWaitWalk),
108 		OPCODE(sfScriptSceneID),
109 		OPCODE(sfChangeActorScene),
110 		OPCODE(sfScriptClimb),
111 		OPCODE(sfSetDoorState),
112 		OPCODE(sfSetActorZ),
113 		OPCODE(sfScriptText),
114 		OPCODE(sfGetActorX),
115 		OPCODE(sfGetActorY),
116 		OPCODE(sfEraseDelta),
117 		OPCODE(sfPlayMusic),
118 		OPCODE(sfPickClimbOutPos),
119 		OPCODE(sfTossRif),
120 		OPCODE(sfShowControls),
121 		OPCODE(sfShowMap),
122 		OPCODE(sfPuzzleWon),
123 		OPCODE(sfEnableEscape),
124 		OPCODE(sfPlaySound),
125 		OPCODE(sfPlayLoopedSound),
126 		OPCODE(sfGetDeltaFrame),
127 		OPCODE(sfShowProtect),
128 		OPCODE(sfProtectResult),
129 		OPCODE(sfRand),
130 		OPCODE(sfFadeMusic),
131 		OPCODE(sfPlayVoice)
132 	};
133 
134 	_scriptFunctionsList = ITEScriptFunctionsList;
135 }
136 
137 // Script function #0 (0x00)
138 // Print a debugging message
sfPutString(SCRIPTFUNC_PARAMS)139 void Script::sfPutString(SCRIPTFUNC_PARAMS) {
140 	const char *str = thread->_strings->getString(thread->pop());
141 
142 	_vm->_console->debugPrintf("sfPutString: %s\n",str);
143 	debug(0, "sfPutString: %s", str);
144 }
145 
146 // Script function #1 (0x01) blocking
147 // Param1: time in ticks
sfWait(SCRIPTFUNC_PARAMS)148 void Script::sfWait(SCRIPTFUNC_PARAMS) {
149 	int16 time = thread->pop();
150 
151 	if (!_skipSpeeches) {
152 		thread->waitDelay(_vm->ticksToMSec(time)); // put thread to sleep
153 	}
154 }
155 
156 // Script function #2 (0x02)
sfTakeObject(SCRIPTFUNC_PARAMS)157 void Script::sfTakeObject(SCRIPTFUNC_PARAMS) {
158 	uint16 objectId = thread->pop();
159 	ObjectData *obj = _vm->_actor->getObj(objectId);
160 
161 	if (obj->_sceneNumber != ITE_SCENE_INV) {
162 		obj->_sceneNumber = ITE_SCENE_INV;
163 
164 		// Normally, when objects are picked up, they should always have the same
165 		// _spriteListResourceId as their _index value. Some don't in IHNM, so
166 		// we fix their sprite here
167 		// Fixes bugs #2057200 - "IHNM: Invisible inventory objects",
168 		// #1861126 - "IHNM: Crash when Gorrister cuts sheet in the mooring ring"
169 		// and some incorrect objects in the IHNM demo
170 		if (_vm->getGameId() == GID_IHNM)
171 			obj->_spriteListResourceId = obj->_index;
172 
173 		_vm->_interface->addToInventory(objectId);
174 	}
175 }
176 
177 // Script function #3 (0x03)
178 // Check if an object is carried.
sfIsCarried(SCRIPTFUNC_PARAMS)179 void Script::sfIsCarried(SCRIPTFUNC_PARAMS) {
180 	uint16 objectId = thread->pop();
181 	CommonObjectData *object;
182 
183 	if (_vm->_actor->validObjId(objectId)) {
184 		object = _vm->_actor->getObj(objectId);
185 		thread->_returnValue = (object->_sceneNumber == ITE_SCENE_INV) ? 1 : 0;
186 	} else {
187 		thread->_returnValue = 0;
188 	}
189 }
190 
191 // Script function #4 (0x04) nonblocking
192 // Set the command display to the specified text string
193 // Param1: dialogue index of string
sfStatusBar(SCRIPTFUNC_PARAMS)194 void Script::sfStatusBar(SCRIPTFUNC_PARAMS) {
195 	_vm->_interface->setStatusText(thread->_strings->getString(thread->pop()));
196 }
197 
198 // Script function #5 (0x05)
sfMainMode(SCRIPTFUNC_PARAMS)199 void Script::sfMainMode(SCRIPTFUNC_PARAMS) {
200 	_vm->_actor->_centerActor = _vm->_actor->_protagonist;
201 
202 	showVerb();
203 	_vm->_interface->activate();
204 	_vm->_interface->setMode(kPanelMain);
205 	// Sometimes, the active cutaway is cleared after this opcode is called,
206 	// resulting in an incorrect mode being set. An example is Ellen's chapter
207 	// in IHNM, when using the computer with the chaos trebler CD. Make sure
208 	// that the saved mode is kPanelMain, so that it won't get overwritten
209 	// by an incorrect stored mode
210 	_vm->_interface->rememberMode();
211 
212 	if (_vm->getGameId() == GID_ITE)
213 		setPointerVerb();
214 
215 	// The early Windows and Mac demos of ITE were non-interactive. In those demos,
216 	// the intro is shown and then when the first scene is shown, there's a dialog
217 	// thanking the user for playing the demo and asking him to buy the full game,
218 	// without allowing him to continue any further. The game data itself for these
219 	// demos does not contain any scripts for the first scene (i.e. there's no text
220 	// in the game data to look at Rif's silver medallion). Also, there are no more
221 	// scenes apart from the Grand Tournament scene. This opcode is called in those
222 	// demos, and I assume that its use there is to just show the popup window and
223 	// exit the game. Therefore, once this opcode is called in the older ITE demos,
224 	// exit the game. Known non-interactive demos are GID_ITE_MACDEMO1 and
225 	// GID_ITE_WINDEMO1
226 	if (_vm->_script->isNonInteractiveDemo())
227 		_vm->quitGame();
228 }
229 
230 // Script function #6 (0x06) blocking
231 // Param1: actor id
232 // Param2: actor x
233 // Param3: actor y
sfScriptWalkTo(SCRIPTFUNC_PARAMS)234 void Script::sfScriptWalkTo(SCRIPTFUNC_PARAMS) {
235 	uint16 actorId = thread->pop();
236 	ActorData *actor = _vm->_actor->getActor(actorId);
237 	Location actorLocation;
238 	actorLocation.x = thread->pop();
239 	actorLocation.y = thread->pop();
240 	actorLocation.z = actor->_location.z;
241 
242 	actor->_flags &= ~kFollower;
243 
244 	if (_vm->_actor->actorWalkTo(actorId, actorLocation)) {
245 		thread->waitWalk(actor);
246 	}
247 }
248 
249 // Script function #7 (0x07)
250 // Param1: actor id
251 // Param2: action
252 // Param3: theObject
253 // Param4: withObject
sfScriptDoAction(SCRIPTFUNC_PARAMS)254 void Script::sfScriptDoAction(SCRIPTFUNC_PARAMS) {
255 	uint16 objectId = thread->pop();
256 	uint16 action = thread->pop();
257 	uint16 theObject = thread->pop();
258 	uint16 withObject = thread->pop();
259 	int16 scriptEntryPointNumber;
260 	int16 moduleNumber;
261 	ActorData *actor;
262 	ObjectData *obj;
263 	const HitZone *hitZone;
264 	Event event;
265 
266 	// If the player uses an object and then immediately reuses that object
267 	// (without it being shown in the verb area), the object returned is wrong (0),
268 	// so we make it equal to the second object here.
269 	// Fixes bug #1861863 - "ITE: Crash when using Eeah with Eeah"
270 	if (theObject == 0 && objectId == 0 && withObject > 0)
271 		theObject = objectId = withObject;
272 
273 	switch (objectTypeId(objectId)) {
274 		case kGameObjectObject:
275 			obj = _vm->_actor->getObj(objectId);
276 			scriptEntryPointNumber = obj->_scriptEntrypointNumber;
277 			if (scriptEntryPointNumber <= 0) {
278 				return;
279 			}
280 			moduleNumber = 0;
281 			if (_vm->getGameId() == GID_IHNM)
282 				moduleNumber = _vm->_scene->getScriptModuleNumber();
283 			break;
284 		case kGameObjectActor:
285 			actor = _vm->_actor->getActor(objectId);
286 			scriptEntryPointNumber = actor->_scriptEntrypointNumber;
287 			if (scriptEntryPointNumber <= 0) {
288 				return;
289 			}
290 			if (actor->_flags & (kProtagonist | kFollower)) {
291 				moduleNumber = 0;
292 			} else {
293 				moduleNumber = _vm->_scene->getScriptModuleNumber();
294 			}
295 			if (_vm->getGameId() == GID_IHNM)
296 				moduleNumber = _vm->_scene->getScriptModuleNumber();
297 			break;
298 		case kGameObjectHitZone:
299 		case kGameObjectStepZone:
300 			if (objectTypeId(objectId) == kGameObjectHitZone)
301 				hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(objectId));
302 			else
303 				hitZone = _vm->_scene->_actionMap->getHitZone(objectIdToIndex(objectId));
304 
305 			if (hitZone == NULL)
306 				return;
307 
308 			scriptEntryPointNumber = hitZone->getScriptNumber();
309 			moduleNumber = _vm->_scene->getScriptModuleNumber();
310 			break;
311 		default:
312 			// Unknown case, do nothing
313 			warning("Script::sfScriptDoAction wrong object type 0x%X", objectId);
314 			return;
315 	}
316 
317 	event.type = kEvTOneshot;
318 	event.code = kScriptEvent;
319 	event.op = kEventExecNonBlocking;
320 	event.time = 0;
321 	event.param = moduleNumber;
322 	event.param2 = scriptEntryPointNumber;
323 	event.param3 = action;		// Action
324 	event.param4 = theObject;	// Object
325 	event.param5 = withObject;	// With Object
326 	event.param6 = objectId;
327 	_vm->_events->queue(event);
328 }
329 
330 // Script function #8 (0x08) nonblocking
331 // Param1: actor id
332 // Param2: actor orientation
sfSetActorFacing(SCRIPTFUNC_PARAMS)333 void Script::sfSetActorFacing(SCRIPTFUNC_PARAMS) {
334 	ActorData *actor = _vm->_actor->getActor(thread->pop());
335 	int actorDirection = thread->pop();
336 
337 	actor->_facingDirection = actor->_actionDirection = actorDirection;
338 	actor->_targetObject = ID_NOTHING;
339 }
340 
341 // Script function #9 (0x09)
sfStartBgdAnim(SCRIPTFUNC_PARAMS)342 void Script::sfStartBgdAnim(SCRIPTFUNC_PARAMS) {
343 	int16 animId = thread->pop();
344 	int16 cycles = thread->pop();
345 
346 	_vm->_anim->setCycles(animId, cycles);
347 	_vm->_anim->setFrameTime(animId, _vm->ticksToMSec(kRepeatSpeedTicks));
348 
349 	if (!_vm->_anim->isPlaying(animId))
350 		_vm->_anim->play(animId, 0);
351 
352 	debug(1, "sfStartBgdAnim(%d, %d)", animId, cycles);
353 }
354 
355 // Script function #10 (0x0A)
sfStopBgdAnim(SCRIPTFUNC_PARAMS)356 void Script::sfStopBgdAnim(SCRIPTFUNC_PARAMS) {
357 	int16 animId = thread->pop();
358 
359 	_vm->_anim->stop(animId);
360 
361 	debug(1, "sfStopBgdAnim(%d)", animId);
362 }
363 
364 // Script function #11 (0x0B) nonblocking
365 // If the parameter is true, the user interface is disabled while script
366 // continues to run. If the parameter is false, the user interface is
367 // reenabled.
368 // Param1: boolean
sfLockUser(SCRIPTFUNC_PARAMS)369 void Script::sfLockUser(SCRIPTFUNC_PARAMS) {
370 	int16 param = thread->pop();
371 
372 	if (param != 0) {
373 		_vm->_interface->deactivate();
374 	} else {
375 		_vm->_interface->activate();
376 	}
377 
378 	debug(1, "sfLockUser(%d)", param);
379 }
380 
381 // Script function #12 (0x0C)
382 // Disables mouse input, etc.
sfPreDialog(SCRIPTFUNC_PARAMS)383 void Script::sfPreDialog(SCRIPTFUNC_PARAMS) {
384 	_vm->_interface->deactivate();
385 	_vm->_interface->converseClear();
386 
387 	if (_vm->_interface->isInMainMode())
388 		_vm->_interface->setMode(kPanelConverse);
389 	else
390 		_vm->_interface->converseDisplayText();
391 
392 	_vm->_interface->setMode(kPanelNull);
393 }
394 
395 // Script function #13 (0x0D)
sfKillActorThreads(SCRIPTFUNC_PARAMS)396 void Script::sfKillActorThreads(SCRIPTFUNC_PARAMS) {
397 	ScriptThreadList::iterator threadIterator;
398 	int16 actorId = thread->pop();
399 
400 	for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) {
401 		ScriptThread &anotherThread = *threadIterator;
402 		if ((&anotherThread != thread) && (anotherThread._threadVars[kThreadVarActor] == actorId)) {
403 			anotherThread._flags &= ~kTFlagWaiting;
404 			anotherThread._flags |= kTFlagAborted;
405 		}
406 	}
407 }
408 
409 // Script function #14 (0x0E)
410 // Param1: actor id
411 // Param2: object id
sfFaceTowards(SCRIPTFUNC_PARAMS)412 void Script::sfFaceTowards(SCRIPTFUNC_PARAMS) {
413 	ActorData *actor = _vm->_actor->getActor(thread->pop());
414 	actor->_targetObject = thread->pop();
415 }
416 
417 // Script function #15 (0x0F)
418 // Param1: actor id
419 // Param2: target object
sfSetFollower(SCRIPTFUNC_PARAMS)420 void Script::sfSetFollower(SCRIPTFUNC_PARAMS) {
421 	int16 actorId = thread->pop();
422 	ActorData *actor = _vm->_actor->getActor(actorId);
423 	actor->_targetObject = thread->pop();
424 
425 	debug(1, "sfSetFollower(%d, %d) [%d]", actorId, actor->_targetObject, _vm->_actor->actorIdToIndex(actorId));
426 
427 	if (actor->_targetObject != ID_NOTHING) {
428 		actor->_flags |= kFollower;
429 		actor->_actorFlags &= ~kActorNoFollow;
430 	} else {
431 		actor->_flags &= ~kFollower;
432 	}
433 }
434 
435 // Script function #16 (0x10)
sfScriptGotoScene(SCRIPTFUNC_PARAMS)436 void Script::sfScriptGotoScene(SCRIPTFUNC_PARAMS) {
437 	int16 sceneNumber = thread->pop();
438 	int16 entrance = thread->pop();
439 
440 #ifdef ENABLE_IHNM
441 	if (_vm->getGameId() == GID_IHNM) {
442 		_vm->_gfx->setCursor(kCursorBusy);
443 	}
444 #endif
445 
446 	if (_vm->getGameId() == GID_ITE && sceneNumber < 0) {
447 		_vm->quitGame();
448 		return;
449 	}
450 
451 #ifdef ENABLE_IHNM
452 	if (_vm->getGameId() == GID_IHNM && sceneNumber == 0) {
453 		_vm->_scene->creditsScene();
454 		return;
455 	}
456 #endif
457 
458 	// It is possible to leave scene when converse panel is on,
459 	// particulalrly it may happen at Moneychanger tent. This
460 	// prevents this from happening.
461 	if (_vm->_interface->getMode() == kPanelConverse) {
462 		_vm->_interface->setMode(kPanelMain);
463 	}
464 
465 	// changeScene calls loadScene which calls setVerb. setVerb resets all pending objects and object flags
466 	if (sceneNumber == -1 && _vm->getGameId() == GID_IHNM) {
467 		// Return back to the character selection screen in IHNM
468 		_vm->_scene->changeScene(154, entrance, kTransitionFade, 8);
469 	} else {
470 		_vm->_scene->changeScene(sceneNumber, entrance, (sceneNumber == ITE_SCENE_ENDCREDIT1) ? kTransitionFade : kTransitionNoFade);
471 	}
472 
473 	if (_vm->_interface->getMode() == kPanelPlacard ||
474 		_vm->_interface->getMode() == kPanelCutaway ||
475 		_vm->_interface->getMode() == kPanelVideo) {
476 		_vm->_gfx->showCursor(true);
477 		_vm->_interface->setMode(kPanelMain);
478 	}
479 
480 	_pendingVerb = _vm->_script->getVerbType(kVerbNone);
481 	_currentObject[0] = _currentObject[1] = ID_NOTHING;
482 	showVerb();	// calls setStatusText("")
483 
484 #ifdef ENABLE_IHNM
485 	if (_vm->getGameId() == GID_IHNM) {
486 		// There are some cutaways which are not removed by game scripts, like the cutaway
487 		// after the intro of IHNM or the cutaway at the end of Ellen's part in the IHNM demo.
488 		// Clear any remaining cutaways here
489 		_vm->_anim->clearCutaway();
490 		_vm->_gfx->setCursor(kCursorNormal);
491 	}
492 #endif
493 
494 }
495 
496 // Script function #17 (0x11)
497 // Param1: object id
498 // Param2: sprite index
sfSetObjImage(SCRIPTFUNC_PARAMS)499 void Script::sfSetObjImage(SCRIPTFUNC_PARAMS) {
500 	uint16 objectId = thread->pop();
501 	uint16 spriteId = thread->pop();
502 
503 	_vm->_actor->getObj(objectId)->_spriteListResourceId = spriteId + (_vm->getGameId() == GID_ITE ? 9 : 0);
504 	_vm->_interface->refreshInventory();
505 }
506 
507 // Script function #18 (0x12)
508 // Param1: object id
509 // Param2: name index
sfSetObjName(SCRIPTFUNC_PARAMS)510 void Script::sfSetObjName(SCRIPTFUNC_PARAMS) {
511 	uint16 objectId = thread->pop();
512 	uint16 nameIdx = thread->pop();
513 	_vm->_actor->getObj(objectId)->_nameIndex = nameIdx;
514 }
515 
516 // Script function #19 (0x13)
517 // Param1: object id
sfGetObjImage(SCRIPTFUNC_PARAMS)518 void Script::sfGetObjImage(SCRIPTFUNC_PARAMS) {
519 	uint16 objectId = thread->pop();
520 	thread->_returnValue = _vm->_actor->getObj(objectId)->_spriteListResourceId - (_vm->getGameId() == GID_ITE ? 9 : 0);
521 }
522 
523 // Script function #20 (0x14)
sfGetNumber(SCRIPTFUNC_PARAMS)524 void Script::sfGetNumber(SCRIPTFUNC_PARAMS) {
525 	if (_vm->_interface->_statusTextInputState == kStatusTextInputFirstRun) {
526 		_vm->_interface->enterStatusString();
527 		thread->wait(kWaitTypeStatusTextInput);
528 		disContinue = true;
529 	} else {
530 		if (_vm->_interface->_statusTextInputState == kStatusTextInputAborted) {
531 			thread->_returnValue = -1;
532 		} else {
533 			thread->_returnValue = atoi(_vm->_interface->_statusTextInputString);
534 		}
535 
536 		_vm->_interface->_statusTextInputState = kStatusTextInputFirstRun;
537 	}
538 }
539 
540 // Script function #21 (0x15)
541 // Param1: door #
sfScriptOpenDoor(SCRIPTFUNC_PARAMS)542 void Script::sfScriptOpenDoor(SCRIPTFUNC_PARAMS) {
543 	int16 doorNumber = thread->pop();
544 
545 	if (_vm->_scene->getFlags() & kSceneFlagISO) {
546 		_vm->_isoMap->setTileDoorState(doorNumber, 1);
547 	} else {
548 		_vm->_scene->setDoorState(doorNumber, 0);
549 	}
550 }
551 
552 // Script function #22 (0x16)
553 // Param1: door #
sfScriptCloseDoor(SCRIPTFUNC_PARAMS)554 void Script::sfScriptCloseDoor(SCRIPTFUNC_PARAMS) {
555 	int16 doorNumber = thread->pop();
556 
557 	if (_vm->_scene->getFlags() & kSceneFlagISO) {
558 		_vm->_isoMap->setTileDoorState(doorNumber, 0);
559 	} else {
560 		_vm->_scene->setDoorState(doorNumber, 0xff);
561 	}
562 }
563 
564 // Script function #23 (0x17)
sfSetBgdAnimSpeed(SCRIPTFUNC_PARAMS)565 void Script::sfSetBgdAnimSpeed(SCRIPTFUNC_PARAMS) {
566 	int16 animId = thread->pop();
567 	int16 speed = thread->pop();
568 
569 	_vm->_anim->setFrameTime(animId, _vm->ticksToMSec(speed));
570 	debug(1, "sfSetBgdAnimSpeed(%d, %d)", animId, speed);
571 }
572 
573 // Script function #24 (0x18)
sfCycleColors(SCRIPTFUNC_PARAMS)574 void Script::sfCycleColors(SCRIPTFUNC_PARAMS) {
575 	sfStub("sfCycleColors", thread, nArgs);
576 
577 	error("Please, report this to sev");
578 }
579 
580 // Script function #25 (0x19)
581 // Param1: actor id
sfDoCenterActor(SCRIPTFUNC_PARAMS)582 void Script::sfDoCenterActor(SCRIPTFUNC_PARAMS) {
583 	_vm->_actor->_centerActor = _vm->_actor->getActor(thread->pop());
584 }
585 
586 // Script function #26 (0x1A) nonblocking
587 // Starts the specified animation
sfStartBgdAnimSpeed(SCRIPTFUNC_PARAMS)588 void Script::sfStartBgdAnimSpeed(SCRIPTFUNC_PARAMS) {
589 	int16 animId = thread->pop();
590 	int16 cycles = thread->pop();
591 	int16 speed = thread->pop();
592 
593 	_vm->_anim->setCycles(animId, cycles);
594 	_vm->_anim->setFrameTime(animId, _vm->ticksToMSec(speed));
595 
596 	if (!_vm->_anim->isPlaying(animId))
597 		_vm->_anim->play(animId, 0);
598 
599 	debug(1, "sfStartBgdAnimSpeed(%d, %d, %d)", animId, cycles, speed);
600 }
601 
602 // Script function #27 (0x1B) nonblocking
603 // Param1: actor id
604 // Param2: actor x
605 // Param3: actor y
sfScriptWalkToAsync(SCRIPTFUNC_PARAMS)606 void Script::sfScriptWalkToAsync(SCRIPTFUNC_PARAMS) {
607 	int16 actorId = thread->pop();
608 	ActorData *actor = _vm->_actor->getActor(actorId);
609 	Location actorLocation;
610 	actorLocation.x = thread->pop();
611 	actorLocation.y = thread->pop();
612 	actorLocation.z = actor->_location.z;
613 
614 	actor->_flags &= ~kFollower;
615 	_vm->_actor->actorWalkTo(actorId, actorLocation);
616 }
617 
618 // Script function #28 (0x1C)
sfEnableZone(SCRIPTFUNC_PARAMS)619 void Script::sfEnableZone(SCRIPTFUNC_PARAMS) {
620 	uint16 objectId = thread->pop();
621 	int16 flag = thread->pop();
622 	HitZone *hitZone;
623 
624 	if (objectTypeId(objectId) == 0)
625 		return;
626 	else if (objectTypeId(objectId) == kGameObjectHitZone)
627 		hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(objectId));
628 	else
629 		hitZone = _vm->_scene->_actionMap->getHitZone(objectIdToIndex(objectId));
630 
631 	if (hitZone == NULL)
632 		return;
633 
634 	if (flag) {
635 		hitZone->setFlag(kHitZoneEnabled);
636 	} else {
637 		hitZone->clearFlag(kHitZoneEnabled);
638 		_vm->_actor->_protagonist->_lastZone = NULL;
639 	}
640 }
641 
642 // Script function #29 (0x1D)
643 // Param1: actor id
644 // Param2: current action
sfSetActorState(SCRIPTFUNC_PARAMS)645 void Script::sfSetActorState(SCRIPTFUNC_PARAMS) {
646 	ActorData *actor = _vm->_actor->getActor(thread->pop());
647 	int currentAction = thread->pop();
648 
649 	if ((currentAction >= kActionWalkToPoint) && (currentAction <= kActionWalkToPoint)) {
650 		wakeUpActorThread(kWaitTypeWalk, actor);
651 	}
652 	actor->_currentAction = currentAction;
653 	actor->_actorFlags &= ~kActorBackwards;
654 }
655 
656 // Script function #30 (0x1E) nonblocking
657 // Param1: actor id
658 // Param2: actor pos x
659 // Param3: actor pos y
sfScriptMoveTo(SCRIPTFUNC_PARAMS)660 void Script::sfScriptMoveTo(SCRIPTFUNC_PARAMS) {
661 	int16 objectId = thread->pop();
662 	Location location;
663 	location.x = thread->pop();
664 	location.y = thread->pop();
665 	ActorData *actor;
666 	ObjectData *obj;
667 
668 	if (_vm->_actor->validActorId(objectId)) {
669 		actor = _vm->_actor->getActor(objectId);
670 
671 		actor->_location.x = location.x;
672 		actor->_location.y = location.y;
673 	} else {
674 		if (_vm->_actor->validObjId(objectId)) {
675 			obj = _vm->_actor->getObj(objectId);
676 			obj->_location.x = location.x;
677 			obj->_location.y = location.y;
678 		}
679 	}
680 }
681 
682 // Script function #31 (0x21)
683 // Param1: sceneNumber
sfSceneEq(SCRIPTFUNC_PARAMS)684 void Script::sfSceneEq(SCRIPTFUNC_PARAMS) {
685 	int16 sceneNumber = thread->pop();
686 
687 	if (_vm->_scene->getSceneResourceId(sceneNumber) == _vm->_scene->currentSceneResourceId())
688 		thread->_returnValue = 1;
689 	else
690 		thread->_returnValue = 0;
691 }
692 
693 // Script function #32 (0x20)
sfDropObject(SCRIPTFUNC_PARAMS)694 void Script::sfDropObject(SCRIPTFUNC_PARAMS) {
695 	uint16 objectId = thread->pop();
696 	ObjectData *obj = _vm->_actor->getObj(objectId);
697 	uint16 spriteId = thread->pop();
698 	obj->_location.x = thread->pop();
699 	obj->_location.y = thread->pop();
700 
701 	if (obj->_sceneNumber == ITE_SCENE_INV) {
702 		_vm->_interface->removeFromInventory(objectId);
703 	}
704 
705 	obj->_sceneNumber = _vm->_scene->currentSceneNumber();
706 
707 	if (_vm->getGameId() == GID_IHNM) {
708 		// Don't update _spriteListResourceId if spriteId is 0 and the object is not the
709 		// psychic profile. If spriteId == 0, the object's sprite is incorrectly reset.
710 		// This occurs in the IHNM demo and with some incorrect scripts in the retail version
711 		// of the game
712 		if (spriteId > 0 || (spriteId == 0 && objectId == IHNM_OBJ_PROFILE))
713 			obj->_spriteListResourceId = spriteId;
714 	} else {
715 		obj->_spriteListResourceId = spriteId + 9;
716 	}
717 }
718 
719 // Script function #33 (0x21)
sfFinishBgdAnim(SCRIPTFUNC_PARAMS)720 void Script::sfFinishBgdAnim(SCRIPTFUNC_PARAMS) {
721 	int16 animId = thread->pop();
722 
723 	_vm->_anim->finish(animId);
724 
725 	debug(1, "sfFinishBgdAnim(%d)", animId);
726 }
727 
728 // Script function #34 (0x22)
729 // Param1: actor id 1
730 // Param2: actor id 2
sfSwapActors(SCRIPTFUNC_PARAMS)731 void Script::sfSwapActors(SCRIPTFUNC_PARAMS) {
732 	int16 actorId1 = thread->pop();
733 	int16 actorId2 = thread->pop();
734 	ActorData *actor1 = _vm->_actor->getActor(actorId1);
735 	ActorData *actor2 = _vm->_actor->getActor(actorId2);
736 
737 	SWAP(actor1->_location, actor2->_location);
738 	SWAP(actor1->_lastZone, actor2->_lastZone);
739 
740 	if (actor1->_flags & kProtagonist) {
741 		actor1->_flags &= ~kProtagonist;
742 		actor2->_flags |= kProtagonist;
743 		_vm->_actor->_protagonist = _vm->_actor->_centerActor = actor2;
744 	} else if (actor2->_flags & kProtagonist) {
745 		actor2->_flags &= ~kProtagonist;
746 		actor1->_flags |= kProtagonist;
747 		_vm->_actor->_protagonist = _vm->_actor->_centerActor = actor1;
748 	}
749 }
750 
751 // Script function #35 (0x23)
752 // Param1: string rid
753 // Param2: actorscount
754 // Param3: actor id1
755 ///....
756 // Param3: actor idN
sfSimulSpeech(SCRIPTFUNC_PARAMS)757 void Script::sfSimulSpeech(SCRIPTFUNC_PARAMS) {
758 	int16 stringId = thread->pop();
759 	int16 actorsCount = thread->pop();
760 	int i;
761 	uint16 actorsIds[ACTOR_SPEECH_ACTORS_MAX];
762 	const char *string = thread->_strings->getString(stringId);
763 	int16 sampleResourceId = -1;
764 
765 	if (actorsCount > ACTOR_SPEECH_ACTORS_MAX)
766 		error("sfSimulSpeech actorsCount=0x%X exceed ACTOR_SPEECH_ACTORS_MAX", actorsCount);
767 
768 	for (i = 0; i < actorsCount; i++)
769 		actorsIds[i] = thread->pop();
770 
771 	if (!thread->_voiceLUT->empty()) {
772 		if (_vm->getGameId() == GID_IHNM && stringId >= 338) {
773 			sampleResourceId = -1;
774 		} else {
775 			sampleResourceId = (*thread->_voiceLUT)[stringId];
776 			if (sampleResourceId <= 0 || sampleResourceId > 4000)
777 				sampleResourceId = -1;
778 		}
779 	}
780 
781 	_vm->_actor->simulSpeech(string, actorsIds, actorsCount, 0, sampleResourceId);
782 	thread->wait(kWaitTypeSpeech);
783 }
784 
785 // Script function #36 (0x24) ?
786 // Param1: actor id
787 // Param2: actor x
788 // Param3: actor y
789 // Param4: actor walk flag
sfScriptWalk(SCRIPTFUNC_PARAMS)790 void Script::sfScriptWalk(SCRIPTFUNC_PARAMS) {
791 	int16 actorId = thread->pop();
792 	ActorData *actor = _vm->_actor->getActor(actorId);
793 	Location actorLocation;
794 	actorLocation.x = thread->pop();
795 	actorLocation.y = thread->pop();
796 	actorLocation.z = actor->_location.z;
797 	uint16 walkFlags = thread->pop();
798 
799 	actor->_flags &= ~kFollower;
800 	_vm->_actor->realLocation(actorLocation, ID_NOTHING, walkFlags);
801 
802 	if (_vm->_actor->actorWalkTo(actorId, actorLocation) && !(walkFlags & kWalkAsync)) {
803 		thread->waitWalk(actor);
804 	}
805 
806 	if (walkFlags & kWalkBackPedal) {
807 		actor->_actorFlags |= kActorBackwards;
808 	}
809 
810 	actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask);
811 }
812 
813 // Script function #37 (0x25) nonblocking
814 // Param1: actor id
815 // Param2: flags telling how to cycle the frames
816 // Param3: cycle frame number
817 // Param4: cycle delay
sfCycleFrames(SCRIPTFUNC_PARAMS)818 void Script::sfCycleFrames(SCRIPTFUNC_PARAMS) {
819 	ActorData *actor = _vm->_actor->getActor(thread->pop());
820 	int16 flags = thread->pop();
821 	int cycleFrameSequence = thread->pop();
822 	int cycleDelay = thread->pop();
823 
824 	if (flags & kCyclePong) {
825 		actor->_currentAction = kActionPongFrames;
826 	} else {
827 		actor->_currentAction = kActionCycleFrames;
828 	}
829 
830 	actor->_actorFlags &= ~(kActorContinuous | kActorRandom | kActorBackwards);
831 
832 	if (!(flags & kCycleOnce)) {
833 		actor->_actorFlags |= kActorContinuous;
834 	}
835 	if (flags & kCycleRandom) {
836 		actor->_actorFlags |= kActorRandom;
837 	}
838 	if (flags & kCycleReverse) {
839 		if (_vm->getGameId() == GID_IHNM &&
840 			_vm->_scene->currentChapterNumber() == 2 && _vm->_scene->currentSceneNumber() == 41) {
841 			// WORKAROUND: Prevent Benny from walking backwards after talking to the child via the monitor. This
842 			// occurs in the original as well, and is fixed by not setting the kActorBackwards flag at this point
843 		} else {
844 			actor->_actorFlags |= kActorBackwards;
845 		}
846 	}
847 
848 	actor->_cycleFrameSequence = cycleFrameSequence;
849 	actor->_cycleTimeCount = 0;
850 	actor->_cycleDelay = cycleDelay;
851 	actor->_actionCycle = 0;
852 }
853 
854 // Script function #38 (0x26) nonblocking
855 // Param1: actor id
856 // Param2: frame type
857 // Param3: frame offset
sfSetFrame(SCRIPTFUNC_PARAMS)858 void Script::sfSetFrame(SCRIPTFUNC_PARAMS) {
859 	int16 actorId = thread->pop();
860 	ActorData *actor = _vm->_actor->getActor(actorId);
861 	int frameType = thread->pop();
862 	int frameOffset = thread->pop();
863 	ActorFrameRange *frameRange = _vm->_actor->getActorFrameRange(actorId, frameType);
864 
865 	actor->_frameNumber = frameRange->frameIndex + frameOffset;
866 
867 	if (actor->_currentAction != kActionFall) {
868 		actor->_currentAction = kActionFreeze;
869 	}
870 }
871 
872 // Script function #39 (0x27)
873 // Sets the right-hand portrait
sfSetPortrait(SCRIPTFUNC_PARAMS)874 void Script::sfSetPortrait(SCRIPTFUNC_PARAMS) {
875 	_vm->_interface->setRightPortrait(thread->pop());
876 }
877 
878 // Script function #40 (0x28)
879 // Sets the left-hand portrait
sfSetProtagPortrait(SCRIPTFUNC_PARAMS)880 void Script::sfSetProtagPortrait(SCRIPTFUNC_PARAMS) {
881 	_vm->_interface->setLeftPortrait(thread->pop());
882 }
883 
884 // Script function #41 (0x29) nonblocking
885 // Links the specified animations for playback
886 
887 // Param1: ?
888 // Param2: total linked frame count
889 // Param3: animation id link target
890 // Param4: animation id link source
sfChainBgdAnim(SCRIPTFUNC_PARAMS)891 void Script::sfChainBgdAnim(SCRIPTFUNC_PARAMS) {
892 	int16 animId1 = thread->pop();
893 	int16 animId = thread->pop();
894 	int16 cycles = thread->pop();
895 	int16 speed = thread->pop();
896 
897 	if (speed >= 0) {
898 		_vm->_anim->setCycles(animId, cycles);
899 		_vm->_anim->stop(animId);
900 		_vm->_anim->setFrameTime(animId, _vm->ticksToMSec(speed));
901 	}
902 
903 	_vm->_anim->link(animId1, animId);
904 	debug(1, "sfChainBgdAnim(%d, %d, %d, %d)", animId1, animId, cycles, speed);
905 }
906 
907 // Script function #42 (0x2A)
908 // Param1: actor id
909 // Param2: actor x
910 // Param3: actor y
911 // Param4: frame seq
sfScriptSpecialWalk(SCRIPTFUNC_PARAMS)912 void Script::sfScriptSpecialWalk(SCRIPTFUNC_PARAMS) {
913 	int16 actorId = thread->pop();
914 	ActorData *actor = _vm->_actor->getActor(actorId);
915 	Location actorLocation;
916 	actorLocation.x = thread->pop();
917 	actorLocation.y = thread->pop();
918 	actorLocation.z = actor->_location.z;
919 	int16 walkFrameSequence = thread->pop();
920 
921 	_vm->_actor->actorWalkTo(actorId, actorLocation);
922 	actor->_walkFrameSequence = walkFrameSequence;
923 }
924 
925 // Script function #43 (0x2B) nonblocking
926 // Param1: actor id
927 // Param2: actor x
928 // Param3: actor y
929 // Param4: actor direction
930 // Param5: actor action
931 // Param6: actor frame number
sfPlaceActor(SCRIPTFUNC_PARAMS)932 void Script::sfPlaceActor(SCRIPTFUNC_PARAMS) {
933 	int16 actorId = thread->pop();
934 	ActorData *actor = _vm->_actor->getActor(actorId);
935 	actor->_location.x = thread->pop();
936 	actor->_location.y = thread->pop();
937 	actor->_facingDirection = actor->_actionDirection = thread->pop();
938 	int frameType = thread->pop();
939 	int frameOffset = thread->pop();
940 	ActorFrameRange *frameRange;
941 
942 	debug(1, "sfPlaceActor(id = 0x%X, x=%d, y=%d, dir=%d, frameType=%d, frameOffset=%d)", actorId, actor->_location.x,
943 		  actor->_location.y, actor->_facingDirection, frameType, frameOffset);
944 
945 	if (frameType >= 0) {
946 		frameRange = _vm->_actor->getActorFrameRange(actorId, frameType);
947 		actor->_frameNumber = frameRange->frameIndex + frameOffset;
948 		actor->_currentAction = kActionFreeze;
949 	} else {
950 		actor->_currentAction = kActionWait;
951 	}
952 
953 	actor->_targetObject = ID_NOTHING;
954 }
955 
956 // Script function #44 (0x2C) nonblocking
957 // Checks to see if the user has interrupted a currently playing
958 // game cinematic. Pushes a zero or positive value if the game
959 // has not been interrupted.
sfCheckUserInterrupt(SCRIPTFUNC_PARAMS)960 void Script::sfCheckUserInterrupt(SCRIPTFUNC_PARAMS) {
961 	thread->_returnValue = (_skipSpeeches == true);
962 }
963 
964 // Script function #45 (0x2D)
965 // Param1: actor id
966 // Param2: object id
967 // Param3: actor x
968 // Param4: actor y
969 // Param5: actor walk flag
sfScriptWalkRelative(SCRIPTFUNC_PARAMS)970 void Script::sfScriptWalkRelative(SCRIPTFUNC_PARAMS) {
971 	int16 actorId = thread->pop();
972 	ActorData *actor = _vm->_actor->getActor(actorId);
973 	int16 objectId = thread->pop();
974 	Location actorLocation;
975 	actorLocation.x = thread->pop();
976 	actorLocation.y = thread->pop();
977 	actorLocation.z = actor->_location.z;
978 	uint16 walkFlags = thread->pop();
979 
980 	actor->_flags &= ~kFollower;
981 	_vm->_actor->realLocation(actorLocation, objectId, walkFlags);
982 
983 	if (_vm->_actor->actorWalkTo(actorId, actorLocation) && !(walkFlags & kWalkAsync)) {
984 		thread->waitWalk(actor);
985 	}
986 
987 	if (walkFlags & kWalkBackPedal) {
988 		actor->_actorFlags |= kActorBackwards;
989 	}
990 
991 	actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask);
992 }
993 
994 // Script function #46 (0x2E)
995 // Param1: actor id
996 // Param2: object id
997 // Param3: actor x
998 // Param4: actor y
999 // Param5: actor walk flag
sfScriptMoveRelative(SCRIPTFUNC_PARAMS)1000 void Script::sfScriptMoveRelative(SCRIPTFUNC_PARAMS) {
1001 	ActorData *actor = _vm->_actor->getActor(thread->pop());
1002 	int16 objectId = thread->pop();
1003 	Location actorLocation;
1004 	actorLocation.x = thread->pop();
1005 	actorLocation.y = thread->pop();
1006 	actorLocation.z = actor->_location.z;
1007 	uint16 walkFlags = thread->pop();
1008 
1009 	_vm->_actor->realLocation(actorLocation, objectId, walkFlags);
1010 
1011 	actor->_location = actorLocation;
1012 	actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask);
1013 }
1014 
1015 // Script function #47 (0x2F)
sfSimulSpeech2(SCRIPTFUNC_PARAMS)1016 void Script::sfSimulSpeech2(SCRIPTFUNC_PARAMS) {
1017 	int16 stringId = thread->pop();
1018 	const char *string = thread->_strings->getString(stringId);
1019 	int16 actorsCount = thread->pop();
1020 	int16 speechFlags = thread->pop();
1021 	int i;
1022 	uint16 actorsIds[ACTOR_SPEECH_ACTORS_MAX];
1023 	int16 sampleResourceId = -1;
1024 
1025 	if (actorsCount > ACTOR_SPEECH_ACTORS_MAX)
1026 		error("sfSimulSpeech2 actorsCount=0x%X exceed ACTOR_SPEECH_ACTORS_MAX", actorsCount);
1027 
1028 	for (i = 0; i < actorsCount; i++)
1029 		actorsIds[i] = thread->pop();
1030 
1031 	if (!thread->_voiceLUT->empty()) {
1032 		sampleResourceId = (*thread->_voiceLUT)[stringId];
1033 		if (sampleResourceId <= 0 || sampleResourceId > 4000)
1034 			sampleResourceId = -1;
1035 	}
1036 
1037 	_vm->_actor->simulSpeech(string, actorsIds, actorsCount, speechFlags, sampleResourceId);
1038 	thread->wait(kWaitTypeSpeech);
1039 }
1040 
1041 
1042 // Script function #48 (0x30)
1043 // Param1: string rid
sfPlacard(SCRIPTFUNC_PARAMS)1044 void Script::sfPlacard(SCRIPTFUNC_PARAMS) {
1045 	int stringId = thread->pop();
1046 	static PalEntry cur_pal[PAL_ENTRIES];
1047 	PalEntry *pal;
1048 	Event event;
1049 	EventColumns *eventColumns;
1050 
1051 	thread->wait(kWaitTypePlacard);
1052 
1053 	_vm->_interface->rememberMode();
1054 	_vm->_interface->setMode(kPanelPlacard);
1055 
1056 	event.type = kEvTOneshot;
1057 	event.code = kCursorEvent;
1058 	event.op = kEventHide;
1059 	eventColumns = _vm->_events->queue(event);
1060 
1061 	_vm->_interface->setFadeMode(kFadeOut);
1062 
1063 	// Fade to black out
1064 	_vm->_gfx->getCurrentPal(cur_pal);
1065 	event.type = kEvTImmediate;
1066 	event.code = kPalEvent;
1067 	event.op = kEventPalToBlack;
1068 	event.time = 0;
1069 	event.duration = kNormalFadeDuration;
1070 	event.data = cur_pal;
1071 	_vm->_events->chain(eventColumns, event);
1072 
1073 	// set fade mode
1074 	event.type = kEvTImmediate;
1075 	event.code = kInterfaceEvent;
1076 	event.op = kEventSetFadeMode;
1077 	event.param = kNoFade;
1078 	event.time = 0;
1079 	event.duration = 0;
1080 	_vm->_events->chain(eventColumns, event);
1081 
1082 	event.type = kEvTOneshot;
1083 	event.code = kInterfaceEvent;
1084 	event.op = kEventClearStatus;
1085 	_vm->_events->chain(eventColumns, event);
1086 
1087 	event.type = kEvTOneshot;
1088 	event.code = kGraphicsEvent;
1089 	event.op = kEventFillRect;
1090 	event.param = 138;
1091 	event.param2 = 0;
1092 	event.param3 = _vm->_scene->getHeight();
1093 	event.param4 = 0;
1094 	event.param5 = _vm->getDisplayInfo().width;
1095 	_vm->_events->chain(eventColumns, event);
1096 
1097 	// Put the text in the center of the viewport, assuming it will fit on
1098 	// one line. If we cannot make that assumption we'll need to extend
1099 	// the text drawing function so that it can center text around a point.
1100 	// It doesn't end up in exactly the same spot as the original did it,
1101 	// but it's close enough for now at least.
1102 
1103 	TextListEntry textEntry;
1104 
1105 	textEntry.knownColor = kKnownColorBrightWhite;
1106 	textEntry.effectKnownColor = kKnownColorBlack;
1107 	textEntry.point.x = _vm->getDisplayInfo().width / 2;
1108 	textEntry.point.y = (_vm->_scene->getHeight() - _vm->_font->getHeight(kKnownFontMedium)) / 2;
1109 	textEntry.font = kKnownFontMedium;
1110 	textEntry.flags = (FontEffectFlags)(kFontOutline | kFontCentered);
1111 	textEntry.text = thread->_strings->getString(stringId);
1112 
1113 	_placardTextEntry = _vm->_scene->_textList.addEntry(textEntry);
1114 
1115 	event.type = kEvTOneshot;
1116 	event.code = kTextEvent;
1117 	event.op = kEventDisplay;
1118 	event.data = _placardTextEntry;
1119 	_vm->_events->chain(eventColumns, event);
1120 
1121 	_vm->_scene->getBGPal(pal);
1122 	event.type = kEvTImmediate;
1123 	event.code = kPalEvent;
1124 	event.op = kEventBlackToPal;
1125 	event.time = 0;
1126 	event.duration = kNormalFadeDuration;
1127 	event.data = pal;
1128 	_vm->_events->chain(eventColumns, event);
1129 
1130 	event.type = kEvTOneshot;
1131 	event.code = kScriptEvent;
1132 	event.op = kEventThreadWake;
1133 	event.param = kWaitTypePlacard;
1134 	_vm->_events->chain(eventColumns, event);
1135 
1136 }
1137 
1138 // Script function #49 (0x31)
sfPlacardOff(SCRIPTFUNC_PARAMS)1139 void Script::sfPlacardOff(SCRIPTFUNC_PARAMS) {
1140 	thread->wait(kWaitTypePlacard);
1141 
1142 	_vm->_scene->clearPlacard();
1143 }
1144 
1145 // Script function #50 (0x32)
sfSetProtagState(SCRIPTFUNC_PARAMS)1146 void Script::sfSetProtagState(SCRIPTFUNC_PARAMS) {
1147 	_vm->_actor->setProtagState(thread->pop());
1148 }
1149 
1150 // Script function #51 (0x33)
sfResumeBgdAnim(SCRIPTFUNC_PARAMS)1151 void Script::sfResumeBgdAnim(SCRIPTFUNC_PARAMS) {
1152 	int16 animId = thread->pop();
1153 	int16 cycles = thread->pop();
1154 
1155 	_vm->_anim->resume(animId, cycles);
1156 	debug(1, "sfResumeBgdAnimSpeed(%d, %d)", animId, cycles);
1157 
1158 }
1159 
1160 // Script function #52 (0x34)
1161 // Param1: actor id
1162 // Param2: x
1163 // Param3: y
1164 // Param4: unknown
1165 // Param5: actionCycle
1166 // Param6: flags
sfThrowActor(SCRIPTFUNC_PARAMS)1167 void Script::sfThrowActor(SCRIPTFUNC_PARAMS) {
1168 	ActorData *actor = _vm->_actor->getActor(thread->pop());
1169 	actor->_finalTarget.x = thread->pop();
1170 	actor->_finalTarget.y = thread->pop();
1171 	actor->_finalTarget.z = actor->_location.z;
1172 	thread->pop();	// not used
1173 	int32 actionCycle = thread->pop();
1174 	int16 flags = thread->pop();
1175 
1176 	actor->_currentAction = kActionFall;
1177 	actor->_actionCycle = actionCycle;
1178 	actor->_fallAcceleration	= -20;
1179 	actor->_fallVelocity = - (actor->_fallAcceleration * actor->_actionCycle) / 2;
1180 	actor->_fallPosition = actor->_location.z << 4;
1181 
1182 	actor->_actionCycle--;
1183 	if (!(flags & kWalkAsync)) {
1184 		thread->waitWalk(actor);
1185 	}
1186 }
1187 
1188 // Script function #53 (0x35)
1189 // Param1: actor id
1190 // Param2: target object
sfWaitWalk(SCRIPTFUNC_PARAMS)1191 void Script::sfWaitWalk(SCRIPTFUNC_PARAMS) {
1192 	ActorData *actor = _vm->_actor->getActor(thread->pop());
1193 
1194 	if ((actor->_currentAction == kActionWalkToPoint) ||
1195 		(actor->_currentAction == kActionWalkToLink) ||
1196 		(actor->_currentAction == kActionFall)) {
1197 			thread->waitWalk(actor);
1198 	}
1199 }
1200 
1201 // Script function #54 (0x36)
sfScriptSceneID(SCRIPTFUNC_PARAMS)1202 void Script::sfScriptSceneID(SCRIPTFUNC_PARAMS) {
1203 	thread->_returnValue = _vm->_scene->currentSceneNumber();
1204 }
1205 
1206 // Script function #55 (0x37)
1207 // Param1: actor id
1208 // Param2: scene number
sfChangeActorScene(SCRIPTFUNC_PARAMS)1209 void Script::sfChangeActorScene(SCRIPTFUNC_PARAMS) {
1210 	ActorData *actor = _vm->_actor->getActor(thread->pop());
1211 	actor->_sceneNumber = thread->pop();
1212 }
1213 
1214 // Script function #56 (0x38)
1215 // Param1: actor id
1216 // Param2: z
1217 // Param3: frame seq
1218 // Param4: flags
sfScriptClimb(SCRIPTFUNC_PARAMS)1219 void Script::sfScriptClimb(SCRIPTFUNC_PARAMS) {
1220 	ActorData *actor = _vm->_actor->getActor(thread->pop());
1221 	actor->_finalTarget.z = thread->pop();
1222 	int cycleFrameSequence = thread->pop();
1223 	uint16 flags = thread->pop();
1224 
1225 	actor->_flags &= ~kFollower;
1226 	actor->_actionCycle = 1;
1227 	actor->_cycleFrameSequence = cycleFrameSequence;
1228 	actor->_currentAction = kActionClimb;
1229 	if (!(flags & kWalkAsync)) {
1230 		thread->waitWalk(actor);
1231 	}
1232 }
1233 
1234 // Script function #57 (0x39)
1235 // Param1: door #
1236 // Param2: door state
sfSetDoorState(SCRIPTFUNC_PARAMS)1237 void Script::sfSetDoorState(SCRIPTFUNC_PARAMS) {
1238 	int16 doorNumber = thread->pop();
1239 	int16 doorState = thread->pop();
1240 
1241 	if (_vm->_scene->getFlags() & kSceneFlagISO) {
1242 		_vm->_isoMap->setTileDoorState(doorNumber, doorState);
1243 	} else {
1244 		_vm->_scene->setDoorState(doorNumber, doorState);
1245 	}
1246 }
1247 
1248 // Script function #58 (0x3A)
1249 // Param1: actor id
1250 // Param2: z
sfSetActorZ(SCRIPTFUNC_PARAMS)1251 void Script::sfSetActorZ(SCRIPTFUNC_PARAMS) {
1252 	int16 objectId = thread->pop();
1253 	int16 z = thread->pop();
1254 	ActorData *actor;
1255 	ObjectData *obj;
1256 
1257 	if (_vm->_actor->validActorId(objectId)) {
1258 		actor = _vm->_actor->getActor(objectId);
1259 		actor->_location.z = z;
1260 	} else {
1261 		if (_vm->_actor->validObjId(objectId)) {
1262 			obj = _vm->_actor->getObj(objectId);
1263 			obj->_location.z = z;
1264 		}
1265 	}
1266 }
1267 
1268 // Script function #59 (0x3B)
1269 // Param1: stringId
1270 // Param2: flags
1271 // Param3: color
1272 // Param4: x
1273 // Param5: y
sfScriptText(SCRIPTFUNC_PARAMS)1274 void Script::sfScriptText(SCRIPTFUNC_PARAMS) {
1275 	const char *text = thread->_strings->getString(thread->pop());
1276 	int16 flags = thread->pop();
1277 	int color = thread->pop();
1278 	Point point;
1279 	point.x = thread->pop();
1280 	point.y = thread->pop();
1281 	Rect rect;
1282 	int width = _vm->_font->getStringWidth(kKnownFontScript, text, 0, kFontOutline);
1283 
1284 	rect.top = point.y - 6;
1285 	rect.setHeight(12);
1286 	rect.left = point.x - width / 2;
1287 	rect.setWidth(width);
1288 
1289 	_vm->_actor->setSpeechColor(color, _vm->KnownColor2ColorId(kKnownColorBlack));
1290 	_vm->_actor->nonActorSpeech(rect, &text, 1, -1, flags);
1291 }
1292 
1293 // Script function #60 (0x3C)
1294 // Param1: actor id
sfGetActorX(SCRIPTFUNC_PARAMS)1295 void Script::sfGetActorX(SCRIPTFUNC_PARAMS) {
1296 	ActorData *actor = _vm->_actor->getActor(thread->pop());
1297 
1298 	thread->_returnValue = actor->_location.x >> 2;
1299 }
1300 
1301 // Script function #61 (0x3D)
1302 // Param1: actor id
sfGetActorY(SCRIPTFUNC_PARAMS)1303 void Script::sfGetActorY(SCRIPTFUNC_PARAMS) {
1304 	ActorData *actor = _vm->_actor->getActor(thread->pop());
1305 
1306 	thread->_returnValue = actor->_location.y >> 2;
1307 }
1308 
1309 // Script function #62 (0x3E)
sfEraseDelta(SCRIPTFUNC_PARAMS)1310 void Script::sfEraseDelta(SCRIPTFUNC_PARAMS) {
1311 	BGInfo backGroundInfo;
1312 	_vm->_scene->getBGInfo(backGroundInfo);
1313 	_vm->_render->getBackGroundSurface()->blit(backGroundInfo.bounds, backGroundInfo.buffer);
1314 	_vm->_render->addDirtyRect(backGroundInfo.bounds);
1315 }
1316 
1317 // Script function #63 (0x3F)
sfPlayMusic(SCRIPTFUNC_PARAMS)1318 void Script::sfPlayMusic(SCRIPTFUNC_PARAMS) {
1319 	if (_vm->getGameId() == GID_ITE) {
1320 		int16 param = thread->pop() + 9;
1321 
1322 		if (param >= 9 && param <= 34) {
1323 			_vm->_music->setVolume(_vm->_musicVolume, 1);
1324 			_vm->_music->play(param);
1325 		} else {
1326 			_vm->_music->stop();
1327 		}
1328 #ifdef ENABLE_IHNM
1329 	} else if (_vm->getGameId() == GID_IHNM) {
1330 		int16 param1 = thread->pop();
1331 		int16 param2 = thread->pop();
1332 
1333 		if (param1 < 0) {
1334 			_vm->_music->stop();
1335 			return;
1336 		}
1337 
1338 		if (uint(param1) >= _vm->_music->_songTable.size()) {
1339 			warning("sfPlayMusic: Wrong song number (%d > %d)", param1, _vm->_music->_songTable.size() - 1);
1340 		} else {
1341 			_vm->_music->setVolume(_vm->_musicVolume, 1);
1342 			_vm->_music->play(_vm->_music->_songTable[param1], param2 ? MUSIC_LOOP : MUSIC_NORMAL);
1343 			if (!_vm->_scene->haveChapterPointsChanged()) {
1344 				_vm->_scene->setCurrentMusicTrack(param1);
1345 				_vm->_scene->setCurrentMusicRepeat(param2);
1346 			} else {
1347 				// Don't save this music track when saving in IHNM
1348 				_vm->_scene->setChapterPointsChanged(false);
1349 			}
1350 		}
1351 #endif
1352 	}
1353 }
1354 
1355 // Script function #64 (0x40)
sfPickClimbOutPos(SCRIPTFUNC_PARAMS)1356 void Script::sfPickClimbOutPos(SCRIPTFUNC_PARAMS) {
1357 	int16 u, v, t;
1358 	ActorData *protagonist = _vm->_actor->_protagonist;
1359 	while (true) {
1360 
1361 		u = (_vm->_rnd.getRandomNumber(63) & 63) + 40;
1362 		v = (_vm->_rnd.getRandomNumber(63) & 63) + 40;
1363 		t = _vm->_isoMap->getTileIndex(u, v, 6);
1364 		if (t == 65) {
1365 			protagonist->_location.u() = (u << 4) + 4;
1366 			protagonist->_location.v() = (v << 4) + 4;
1367 			protagonist->_location.z = 48;
1368 			break;
1369 		}
1370 
1371 	}
1372 }
1373 
1374 // Script function #65 (0x41)
sfTossRif(SCRIPTFUNC_PARAMS)1375 void Script::sfTossRif(SCRIPTFUNC_PARAMS) {
1376 	uint16 direction;
1377 	ActorData *protagonist = _vm->_actor->_protagonist;
1378 	int16 uc = protagonist->_location.u() >> 4;
1379 	int16 vc = protagonist->_location.v() >> 4;
1380 
1381 	if (_vm->_isoMap->findNearestChasm(uc, vc, direction)) {
1382 		uc <<= 4;
1383 		vc <<= 4;
1384 		protagonist->_facingDirection = direction;
1385 
1386 		protagonist->_finalTarget.u() = uc;
1387 		protagonist->_finalTarget.v() = vc;
1388 		protagonist->_finalTarget.z = -40;
1389 		protagonist->_currentAction = kActionFall;
1390 		protagonist->_actionCycle = 24;
1391 		protagonist->_fallAcceleration = - 20;
1392 		protagonist->_fallVelocity = - (protagonist->_fallAcceleration * 16) / 2 - (44 / 12);
1393 		protagonist->_fallPosition = protagonist->_location.z << 4;
1394 		protagonist->_actionCycle--;
1395 	}
1396 }
1397 
1398 // Script function #66 (0x42)
sfShowControls(SCRIPTFUNC_PARAMS)1399 void Script::sfShowControls(SCRIPTFUNC_PARAMS) {
1400 	// It has zero implementation in Win rerelase, and in DOS
1401 	// release it deals with video ports.
1402 }
1403 
1404 // Script function #67 (0x43)
sfShowMap(SCRIPTFUNC_PARAMS)1405 void Script::sfShowMap(SCRIPTFUNC_PARAMS) {
1406 	_vm->_interface->setMode(kPanelMap);
1407 }
1408 
1409 // Script function #68 (0x44)
sfPuzzleWon(SCRIPTFUNC_PARAMS)1410 void Script::sfPuzzleWon(SCRIPTFUNC_PARAMS) {
1411 	thread->_returnValue = _vm->_puzzle->isSolved();
1412 }
1413 
1414 // Script function #69 (0x45)
sfEnableEscape(SCRIPTFUNC_PARAMS)1415 void Script::sfEnableEscape(SCRIPTFUNC_PARAMS) {
1416 	if (thread->pop())
1417 		_abortEnabled = true;
1418 	else {
1419 		_skipSpeeches = false;
1420 		_abortEnabled = false;
1421 	}
1422 }
1423 
1424 // Script function #70 (0x46)
sfPlaySound(SCRIPTFUNC_PARAMS)1425 void Script::sfPlaySound(SCRIPTFUNC_PARAMS) {
1426 	int16 param = thread->pop();
1427 	int res;
1428 
1429 	if (uint(param) < _vm->_sndRes->_fxTable.size()) {
1430 		res = _vm->_sndRes->_fxTable[param].res;
1431 		if (_vm->getGameId() == GID_ITE && !(_vm->getFeatures() & GF_ITE_FLOPPY))
1432 			res -= 14;
1433 		_vm->_sndRes->playSound(res, _vm->_sndRes->_fxTable[param].vol, false);
1434 	} else {
1435 		_vm->_sound->stopSound();
1436 	}
1437 }
1438 
1439 // Script function #71 (0x47)
sfPlayLoopedSound(SCRIPTFUNC_PARAMS)1440 void Script::sfPlayLoopedSound(SCRIPTFUNC_PARAMS) {
1441 	int16 param = thread->pop();
1442 	int res;
1443 
1444 	if (uint(param) < _vm->_sndRes->_fxTable.size()) {
1445 		res = _vm->_sndRes->_fxTable[param].res;
1446 		if (_vm->getGameId() == GID_ITE && !(_vm->getFeatures() & GF_ITE_FLOPPY))
1447 			res -= 14;
1448 
1449 		_vm->_sndRes->playSound(res, _vm->_sndRes->_fxTable[param].vol, true);
1450 	} else {
1451 		_vm->_sound->stopSound();
1452 	}
1453 
1454 	debug(1, "sfPlayLoopedSound(%d)", param);
1455 }
1456 
1457 // Script function #72 (0x48)
1458 // Param1: animation id
sfGetDeltaFrame(SCRIPTFUNC_PARAMS)1459 void Script::sfGetDeltaFrame(SCRIPTFUNC_PARAMS) {
1460 	thread->_returnValue = _vm->_anim->getCurrentFrame((uint16)thread->pop());
1461 }
1462 
1463 // Script function #73 (0x49)
sfShowProtect(SCRIPTFUNC_PARAMS)1464 void Script::sfShowProtect(SCRIPTFUNC_PARAMS) {
1465 	if (_vm->_copyProtection) {
1466 		thread->wait(kWaitTypeRequest);
1467 
1468 		_vm->_interface->setMode(kPanelProtect);
1469 	}
1470 }
1471 
1472 // Script function #74 (0x4A)
sfProtectResult(SCRIPTFUNC_PARAMS)1473 void Script::sfProtectResult(SCRIPTFUNC_PARAMS) {
1474 	if (_vm->_copyProtection) {
1475 		thread->_returnValue = _vm->_interface->getProtectHash();
1476 	} else {
1477 		//cheating
1478 		int protectHash = thread->pop();
1479 		thread->push(protectHash);
1480 		thread->_returnValue = protectHash;
1481 	}
1482 }
1483 
1484 // Script function #75 (0x4b)
sfRand(SCRIPTFUNC_PARAMS)1485 void Script::sfRand(SCRIPTFUNC_PARAMS) {
1486 	thread->_returnValue = _vm->_rnd.getRandomNumber(thread->pop() - 1);
1487 }
1488 
1489 // Script function #76 (0x4c)
sfFadeMusic(SCRIPTFUNC_PARAMS)1490 void Script::sfFadeMusic(SCRIPTFUNC_PARAMS) {
1491 	_vm->_music->setVolume(0, 1000);
1492 }
1493 
1494 // Script function #77 (0x4d)
sfPlayVoice(SCRIPTFUNC_PARAMS)1495 void Script::sfPlayVoice(SCRIPTFUNC_PARAMS) {
1496 	int16 param = thread->pop();
1497 
1498 	if (param > 0) {
1499 		_vm->_sndRes->playVoice(param + 3712);
1500 	} else {
1501 		_vm->_sound->stopSound();
1502 	}
1503 }
1504 
finishDialog(int strID,int replyID,int flags,int bitOffset)1505 void Script::finishDialog(int strID, int replyID, int flags, int bitOffset) {
1506 	byte *addr;
1507 
1508 	if (_conversingThread) {
1509 		_vm->_interface->setMode(kPanelNull);
1510 
1511 #ifdef ENABLE_IHNM
1512 		if (_vm->getGameId() == GID_IHNM) {
1513 			const char *str = _conversingThread->_strings->getString(strID);
1514 			if (*str != '[') {
1515 				int sampleResourceId = -1;
1516 				sampleResourceId = (*_conversingThread->_voiceLUT)[strID];
1517 				if (sampleResourceId < 0 || sampleResourceId > 4000)
1518 					sampleResourceId = -1;
1519 
1520 				_vm->_actor->actorSpeech(_vm->_actor->_protagonist->_id, &str, 1, sampleResourceId, 0);
1521 			}
1522 		}
1523 #endif
1524 
1525 		_conversingThread->_flags &= ~kTFlagWaiting;
1526 
1527 		_conversingThread->push(replyID);
1528 
1529 		if (flags & kReplyOnce) {
1530 			addr = _conversingThread->_staticBase + (bitOffset >> 3);
1531 			*addr |= (1 << (bitOffset & 7));
1532 		}
1533 	}
1534 
1535 	_conversingThread = NULL;
1536 	wakeUpThreads(kWaitTypeDialogBegin);
1537 }
1538 
sfNull(SCRIPTFUNC_PARAMS)1539 void Script::sfNull(SCRIPTFUNC_PARAMS) {
1540 	for (int i = 0; i < nArgs; i++)
1541 		thread->pop();
1542 }
1543 
sfStub(const char * name,ScriptThread * thread,int nArgs)1544 void Script::sfStub(const char *name, ScriptThread *thread, int nArgs) {
1545 	debugN(0, "STUB: %s(", name);
1546 
1547 	for (int i = 0; i < nArgs; i++) {
1548 		debugN(0, "%d", thread->pop());
1549 		if (i + 1 < nArgs)
1550 			debugN(0, ", ");
1551 	}
1552 
1553 	debug(0, ")");
1554 }
1555 
1556 } // End of namespace Saga
1557