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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
526 _vm->_game->setMarkedAnimationIndex(_vm->_anims->getLastIndex());
527 }
528
release(const Common::Array<int> & params)529 void Script::release(const Common::Array<int> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
834 _vm->_game->setDialogueExit(true);
835 }
836
roomMap(const Common::Array<int> & params)837 void Script::roomMap(const Common::Array<int> ¶ms) {
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> ¶ms) {
843 _vm->_game->setEnableQuickHero(false);
844 }
845
enableQuickHero(const Common::Array<int> & params)846 void Script::enableQuickHero(const Common::Array<int> ¶ms) {
847 _vm->_game->setEnableQuickHero(true);
848 }
849
disableSpeedText(const Common::Array<int> & params)850 void Script::disableSpeedText(const Common::Array<int> ¶ms) {
851 _vm->_game->setEnableSpeedText(false);
852 }
853
enableSpeedText(const Common::Array<int> & params)854 void Script::enableSpeedText(const Common::Array<int> ¶ms) {
855 _vm->_game->setEnableSpeedText(true);
856 }
857
loadPalette(const Common::Array<int> & params)858 void Script::loadPalette(const Common::Array<int> ¶ms) {
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> ¶ms) {
865
866 _vm->_game->schedulePalette(kBlackPalette);
867 }
868
fadePalette(const Common::Array<int> & params)869 void Script::fadePalette(const Common::Array<int> ¶ms) {
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> ¶ms) {
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> ¶ms) {
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> ¶ms) {
902 _vm->_game->setQuit(true);
903 }
904
pushNewRoom(const Common::Array<int> & params)905 void Script::pushNewRoom(const Common::Array<int> ¶ms) {
906 _vm->_game->pushNewRoom();
907 }
908
popNewRoom(const Common::Array<int> & params)909 void Script::popNewRoom(const Common::Array<int> ¶ms) {
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