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 "common/array.h"
24 #include "common/debug.h"
25 #include "common/memstream.h"
26 #include "common/stack.h"
27 
28 #include "draci/draci.h"
29 #include "draci/animation.h"
30 #include "draci/barchive.h"
31 #include "draci/game.h"
32 #include "draci/mouse.h"
33 #include "draci/music.h"
34 #include "draci/screen.h"
35 #include "draci/script.h"
36 #include "draci/sound.h"
37 #include "draci/surface.h"
38 
39 namespace Draci {
40 
setupCommandList()41 void Script::setupCommandList() {
42 	/** A table of all the commands the game player uses */
43 	static const GPL2Command gplCommands[] = {
44 		{ 0,  0, "gplend",            0, { },                                NULL },
45 		{ 0,  1, "exit",              0, { },                                NULL },
46 		{ 1,  1, "goto",              1, { kGPL2Ident },                     &Script::c_Goto },
47 		{ 2,  1, "Let",               2, { kGPL2Ident, kGPL2Math },          &Script::c_Let },
48 		{ 3,  1, "if",                2, { kGPL2Math, kGPL2Ident },          &Script::c_If },
49 		{ 4,  1, "Start",             2, { kGPL2Ident, kGPL2Str },           &Script::start },
50 		{ 5,  1, "Load",              2, { kGPL2Ident, kGPL2Str },           &Script::load },
51 		{ 5,  2, "StartPlay",         2, { kGPL2Ident, kGPL2Str },           &Script::startPlay },
52 		{ 5,  3, "JustTalk",          0, { },                                &Script::justTalk },
53 		{ 5,  4, "JustStay",          0, { },                                &Script::justStay },
54 		{ 6,  1, "Talk",              2, { kGPL2Ident, kGPL2Str },           &Script::talk },
55 		{ 7,  1, "ObjStat",           2, { kGPL2Ident, kGPL2Ident },         &Script::objStat },
56 		{ 7,  2, "ObjStat_On",        2, { kGPL2Ident, kGPL2Ident },         &Script::objStatOn },
57 		{ 8,  1, "IcoStat",           2, { kGPL2Ident, kGPL2Ident },         &Script::icoStat },
58 		{ 9,  1, "Dialogue",          1, { kGPL2Str },                       &Script::dialogue },
59 		{ 9,  2, "ExitDialogue",      0, { },                                &Script::exitDialogue },
60 		{ 9,  3, "ResetDialogue",     0, { },                                &Script::resetDialogue },
61 		{ 9,  4, "ResetDialogueFrom", 0, { },                                &Script::resetDialogueFrom },
62 		{ 9,  5, "ResetBlock",        1, { kGPL2Ident },                     &Script::resetBlock },
63 		{ 10, 1, "WalkOn",            3, { kGPL2Num, kGPL2Num, kGPL2Ident }, &Script::walkOn },
64 		{ 10, 2, "StayOn",            3, { kGPL2Num, kGPL2Num, kGPL2Ident }, &Script::stayOn },
65 		{ 10, 3, "WalkOnPlay",        3, { kGPL2Num, kGPL2Num, kGPL2Ident }, &Script::walkOnPlay },
66 		{ 11, 1, "LoadPalette",       1, { kGPL2Str },                       &Script::loadPalette },
67 		{ 12, 1, "SetPalette",        0, { },                                &Script::setPalette },
68 		{ 12, 2, "BlackPalette",      0, { },                                &Script::blackPalette },
69 		{ 13, 1, "FadePalette",       3, { kGPL2Num, kGPL2Num, kGPL2Num },   &Script::fadePalette },
70 		{ 13, 2, "FadePalettePlay",   3, { kGPL2Num, kGPL2Num, kGPL2Num },   &Script::fadePalettePlay },
71 		{ 14, 1, "NewRoom",           2, { kGPL2Ident, kGPL2Num },           &Script::newRoom },
72 		{ 15, 1, "ExecInit",          1, { kGPL2Ident },                     &Script::execInit },
73 		{ 15, 2, "ExecLook",          1, { kGPL2Ident },                     &Script::execLook },
74 		{ 15, 3, "ExecUse",           1, { kGPL2Ident },                     &Script::execUse },
75 		{ 18, 1, "LoadMusic",         1, { kGPL2Str },                       &Script::loadMusic },
76 		{ 18, 2, "StartMusic",        0, { },                                &Script::startMusic },
77 		{ 18, 3, "StopMusic",         0, { },                                &Script::stopMusic },
78 		{ 19, 1, "Mark",              0, { },                                &Script::mark },
79 		{ 19, 2, "Release",           0, { },                                &Script::release },
80 		{ 20, 1, "Play",              0, { },                                &Script::play },
81 		{ 21, 1, "LoadMap",           1, { kGPL2Str },                       &Script::loadMap },
82 		{ 21, 2, "RoomMap",           0, { },                                &Script::roomMap },
83 		{ 22, 1, "DisableQuickHero",  0, { },                                &Script::disableQuickHero },
84 		{ 22, 2, "EnableQuickHero",   0, { },                                &Script::enableQuickHero },
85 		{ 23, 1, "DisableSpeedText",  0, { },                                &Script::disableSpeedText },
86 		{ 23, 2, "EnableSpeedText",   0, { },                                &Script::enableSpeedText },
87 		{ 24, 1, "QuitGame",          0, { },                                &Script::quitGame },
88 		{ 25, 1, "PushNewRoom",       0, { },                                &Script::pushNewRoom },
89 		{ 25, 2, "PopNewRoom",        0, { },                                &Script::popNewRoom },
90 		// The following commands are not used in the original game files.
91 		{ 16, 1, "RepaintInventory",  0, { },                                NULL },
92 		{ 16, 2, "ExitInventory",     0, { },                                NULL },
93 		{ 17, 1, "ExitMap",           0, { },                                NULL },
94 		{ 18, 4, "FadeOutMusic",      1, { kGPL2Num },                       NULL },
95 		{ 18, 5, "FadeInMusic",       1, { kGPL2Num },                       NULL },
96 		// The following commands are not even defined in the game
97 		// sources, but their numbers are allocated for internal
98 		// purposes of the old player.
99 		{ 26, 1, "ShowCheat",         0, { },                                NULL },
100 		{ 26, 2, "HideCheat",         0, { },                                NULL },
101 		{ 26, 3, "ClearCheat",        1, { kGPL2Num },                       NULL },
102 		{ 27, 1, "FeedPassword",      3, { kGPL2Num, kGPL2Num, kGPL2Num },   NULL }
103 	};
104 
105 	/** Operators used by the mathematical evaluator */
106 	static const GPL2Operator gplOperators[] = {
107 		{ &Script::operAnd,            "&" },
108 		{ &Script::operOr,             "|" },
109 		{ &Script::operXor,            "^" },
110 		{ &Script::operEqual,          "==" },
111 		{ &Script::operNotEqual,       "!=" },
112 		{ &Script::operLess,           "<" },
113 		{ &Script::operGreater,        ">" },
114 		{ &Script::operLessOrEqual,    "<=" },
115 		{ &Script::operGreaterOrEqual, ">=" },
116 		{ &Script::operMul,            "*" },
117 		{ &Script::operDiv,            "/" },
118 		{ &Script::operMod,            "%" },
119 		{ &Script::operAdd,            "+" },
120 		{ &Script::operSub,            "-" }
121 	};
122 
123 	/** Functions used by the mathematical evaluator */
124 	static const GPL2Function gplFunctions[] = {
125 		{ &Script::funcNot,       "Not" },
126 		{ &Script::funcRandom,    "Random" },
127 		{ &Script::funcIsIcoOn,   "IsIcoOn" },
128 		{ &Script::funcIsIcoAct,  "IsIcoAct" },
129 		{ &Script::funcIcoStat,   "IcoStat" },
130 		{ &Script::funcActIco,    "ActIco" },
131 		{ &Script::funcIsObjOn,   "IsObjOn" },
132 		{ &Script::funcIsObjOff,  "IsObjOff" },
133 		{ &Script::funcIsObjAway, "IsObjAway" },
134 		{ &Script::funcObjStat,   "ObjStat" },
135 		{ &Script::funcLastBlock, "LastBlock" },
136 		{ &Script::funcAtBegin,   "AtBegin" },
137 		{ &Script::funcBlockVar,  "BlockVar" },
138 		{ &Script::funcHasBeen,   "HasBeen" },
139 		{ &Script::funcMaxLine,   "MaxLine" },
140 		{ &Script::funcActPhase,  "ActPhase" },
141 		// The following function is not even defined in the game
142 		// sources, but its number is allocated for internal purposes
143 		// of the old player.
144 		{ NULL, "Cheat" },
145 	};
146 
147 	_commandList = gplCommands;
148 	_operatorList = gplOperators;
149 	_functionList = gplFunctions;
150 }
151 
152 /** Type of mathematical object */
153 enum mathExpressionObject {
154 	kMathEnd,
155 	kMathNumber,
156 	kMathOperator,
157 	kMathFunctionCall,
158 	kMathVariable
159 };
160 
161 /* GPL operators */
162 
operAnd(int op1,int op2) const163 int Script::operAnd(int op1, int op2) const {
164 	return op1 & op2;
165 }
166 
operOr(int op1,int op2) const167 int Script::operOr(int op1, int op2) const {
168 	return op1 | op2;
169 }
170 
operXor(int op1,int op2) const171 int Script::operXor(int op1, int op2) const {
172 	return op1 ^ op2;
173 }
174 
operEqual(int op1,int op2) const175 int Script::operEqual(int op1, int op2) const {
176 	return op1 == op2;
177 }
178 
operNotEqual(int op1,int op2) const179 int Script::operNotEqual(int op1, int op2) const {
180 	return op1 != op2;
181 }
182 
operLess(int op1,int op2) const183 int Script::operLess(int op1, int op2) const {
184 	return op1 < op2;
185 }
186 
operGreater(int op1,int op2) const187 int Script::operGreater(int op1, int op2) const {
188 	return op1 > op2;
189 }
190 
operGreaterOrEqual(int op1,int op2) const191 int Script::operGreaterOrEqual(int op1, int op2) const {
192 	return op1 >= op2;
193 }
194 
operLessOrEqual(int op1,int op2) const195 int Script::operLessOrEqual(int op1, int op2) const {
196 	return op1 <= op2;
197 }
198 
operMul(int op1,int op2) const199 int Script::operMul(int op1, int op2) const {
200 	return op1 * op2;
201 }
202 
operAdd(int op1,int op2) const203 int Script::operAdd(int op1, int op2) const {
204 	return op1 + op2;
205 }
206 
operSub(int op1,int op2) const207 int Script::operSub(int op1, int op2) const {
208 	return op1 - op2;
209 }
210 
operDiv(int op1,int op2) const211 int Script::operDiv(int op1, int op2) const {
212 	return op1 / op2;
213 }
214 
operMod(int op1,int op2) const215 int Script::operMod(int op1, int op2) const {
216 	return op1 % op2;
217 }
218 
219 /* GPL functions */
220 
funcRandom(int n) const221 int Script::funcRandom(int n) const {
222 // The function needs to return numbers in the [0..n-1] range so we need to deduce 1
223 // (RandomSource::getRandomNumber returns a number in the range [0..n])
224 
225 	n -= 1;
226 	return _vm->_rnd.getRandomNumber(n);
227 }
228 
funcAtBegin(int yesno) const229 int Script::funcAtBegin(int yesno) const {
230 	return _vm->_game->isDialogueBegin() == (bool)yesno;
231 }
232 
funcLastBlock(int blockID) const233 int Script::funcLastBlock(int blockID) const {
234 	blockID -= 1;
235 
236 	return _vm->_game->getDialogueLastBlock() == blockID;
237 }
238 
funcBlockVar(int blockID) const239 int Script::funcBlockVar(int blockID) const {
240 	blockID -= 1;
241 
242 	const int currentOffset = _vm->_game->getCurrentDialogueOffset();
243 	return _vm->_game->getDialogueVar(currentOffset + blockID);
244 }
245 
funcHasBeen(int blockID) const246 int Script::funcHasBeen(int blockID) const {
247 	blockID -= 1;
248 
249 	const int currentOffset = _vm->_game->getCurrentDialogueOffset();
250 	return _vm->_game->getDialogueVar(currentOffset + blockID) > 0;
251 }
252 
funcMaxLine(int lines) const253 int Script::funcMaxLine(int lines) const {
254 	return _vm->_game->getDialogueLinesNum() < lines;
255 }
256 
funcNot(int n) const257 int Script::funcNot(int n) const {
258 	return !n;
259 }
260 
funcIsIcoOn(int itemID) const261 int Script::funcIsIcoOn(int itemID) const {
262 	itemID -= 1;
263 
264 	return _vm->_game->getItemStatus(itemID) == 1;
265 }
266 
funcIcoStat(int itemID) const267 int Script::funcIcoStat(int itemID) const {
268 	itemID -= 1;
269 
270 	int status = _vm->_game->getItemStatus(itemID);
271 	return (status == 1) ? 1 : 2;
272 }
273 
funcIsIcoAct(int itemID) const274 int Script::funcIsIcoAct(int itemID) const {
275 	itemID -= 1;
276 
277 	return _vm->_game->getCurrentItem() == _vm->_game->getItem(itemID);
278 }
279 
funcActIco(int itemID) const280 int Script::funcActIco(int itemID) const {
281 	// The parameter seems to be an omission in the original player since it's not
282 	// used in the implementation of the function. It's possible that the functions were
283 	// implemented in such a way that they had to have a single parameter so this is only
284 	// passed as a dummy.
285 
286 	const GameItem *item = _vm->_game->getCurrentItem();
287 	return item ? item->_absNum + 1 : 0;
288 }
289 
funcIsObjOn(int objID) const290 int Script::funcIsObjOn(int objID) const {
291 	objID -= 1;
292 
293 	const GameObject *obj = _vm->_game->getObject(objID);
294 
295 	return obj->_visible;
296 }
297 
funcIsObjOff(int objID) const298 int Script::funcIsObjOff(int objID) const {
299 	objID -= 1;
300 
301 	const GameObject *obj = _vm->_game->getObject(objID);
302 
303 	// We index locations from 0 (as opposed to the original player where it was from 1)
304 	// That's why the "away" location 0 from the data files is converted to -1
305 	return !obj->_visible && obj->_location != -1;
306 }
307 
funcObjStat(int objID) const308 int Script::funcObjStat(int objID) const {
309 	objID -= 1;
310 
311 	const GameObject *obj = _vm->_game->getObject(objID);
312 
313 	if (obj->_location == _vm->_game->getRoomNum()) {
314 		if (obj->_visible) {
315 			return 1;   // object is ON (in the room and visible)
316 		} else {
317 			return 2;   // object is OFF (in the room, not visible)
318 		}
319 	} else {
320 		return 3;       // object is AWAY (not in the room)
321 	}
322 }
323 
funcIsObjAway(int objID) const324 int Script::funcIsObjAway(int objID) const {
325 	objID -= 1;
326 
327 	const GameObject *obj = _vm->_game->getObject(objID);
328 
329 	// see Script::funcIsObjOff
330 	return !obj->_visible && obj->_location == -1;
331 }
332 
funcActPhase(int objID) const333 int Script::funcActPhase(int objID) const {
334 	objID -= 1;
335 
336 	// Default return value
337 	int ret = 0;
338 
339 	if (_vm->_game->getLoopStatus() == kStatusInventory) {
340 		return ret;
341 	}
342 
343 	const GameObject *obj = _vm->_game->getObject(objID);
344 
345 	const bool visible = (obj->_location == _vm->_game->getRoomNum() && obj->_visible);
346 	if (objID == kDragonObject || visible) {
347 		const int i = obj->playingAnim();
348 		if (i >= 0) {
349 			Animation *anim = obj->_anim[i];
350 			ret = anim->currentFrameNum();
351 		}
352 	}
353 
354 	return ret;
355 }
356 
357 /* GPL commands */
358 
play(const Common::Array<int> & params)359 void Script::play(const Common::Array<int> &params) {
360 	if (_vm->_game->getLoopStatus() == kStatusInventory) {
361 		return;
362 	}
363 
364 	// Runs just one phase of the loop and exits.  Used when waiting for a
365 	// particular animation phase to come.
366 	_vm->_game->loop(kInnerUntilExit, true);
367 }
368 
load(const Common::Array<int> & params)369 void Script::load(const Common::Array<int> &params) {
370 	if (_vm->_game->getLoopStatus() == kStatusInventory) {
371 		return;
372 	}
373 
374 	int objID = params[0] - 1;
375 	int animID = params[1] - 1;
376 
377 	// If the animation is already loaded, return
378 	GameObject *obj = _vm->_game->getObject(objID);
379 	if (obj->getAnim(animID) >= 0) {
380 		return;
381 	}
382 
383 	// We don't test here whether an animation is loaded in the
384 	// AnimationManager while not being registered in the object's array of
385 	// animations.  This cannot legally happen and an assertion will be
386 	// thrown by AnimationManager::load().
387 	obj->addAnim(_vm->_anims->load(animID));
388 }
389 
start(const Common::Array<int> & params)390 void Script::start(const Common::Array<int> &params) {
391 	if (_vm->_game->getLoopStatus() == kStatusInventory) {
392 		return;
393 	}
394 
395 	int objID = params[0] - 1;
396 	int animID = params[1] - 1;
397 
398 	GameObject *obj = _vm->_game->getObject(objID);
399 	obj->stopAnim();
400 
401 	int index = obj->getAnim(animID);
402 	if (index < 0) {
403 		// WORKAROUND:
404 		//
405 		// The original game files seem to contain errors, which I have
406 		// verified by inspecting their source code.  They try to load
407 		// each animation before starting it, but fail to anticipate
408 		// all possible code paths when game loading comes into play.
409 		//
410 		// In particular, if I load the game at the stump location,
411 		// apply a hedgehog on them, and then talk to them, one of the
412 		// animations is not loaded.  This animation would have been
413 		// loaded had I talked to them before applying the hedgehog
414 		// (because a different dialog init code is run before the
415 		// application).  Talking to the stumps is necessary to be able
416 		// to apply the hedgehog, so normal game-play is safe.
417 		// However, if I save the game after talking to them and load
418 		// it later, then the game variables are set so as to allow me
419 		// to apply the hedgehog, but there is no way that the game
420 		// player would load the requested animation by itself.
421 		// See objekty:5077 and parezy.txt:27.
422 		index = obj->addAnim(_vm->_anims->load(animID));
423 		debugC(1, kDraciBytecodeDebugLevel, "start(%d=%s) cannot find animation %d.  Loading.",
424 			objID, obj->_title.c_str(), animID);
425 	}
426 	Animation *anim = obj->_anim[index];
427 	anim->registerCallback(&Animation::stop);
428 
429 	if (objID == kDragonObject) {
430 		_vm->_game->playHeroAnimation(index);
431 	} else {
432 		const bool visible = (obj->_location == _vm->_game->getRoomNum() && obj->_visible);
433 		if (visible) {
434 			obj->playAnim(index);
435 		}
436 	}
437 }
438 
startPlay(const Common::Array<int> & params)439 void Script::startPlay(const Common::Array<int> &params) {
440 	if (_vm->_game->getLoopStatus() == kStatusInventory) {
441 		return;
442 	}
443 
444 	int objID = params[0] - 1;
445 	int animID = params[1] - 1;
446 
447 	GameObject *obj = _vm->_game->getObject(objID);
448 	obj->stopAnim();
449 
450 	int index = obj->getAnim(animID);
451 	if (index < 0) {
452 		index = obj->addAnim(_vm->_anims->load(animID));
453 		debugC(1, kDraciBytecodeDebugLevel, "startPlay(%d=%s) cannot find animation %d.  Loading.",
454 			objID, obj->_title.c_str(), animID);
455 	}
456 	Animation *anim = obj->_anim[index];
457 	anim->registerCallback(&Animation::exitGameLoop);
458 
459 	if (objID == kDragonObject) {
460 		_vm->_game->playHeroAnimation(index);
461 	} else {
462 		const bool visible = (obj->_location == _vm->_game->getRoomNum() && obj->_visible);
463 		if (visible) {
464 			obj->playAnim(index);
465 		}
466 	}
467 
468 	// Runs an inner loop until the animation ends.
469 	_vm->_game->loop(kInnerUntilExit, false);
470 	obj->stopAnim();
471 
472 	anim->registerCallback(&Animation::doNothing);
473 }
474 
justTalk(const Common::Array<int> & params)475 void Script::justTalk(const Common::Array<int> &params) {
476 	const GameObject *dragon = _vm->_game->getObject(kDragonObject);
477 	const int last_anim = static_cast<Movement> (dragon->playingAnim());
478 	const int new_anim = (last_anim == kSpeakRight || last_anim == kStopRight) ? kSpeakRight : kSpeakLeft;
479 	_vm->_game->playHeroAnimation(new_anim);
480 }
481 
justStay(const Common::Array<int> & params)482 void Script::justStay(const Common::Array<int> &params) {
483 	const GameObject *dragon = _vm->_game->getObject(kDragonObject);
484 	const int last_anim = static_cast<Movement> (dragon->playingAnim());
485 	const int new_anim = (last_anim == kSpeakRight || last_anim == kStopRight) ? kStopRight : kStopLeft;
486 	_vm->_game->playHeroAnimation(new_anim);
487 }
488 
c_If(const Common::Array<int> & params)489 void Script::c_If(const Common::Array<int> &params) {
490 	int expression = params[0];
491 	int jump = params[1];
492 
493 	if (expression)
494 		_jump = jump;
495 }
496 
c_Goto(const Common::Array<int> & params)497 void Script::c_Goto(const Common::Array<int> &params) {
498 	int jump = params[0];
499 
500 	_jump = jump;
501 }
502 
c_Let(const Common::Array<int> & params)503 void Script::c_Let(const Common::Array<int> &params) {
504 	int var = params[0] - 1;
505 	int value = params[1];
506 
507 	_vm->_game->setVariable(var, value);
508 }
509 
loadMusic(const Common::Array<int> & params)510 void Script::loadMusic(const Common::Array<int> &params) {
511 	int track = params[0];
512 	_vm->_game->setMusicTrack(track);
513 }
514 
startMusic(const Common::Array<int> & params)515 void Script::startMusic(const Common::Array<int> &params) {
516 	// If already playing this track, nothing happens.
517 	_vm->_music->playSMF(_vm->_game->getMusicTrack(), true);
518 }
519 
stopMusic(const Common::Array<int> & params)520 void Script::stopMusic(const Common::Array<int> &params) {
521 	_vm->_music->stop();
522 	_vm->_game->setMusicTrack(0);
523 }
524 
mark(const Common::Array<int> & params)525 void Script::mark(const Common::Array<int> &params) {
526 	_vm->_game->setMarkedAnimationIndex(_vm->_anims->getLastIndex());
527 }
528 
release(const Common::Array<int> & params)529 void Script::release(const Common::Array<int> &params) {
530 	int markedIndex = _vm->_game->getMarkedAnimationIndex();
531 
532 	_vm->_game->deleteAnimationsAfterIndex(markedIndex);
533 }
534 
icoStat(const Common::Array<int> & params)535 void Script::icoStat(const Common::Array<int> &params) {
536 	int status = params[0];
537 	int itemID = params[1] - 1;
538 	GameItem *item = _vm->_game->getItem(itemID);
539 
540 	_vm->_game->setItemStatus(itemID, status == 1);
541 
542 	if (!_vm->_game->getItemStatus(itemID)) {
543 		// Remove the item from the inventory and release its animations.
544 		_vm->_game->removeItem(item);
545 		item->_anim->del();
546 		item->_anim = NULL;
547 
548 		// If the item was in the hand, remove it from the hands and,
549 		// if the cursor was set to this item (as opposed to, say, an
550 		// arrow leading outside a location), set it to standard.
551 		if (_vm->_game->getCurrentItem() == item) {
552 			_vm->_game->setCurrentItem(NULL);
553 			_vm->_game->setPreviousItemPosition(-1);
554 			if (_vm->_mouse->getCursorType() >= kItemCursor) {
555 				_vm->_mouse->setCursorType(kNormalCursor);
556 			}
557 		}
558 
559 	} else {
560 		_vm->_game->loadItemAnimation(item);
561 		_vm->_game->setCurrentItem(item);
562 		_vm->_game->setPreviousItemPosition(0);		// next time, try to place the item from the beginning
563 		_vm->_mouse->loadItemCursor(item, false);
564 	}
565 }
566 
objStatOn(const Common::Array<int> & params)567 void Script::objStatOn(const Common::Array<int> &params) {
568 	int objID = params[0] - 1;
569 	int roomID = params[1] - 1;
570 
571 	GameObject *obj = _vm->_game->getObject(objID);
572 
573 	obj->_location = roomID;
574 	obj->_visible = true;
575 }
576 
objStat(const Common::Array<int> & params)577 void Script::objStat(const Common::Array<int> &params) {
578 	int status = params[0];
579 	int objID = params[1] - 1;
580 
581 	GameObject *obj = _vm->_game->getObject(objID);
582 
583 	if (status == 1) {
584 		return;
585 	} else if (status == 2) {
586 		obj->_visible = false;
587 	} else {
588 		obj->_visible = false;
589 		obj->_location = -1;
590 	}
591 
592 	obj->stopAnim();
593 }
594 
execInit(const Common::Array<int> & params)595 void Script::execInit(const Common::Array<int> &params) {
596 	if (_vm->_game->getLoopStatus() == kStatusInventory) {
597 		return;
598 	}
599 
600 	int objID = params[0] - 1;
601 
602 	const GameObject *obj = _vm->_game->getObject(objID);
603 	run(obj->_program, obj->_init);
604 }
605 
execLook(const Common::Array<int> & params)606 void Script::execLook(const Common::Array<int> &params) {
607 	if (_vm->_game->getLoopStatus() == kStatusInventory) {
608 		return;
609 	}
610 
611 	int objID = params[0] - 1;
612 
613 	const GameObject *obj = _vm->_game->getObject(objID);
614 
615 	// We don't have to use runWrapper(), because the has already been
616 	// wrapped due to the fact that these commands are only run from a GPL2
617 	// program but never from the core player.
618 	run(obj->_program, obj->_look);
619 }
620 
execUse(const Common::Array<int> & params)621 void Script::execUse(const Common::Array<int> &params) {
622 	if (_vm->_game->getLoopStatus() == kStatusInventory) {
623 		return;
624 	}
625 
626 	int objID = params[0] - 1;
627 
628 	const GameObject *obj = _vm->_game->getObject(objID);
629 	run(obj->_program, obj->_use);
630 }
631 
stayOn(const Common::Array<int> & params)632 void Script::stayOn(const Common::Array<int> &params) {
633 	if (_vm->_game->getLoopStatus() == kStatusInventory) {
634 		return;
635 	}
636 
637 	int x, y;
638 	Common::Point afterLoadingPos = _vm->_game->getHeroLoadingPosition();
639 	if(_vm->_game->isPositionLoaded() == true) {
640 		x = afterLoadingPos.x;
641 		y = afterLoadingPos.y;
642 	}
643 	else {
644 		x = params[0];
645 		y = params[1];
646 	}
647 	SightDirection dir = static_cast<SightDirection> (params[2]);
648 
649 	// Jumps into the given position regardless of the walking map.
650 	Common::Point heroPos(_vm->_game->findNearestWalkable(x, y));
651 	Common::Point mousePos(_vm->_mouse->getPosX(), _vm->_mouse->getPosY());
652 	const GameObject *dragon = _vm->_game->getObject(kDragonObject);
653 	Movement startingDirection = static_cast<Movement> (dragon->playingAnim());
654 
655 	_vm->_game->stopWalking();
656 	_vm->_game->setHeroPosition(heroPos);
657 	_vm->_game->playHeroAnimation(WalkingState::animationForSightDirection(
658 		  dir, heroPos, mousePos, WalkingPath(), startingDirection));
659 }
660 
walkOn(const Common::Array<int> & params)661 void Script::walkOn(const Common::Array<int> &params) {
662 	if (_vm->_game->getLoopStatus() == kStatusInventory) {
663 		return;
664 	}
665 
666 	int x = params[0];
667 	int y = params[1];
668 	SightDirection dir = static_cast<SightDirection> (params[2]);
669 
670 	// Constructs an optimal path and starts walking there.  No callback
671 	// will be called at the end nor will the loop-body exit.
672 	_vm->_game->stopWalking();
673 	_vm->_game->walkHero(x, y, dir);
674 }
675 
walkOnPlay(const Common::Array<int> & params)676 void Script::walkOnPlay(const Common::Array<int> &params) {
677 	if (_vm->_game->getLoopStatus() == kStatusInventory) {
678 		return;
679 	}
680 
681 	if(_vm->_game->isPositionLoaded() == true) {
682 		_vm->_game->setPositionLoaded(false);
683 		return;
684 	}
685 
686 	int x = params[0];
687 	int y = params[1];
688 	SightDirection dir = static_cast<SightDirection> (params[2]);
689 
690 	_vm->_game->stopWalking();
691 	_vm->_game->walkHero(x, y, dir);
692 
693 	// Walk in an inner loop until the hero has arrived at the target
694 	// point.  Then the loop-body will exit.
695 	_vm->_game->loop(kInnerUntilExit, false);
696 }
697 
newRoom(const Common::Array<int> & params)698 void Script::newRoom(const Common::Array<int> &params) {
699 	if (_vm->_game->getLoopStatus() == kStatusInventory) {
700 		return;
701 	}
702 
703 	if(_vm->_game->isPositionLoaded() == true) {
704 		_vm->_game->setPositionLoaded(false);
705 	}
706 
707 	int room = params[0] - 1;
708 	int gate = params[1] - 1;
709 
710 	_vm->_game->scheduleEnteringRoomUsingGate(room, gate);
711 }
712 
talk(const Common::Array<int> & params)713 void Script::talk(const Common::Array<int> &params) {
714 	int personID = params[0] - 1;
715 	int sentenceID = params[1] - 1;
716 
717 	Surface *surface = _vm->_screen->getSurface();
718 
719 	// Fetch string
720 	const BAFile *f = _vm->_stringsArchive->getFile(sentenceID);
721 
722 	// Fetch frame for the speech text
723 	Animation *speechAnim = _vm->_anims->getAnimation(kSpeechText);
724 	Text *speechFrame = reinterpret_cast<Text *>(speechAnim->getCurrentFrame());
725 
726 	// Fetch person info
727 	const Person *person = _vm->_game->getPerson(personID);
728 
729 	// Fetch the dubbing
730 	SoundSample *sample = _vm->_sound->isMutedVoice()
731 		? NULL : _vm->_dubbingArchive->getSample(sentenceID, 0);
732 
733 	// Set the string and text color
734 	surface->markDirtyRect(speechFrame->getRect(kNoDisplacement));
735 	if (_vm->_sound->showSubtitles() || !sample) {
736 		speechFrame->setText(Common::String((const char *)f->_data+1, f->_length-1));
737 	} else {
738 		speechFrame->setText("");
739 	}
740 	speechFrame->setColor(person->_fontColor);
741 	speechFrame->repeatedlySplitLongLines(kScreenWidth);
742 
743 	// Speak the dubbing if possible
744 	uint dubbingDuration = 0;
745 	if (sample) {
746 		dubbingDuration = _vm->_sound->playVoice(sample);
747 		debugC(3, kDraciSoundDebugLevel, "Playing sentence %d: %d+%d with duration %dms",
748 			sentenceID, sample->_offset, sample->_length, dubbingDuration);
749 		dubbingDuration += 500;
750 	}
751 
752 	// Record time
753 	int talkSpeed = _vm->_sound->talkSpeed();
754 	if (!_vm->_game->getEnableSpeedText() && talkSpeed > kStandardSpeed) {
755 		talkSpeed = kStandardSpeed;
756 	}
757 	if (talkSpeed <= 0) {
758 		talkSpeed = 1;
759 	}
760 	uint subtitleDuration;
761 	if (talkSpeed >= 255) {
762 		subtitleDuration = 0;
763 	} else {
764 		subtitleDuration = (kBaseSpeechDuration + speechFrame->getLength() * kSpeechTimeUnit) / talkSpeed;
765 	}
766 	const uint duration = MAX(subtitleDuration, dubbingDuration);
767 	_vm->_game->setSpeechTiming(_vm->_system->getMillis(), duration);
768 
769 	// Set speech text coordinates
770 	int x, y;
771 	if (_vm->_game->getLoopStatus() == kStatusInventory) {
772 		x = surface->centerOnX(160, speechFrame->getWidth());
773 		y = 4;
774 	} else {
775 		x = surface->centerOnX(person->_x, speechFrame->getWidth());
776 		y = surface->putAboveY(person->_y, speechFrame->getHeight());
777 	}
778 
779 	speechFrame->setX(x);
780 	speechFrame->setY(y);
781 
782 	// Call the game loop to enable interactivity until the text expires.
783 	_vm->_game->loop(kInnerWhileTalk, false);
784 
785 	// Delete the text
786 	_vm->_screen->getSurface()->markDirtyRect(speechFrame->getRect(kNoDisplacement));
787 	speechFrame->setText("");
788 
789 	// Stop the playing sample and deallocate it.  Stopping should only be
790 	// necessary if the user interrupts the playback.
791 	if (sample) {
792 		_vm->_sound->stopVoice();
793 		sample->close();
794 	}
795 }
796 
dialogue(const Common::Array<int> & params)797 void Script::dialogue(const Common::Array<int> &params) {
798 	int dialogueID = params[0] - 1;
799 
800 	_vm->_game->dialogueMenu(dialogueID);
801 }
802 
loadMap(const Common::Array<int> & params)803 void Script::loadMap(const Common::Array<int> &params) {
804 	int mapID = params[0] - 1;
805 
806 	_vm->_game->loadWalkingMap(mapID);
807 }
808 
resetDialogue(const Common::Array<int> & params)809 void Script::resetDialogue(const Common::Array<int> &params) {
810 	const int currentOffset = _vm->_game->getCurrentDialogueOffset();
811 
812 	for (int i = 0; i < _vm->_game->getDialogueBlockNum(); ++i) {
813 		_vm->_game->setDialogueVar(currentOffset + i, 0);
814 	}
815 }
816 
resetDialogueFrom(const Common::Array<int> & params)817 void Script::resetDialogueFrom(const Common::Array<int> &params) {
818 	const int currentOffset = _vm->_game->getCurrentDialogueOffset();
819 
820 	for (int i = _vm->_game->getDialogueCurrentBlock(); i < _vm->_game->getDialogueBlockNum(); ++i) {
821 		_vm->_game->setDialogueVar(currentOffset + i, 0);
822 	}
823 }
824 
resetBlock(const Common::Array<int> & params)825 void Script::resetBlock(const Common::Array<int> &params) {
826 	int blockID = params[0] - 1;
827 
828 	const int currentOffset = _vm->_game->getCurrentDialogueOffset();
829 
830 	_vm->_game->setDialogueVar(currentOffset + blockID, 0);
831 }
832 
exitDialogue(const Common::Array<int> & params)833 void Script::exitDialogue(const Common::Array<int> &params) {
834 	_vm->_game->setDialogueExit(true);
835 }
836 
roomMap(const Common::Array<int> & params)837 void Script::roomMap(const Common::Array<int> &params) {
838 	// Load the default walking map for the room
839 	_vm->_game->loadWalkingMap(_vm->_game->getMapID());
840 }
841 
disableQuickHero(const Common::Array<int> & params)842 void Script::disableQuickHero(const Common::Array<int> &params) {
843 	_vm->_game->setEnableQuickHero(false);
844 }
845 
enableQuickHero(const Common::Array<int> & params)846 void Script::enableQuickHero(const Common::Array<int> &params) {
847 	_vm->_game->setEnableQuickHero(true);
848 }
849 
disableSpeedText(const Common::Array<int> & params)850 void Script::disableSpeedText(const Common::Array<int> &params) {
851 	_vm->_game->setEnableSpeedText(false);
852 }
853 
enableSpeedText(const Common::Array<int> & params)854 void Script::enableSpeedText(const Common::Array<int> &params) {
855 	_vm->_game->setEnableSpeedText(true);
856 }
857 
loadPalette(const Common::Array<int> & params)858 void Script::loadPalette(const Common::Array<int> &params) {
859 	int palette = params[0] - 1;
860 
861 	_vm->_game->schedulePalette(palette);
862 }
863 
blackPalette(const Common::Array<int> & params)864 void Script::blackPalette(const Common::Array<int> &params) {
865 
866 	_vm->_game->schedulePalette(kBlackPalette);
867 }
868 
fadePalette(const Common::Array<int> & params)869 void Script::fadePalette(const Common::Array<int> &params) {
870 	// Unused first and last
871 	int phases = params[2];
872 
873 	// Let the palette fade in the background while the game continues.
874 	// Since we don't set substatus to kInnerWhileFade, the outer loop will
875 	// just continue rather than exit.
876 	_vm->_game->initializeFading(phases);
877 }
878 
fadePalettePlay(const Common::Array<int> & params)879 void Script::fadePalettePlay(const Common::Array<int> &params) {
880 	// Unused first and last
881 	int phases = params[2];
882 	_vm->_game->initializeFading(phases);
883 
884 	// Call the game loop to enable interactivity until the fading is done.
885 	_vm->_game->loop(kInnerWhileFade, false);
886 }
887 
setPalette(const Common::Array<int> & params)888 void Script::setPalette(const Common::Array<int> &params) {
889 	if (_vm->_game->getScheduledPalette() == -1) {
890 		_vm->_screen->setPalette(NULL, 0, kNumColors);
891 	} else {
892 		const BAFile *f;
893 		f = _vm->_paletteArchive->getFile(_vm->_game->getScheduledPalette());
894 		_vm->_screen->setPalette(f->_data, 0, kNumColors);
895 	}
896 	// Immediately update the palette
897 	_vm->_screen->copyToScreen();
898 	_vm->_system->delayMillis(kTimeUnit);
899 }
900 
quitGame(const Common::Array<int> & params)901 void Script::quitGame(const Common::Array<int> &params) {
902 	_vm->_game->setQuit(true);
903 }
904 
pushNewRoom(const Common::Array<int> & params)905 void Script::pushNewRoom(const Common::Array<int> &params) {
906 	_vm->_game->pushNewRoom();
907 }
908 
popNewRoom(const Common::Array<int> & params)909 void Script::popNewRoom(const Common::Array<int> &params) {
910 	_vm->_game->popNewRoom();
911 }
912 
913 /**
914  * @brief Evaluates mathematical expressions
915  * @param reader Stream reader set to the beginning of the expression
916  */
handleMathExpression(Common::ReadStream * reader) const917 int Script::handleMathExpression(Common::ReadStream *reader) const {
918 	Common::Stack<int> stk;
919 	mathExpressionObject obj;
920 	GPL2Operator oper;
921 	GPL2Function func;
922 
923 	debugC(4, kDraciBytecodeDebugLevel, "\t<MATHEXPR>");
924 
925 	// Read in initial math object
926 	obj = (mathExpressionObject)reader->readSint16LE();
927 
928 	int value;
929 	int arg1, arg2, res;
930 
931 	while (1) {
932 		if (obj == kMathEnd) {
933 			// Check whether the expression was evaluated correctly
934 			// The stack should contain only one value after the evaluation
935 			// i.e. the result of the expression
936 			assert(stk.size() == 1 && "Mathematical expression error");
937 			break;
938 		}
939 
940 		switch (obj) {
941 
942 		// If the object type is not known, assume that it's a number
943 		default:
944 		case kMathNumber:
945 			value = reader->readSint16LE();
946 			stk.push(value);
947 			debugC(4, kDraciBytecodeDebugLevel, "\t\tnumber: %d", value);
948 			break;
949 
950 		case kMathOperator:
951 			value = reader->readSint16LE();
952 			arg2 = stk.pop();
953 			arg1 = stk.pop();
954 
955 			// Fetch operator
956 			oper = _operatorList[value-1];
957 
958 			// Calculate result
959 			res = (this->*(oper._handler))(arg1, arg2);
960 
961 			// Push result
962 			stk.push(res);
963 
964 			debugC(4, kDraciBytecodeDebugLevel, "\t\t%d %s %d (res: %d)",
965 				arg1, oper._name, arg2, res);
966 			break;
967 
968 		case kMathVariable:
969 			value = reader->readSint16LE() - 1;
970 
971 			stk.push(_vm->_game->getVariable(value));
972 
973 			debugC(4, kDraciBytecodeDebugLevel, "\t\tvariable: %d (%d)", value,
974 				_vm->_game->getVariable(value));
975 			break;
976 
977 		case kMathFunctionCall:
978 			value = reader->readSint16LE();
979 
980 			// Fetch function
981 			func = _functionList[value-1];
982 
983 			// If not yet implemented
984 			if (func._handler == 0) {
985 				stk.pop();
986 
987 				// Pushing dummy value
988 				stk.push(0);
989 
990 				debugC(4, kDraciBytecodeDebugLevel, "\t\tcall: %s (not implemented)",
991 				       func._name);
992 			} else {
993 				arg1 = stk.pop();
994 
995 				// Calculate result
996 				res = (this->*(func._handler))(arg1);
997 
998 				// Push the result on the evaluation stack
999 				stk.push(res);
1000 
1001 				debugC(4, kDraciBytecodeDebugLevel, "\t\tcall: %s(%d) (res: %d)",
1002 				       func._name, arg1, res);
1003 			}
1004 
1005 			break;
1006 		}
1007 
1008 		obj = (mathExpressionObject) reader->readSint16LE();
1009 	}
1010 
1011 	return stk.pop();
1012 }
1013 
1014 /**
1015  * @brief Evaluates a GPL mathematical expression on a given offset and returns
1016  * the result (which is normally a boolean-like value)
1017  *
1018  * @param program   A GPL2Program instance of the program containing the expression
1019  * @param offset    Offset of the expression inside the program (in multiples of 2 bytes)
1020  *
1021  * @return The result of the expression converted to a bool.
1022  *
1023  * Reference: the function equivalent to this one is called "Can()" in the original engine.
1024  */
testExpression(const GPL2Program & program,uint16 offset) const1025 bool Script::testExpression(const GPL2Program &program, uint16 offset) const {
1026 	// Initialize program reader
1027 	Common::MemoryReadStream reader(program._bytecode, program._length);
1028 
1029 	// Offset is given as number of 16-bit integers so we need to convert
1030 	// it to a number of bytes
1031 	offset -= 1;
1032 	offset *= 2;
1033 
1034 	// Seek to the expression
1035 	reader.seek(offset);
1036 
1037 	debugC(4, kDraciBytecodeDebugLevel,
1038 	       "Evaluating (standalone) GPL expression at offset %d:", offset);
1039 
1040 	return (bool)handleMathExpression(&reader);
1041 }
1042 
1043 /**
1044  * @brief Find the current command in the internal table
1045  *
1046  * @param num       Command number
1047  * @param subnum    Command subnumer
1048  *
1049  * @return NULL if command is not found. Otherwise, a pointer to a GPL2Command
1050  *         struct representing the command.
1051  */
findCommand(byte num,byte subnum) const1052 const GPL2Command *Script::findCommand(byte num, byte subnum) const {
1053 	uint i = 0;
1054 	while (1) {
1055 
1056 		// Command not found
1057 		if (i >= kNumCommands) {
1058 			break;
1059 		}
1060 
1061 		// Return found command
1062 		if (_commandList[i]._number == num &&
1063 			_commandList[i]._subNumber == subnum) {
1064 			return &_commandList[i];
1065 		}
1066 
1067 		++i;
1068 	}
1069 
1070 	return NULL;
1071 }
1072 
1073 /**
1074  * @brief GPL2 bytecode interpreter
1075  * @param program GPL program in the form of a GPL2Program struct
1076  *        offset Offset into the program where execution should begin
1077  *
1078  * GPL2 is short for Game Programming Language 2 which is the script language
1079  * used by Draci Historie. This is the interpreter for the language.
1080  *
1081  * A compiled GPL2 program consists of a stream of bytes representing commands
1082  * and their parameters. The syntax is as follows:
1083  *
1084  * Syntax of a command:
1085  *  <name of the command> <number> <sub-number> <list of parameters...>
1086  *
1087  * Syntax of a parameter:
1088  *  - 1: integer number literally passed to the program
1089  *  - 2-1: string stored in the reservouir of game strings (i.e. something to be
1090  *    displayed) and stored as an index in this list
1091  *  - 2-2: string resolved by the compiler (i.e., a path to another file) and
1092  *    replaced by an integer index of this entity in the appropriate namespace
1093  *    (e.g., the index of the palette, location, ...)
1094  *  - 3-0: relative jump to a label defined in this code.  Each label must be
1095  *    first declared in the beginning of the program.
1096  *  - 3-1 .. 3-9: index of an entity in several namespaces, defined in file ident
1097  *  - 4: mathematical expression compiled into a postfix format
1098  *
1099  *  In the compiled program, parameters of type 1..3 are represented by a single
1100  *  16-bit integer.  The called command knows by its definition what namespace the
1101  *  value comes from.
1102  */
1103 
run(const GPL2Program & program,uint16 offset)1104 void Script::run(const GPL2Program &program, uint16 offset) {
1105 	if (shouldEndProgram()) {
1106 		// This might get set by some GPL commands via Script::endCurrentProgram()
1107 		// if they need a program to stop midway.  This flag is sticky until cleared
1108 		// at the top level.
1109 		return;
1110 	}
1111 
1112 	int oldJump = _jump;
1113 
1114 	// Mark the last animation index before we do anything so a Release command
1115 	// doesn't unload too many animations if we forget to use a Mark command first
1116 	_vm->_game->setMarkedAnimationIndex(_vm->_anims->getLastIndex());
1117 
1118 	// Stream reader for the whole program
1119 	Common::MemoryReadStream reader(program._bytecode, program._length);
1120 
1121 	// Parameter queue that is passed to each command
1122 	Common::Array<int> params;
1123 
1124 	// Offset is given as number of 16-bit integers so we need to convert
1125 	// it to a number of bytes
1126 	offset -= 1;
1127 	offset *= 2;
1128 
1129 	// Seek to the requested part of the program
1130 	reader.seek(offset);
1131 
1132 	debugC(3, kDraciBytecodeDebugLevel,
1133 		"Starting GPL program at offset %d (program length: %d)", offset, program._length);
1134 
1135 	const GPL2Command *cmd;
1136 	do {
1137 
1138 		// Account for GPL jump that some commands set
1139 		if (_jump != 0) {
1140 			debugC(3, kDraciBytecodeDebugLevel,
1141 				"Jumping from offset %d to %d (%d bytes)",
1142 				(int)reader.pos(), (int)reader.pos() + _jump, _jump);
1143 			reader.seek(_jump, SEEK_CUR);
1144 		}
1145 
1146 		// Reset jump
1147 		_jump = 0;
1148 
1149 		// Clear any parameters left on the stack from the previous command
1150 		// This likely won't be needed once all commands are implemented
1151 		params.clear();
1152 
1153 		// read in command pair
1154 		uint16 cmdpair = reader.readUint16BE();
1155 
1156 		// extract high byte, i.e. the command number
1157 		byte num = (cmdpair >> 8) & 0xFF;
1158 
1159 		// extract low byte, i.e. the command subnumber
1160 		byte subnum = cmdpair & 0xFF;
1161 
1162 		if ((cmd = findCommand(num, subnum))) {
1163 			int tmp;
1164 
1165 			// Print command name
1166 			debugC(1, kDraciBytecodeDebugLevel, "%s", cmd->_name);
1167 
1168 			for (int i = 0; i < cmd->_numParams; ++i) {
1169 				if (cmd->_paramTypes[i] == kGPL2Math) {
1170 					debugC(3, kDraciBytecodeDebugLevel,
1171 					    "Evaluating (in-script) GPL expression at offset %d: ", offset);
1172 					params.push_back(handleMathExpression(&reader));
1173 				} else {
1174 					tmp = reader.readSint16LE();
1175 					params.push_back(tmp);
1176 					debugC(2, kDraciBytecodeDebugLevel, "\t%d", tmp);
1177 				}
1178 			}
1179 		} else {
1180 			error("Unknown opcode %d, %d", num, subnum);
1181 		}
1182 
1183 		GPLHandler handler = cmd->_handler;
1184 
1185 		if (handler != 0) {
1186 			// Call the handler for the current command
1187 			(this->*(cmd->_handler))(params);
1188 		}
1189 
1190 	} while (cmd->_number != 0 && !shouldEndProgram());    // 0 = gplend and exit
1191 
1192 	_jump = oldJump;
1193 
1194 	// Reset the flags which may have temporarily been altered inside the script.
1195 	_vm->_game->setEnableQuickHero(true);
1196 	_vm->_game->setEnableSpeedText(true);
1197 }
1198 
runWrapper(const GPL2Program & program,uint16 offset,bool disableCursor,bool releaseAnims)1199 void Script::runWrapper(const GPL2Program &program, uint16 offset, bool disableCursor, bool releaseAnims) {
1200 	if (disableCursor) {
1201 		// Fetch the dedicated objects' title animation / current frame
1202 		Animation *titleAnim = _vm->_anims->getAnimation(kTitleText);
1203 		titleAnim->markDirtyRect(_vm->_screen->getSurface());
1204 		Text *title = reinterpret_cast<Text *>(titleAnim->getCurrentFrame());
1205 		title->setText("");
1206 
1207 		_vm->_mouse->cursorOff();
1208 	}
1209 
1210 	// Mark last animation
1211 	int lastAnimIndex = _vm->_anims->getLastIndex();
1212 
1213 	run(program, offset);
1214 
1215 	if (releaseAnims) {
1216 			_vm->_game->deleteAnimationsAfterIndex(lastAnimIndex);
1217 	}
1218 
1219 	if (disableCursor) {
1220 		_vm->_mouse->cursorOn();
1221 	}
1222 }
1223 
1224 } // End of namespace Draci
1225