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 * MIT License:
22 *
23 * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
24 *
25 * Permission is hereby granted, free of charge, to any person
26 * obtaining a copy of this software and associated documentation
27 * files (the "Software"), to deal in the Software without
28 * restriction, including without limitation the rights to use,
29 * copy, modify, merge, publish, distribute, sublicense, and/or sell
30 * copies of the Software, and to permit persons to whom the
31 * Software is furnished to do so, subject to the following
32 * conditions:
33 *
34 * The above copyright notice and this permission notice shall be
35 * included in all copies or substantial portions of the Software.
36 *
37 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
38 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
39 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
40 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
41 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
42 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
43 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
44 * OTHER DEALINGS IN THE SOFTWARE.
45 *
46 */
47
48 #include "wage/wage.h"
49 #include "wage/entities.h"
50 #include "wage/script.h"
51 #include "wage/world.h"
52
53 #include "common/config-manager.h"
54 #include "common/file.h"
55 #include "common/stream.h"
56
57 namespace Wage {
58
toString(const Designed * d)59 static Common::String toString(const Designed *d) {
60 if (!d)
61 return "<NULL>";
62 else
63 return d->toString();
64 }
65
toString(const Common::String * d)66 static Common::String toString(const Common::String *d) {
67 if (!d)
68 return "<NULL>";
69 else
70 return *d;
71 }
72
toString(int16 val)73 static Common::String toString(int16 val) {
74 return Common::String::format("%d", val);
75 }
76
toString() const77 Common::String Script::Operand::toString() const {
78 switch(_type) {
79 case NUMBER:
80 return Wage::toString(_value.number);
81 case STRING:
82 case TEXT_INPUT:
83 return Wage::toString(_value.string);
84 case OBJ:
85 return Wage::toString(_value.obj);
86 case CHR:
87 return Wage::toString(_value.chr);
88 case SCENE:
89 return Wage::toString(_value.scene);
90 case CLICK_INPUT:
91 return Wage::toString(_value.inputClick);
92 default:
93 error("Unhandled operand type: %d", (int)_type);
94 }
95 }
96
Script(Common::SeekableReadStream * data,int num,WageEngine * engine)97 Script::Script(Common::SeekableReadStream *data, int num, WageEngine *engine) : _data(data), _engine(engine) {
98 _world = NULL;
99
100 _loopCount = 0;
101 _inputText = NULL;
102 _inputClick = NULL;
103
104 _handled = false;
105
106 convertToText();
107
108 if (ConfMan.getBool("dump_scripts")) {
109 Common::DumpFile out;
110 Common::String name;
111
112 if (num == -1)
113 name = Common::String::format("./dumps/%s-global.txt", _engine->getTargetName());
114 else
115 name = Common::String::format("./dumps/%s-%d.txt", _engine->getTargetName(), num);
116
117 if (!out.open(name)) {
118 warning("Can not open dump file %s", name.c_str());
119 return;
120 }
121
122 for (uint i = 0; i < _scriptText.size(); i++) {
123 out.write(_scriptText[i]->line.c_str(), strlen(_scriptText[i]->line.c_str()));
124 out.writeByte('\n');
125 }
126
127 out.flush();
128 out.close();
129 }
130 }
131
~Script()132 Script::~Script() {
133 for (uint i = 0; i < _scriptText.size(); i++) {
134 delete _scriptText[i];
135 }
136
137 delete _data;
138 }
139
print()140 void Script::print() {
141 for (uint i = 0; i < _scriptText.size(); i++) {
142 debug(4, "%d [%04x]: %s", i, _scriptText[i]->offset, _scriptText[i]->line.c_str());
143 }
144 }
145
printLine(int offset)146 void Script::printLine(int offset) {
147 for (uint i = 0; i < _scriptText.size(); i++)
148 if (_scriptText[i]->offset >= offset) {
149 debug(4, "%d [%04x]: %s", i, _scriptText[i]->offset, _scriptText[i]->line.c_str());
150 break;
151 }
152 }
153
preprocessInputText(Common::String inputText)154 Common::String Script::preprocessInputText(Common::String inputText) {
155 if (inputText.empty())
156 return inputText;
157
158 // "take " and "pick up " are mapped to "get " before script
159 // execution, so scripts see them as "get ", but only if there's
160 // a trailing space
161 inputText.toLowercase();
162
163 if (inputText.hasPrefix("take "))
164 return Common::String("get ") + (inputText.c_str() + strlen("take "));
165
166 if (inputText.hasPrefix("pick up "))
167 return Common::String("get ") + (inputText.c_str() + strlen("pick up "));
168
169 if (inputText.hasPrefix("put on "))
170 return Common::String("wear ") + (inputText.c_str() + strlen("put on "));
171
172 // exact aliases:
173 if (inputText.size() == 1) {
174 if (inputText.equals("n"))
175 return "north";
176
177 if (inputText.equals("e"))
178 return "east";
179
180 if (inputText.equals("s"))
181 return "south";
182
183 if (inputText.equals("w"))
184 return "west";
185 }
186
187 if (inputText.equals("wait")) {
188 return "rest";
189 }
190
191 return inputText;
192 }
193
execute(World * world,int loopCount,Common::String * inputText,Designed * inputClick)194 bool Script::execute(World *world, int loopCount, Common::String *inputText, Designed *inputClick) {
195 _world = world;
196 _loopCount = loopCount;
197 _inputText = inputText;
198 _inputClick = inputClick;
199 _handled = false;
200 Common::String input;
201
202 if (inputText) {
203 input = preprocessInputText(*_inputText);
204 debug(2, "Input was: '%s' is '%s'", _inputText->c_str(), input.c_str());
205 _inputText = new Common::String(input);
206 }
207
208 _data->seek(12);
209 while (_data->pos() < _data->size() && !_engine->_shouldQuit) {
210 printLine(_data->pos());
211
212 byte command = _data->readByte();
213
214 switch(command) {
215 case 0x80: // IF
216 processIf();
217 break;
218 case 0x87: // EXIT
219 debug(6, "exit at offset %d", _data->pos() - 1);
220
221 return true;
222 case 0x89: // MOVE
223 {
224 Scene *currentScene = _world->_player->_currentScene;
225 processMove();
226 if (_world->_player->_currentScene != currentScene)
227 return true;
228 break;
229 }
230 case 0x8B: // PRINT
231 {
232 Operand *op = readOperand();
233 // TODO check op type is string or number, or something good...
234 _handled = true;
235 _engine->appendText(op->toString().c_str());
236 delete op;
237 byte d = _data->readByte();
238 if (d != 0xFD)
239 warning("Operand 0x8B (PRINT) End Byte != 0xFD");
240 break;
241 }
242 case 0x8C: // SOUND
243 {
244 Operand *op = readOperand();
245 // TODO check op type is string.
246 _handled = true;
247 _engine->playSound(op->toString());
248 delete op;
249 byte d = _data->readByte();
250 if (d != 0xFD)
251 warning("Operand 0x8B (PRINT) End Byte != 0xFD");
252 break;
253 }
254 case 0x8E: // LET
255 processLet();
256 break;
257 case 0x95: // MENU
258 {
259 Operand *op = readStringOperand(); // allows empty menu
260 // TODO check op type is string.
261 _engine->setMenu(op->toString());
262 delete op;
263 byte d = _data->readByte();
264 if (d != 0xFD)
265 warning("Operand 0x8B (PRINT) End Byte != 0xFD");
266 }
267 case 0x88: // END
268 break;
269 default:
270 debug(0, "Unknown opcode: %d", _data->pos());
271 }
272 }
273
274 if (_world->_globalScript != this) {
275 debug(1, "Executing global script...");
276 bool globalHandled = _world->_globalScript->execute(_world, _loopCount, &input, _inputClick);
277 if (globalHandled)
278 _handled = true;
279 } else if (!input.empty()) {
280 if (input.contains("north")) {
281 _handled = _engine->handleMoveCommand(NORTH, "north");
282 } else if (input.contains("east")) {
283 _handled = _engine->handleMoveCommand(EAST, "east");
284 } else if (input.contains("south")) {
285 _handled = _engine->handleMoveCommand(SOUTH, "south");
286 } else if (input.contains("west")) {
287 _handled = _engine->handleMoveCommand(WEST, "west");
288 } else if (input.hasPrefix("get ")) {
289 _handled = _engine->handleTakeCommand(&input.c_str()[4]);
290 } else if (input.hasPrefix("drop ")) {
291 _handled = _engine->handleDropCommand(&input.c_str()[5]);
292 } else if (input.hasPrefix("aim ")) {
293 _handled = _engine->handleAimCommand(&input.c_str()[4]);
294 } else if (input.hasPrefix("wear ")) {
295 _handled = _engine->handleWearCommand(&input.c_str()[5]);
296 } else if (input.hasPrefix("offer ")) {
297 _handled = _engine->handleOfferCommand(&input.c_str()[6]);
298 } else if (input.contains("look")) {
299 _handled = _engine->handleLookCommand();
300 } else if (input.contains("inven")) {
301 _handled = _engine->handleInventoryCommand();
302 } else if (input.contains("status")) {
303 _handled = _engine->handleStatusCommand();
304 } else if (input.contains("rest")) {
305 _handled = _engine->handleRestCommand();
306 } else if (_engine->getOffer() != NULL && input.contains("accept")) {
307 _handled = _engine->handleAcceptCommand();
308 } else {
309 Chr *player = _world->_player;
310 ObjArray *weapons = player->getWeapons(true);
311 for (ObjArray::const_iterator weapon = weapons->begin(); weapon != weapons->end(); ++weapon) {
312 if (_engine->tryAttack(*weapon, input)) {
313 _handled = _engine->handleAttack(*weapon);
314 break;
315 }
316 }
317
318 delete weapons;
319 }
320 // TODO: weapons, offer, etc...
321 } else if (_inputClick->_classType == OBJ) {
322 Obj *obj = (Obj *)_inputClick;
323 if (obj->_type != Obj::IMMOBILE_OBJECT) {
324 _engine->takeObj(obj);
325 } else {
326 _engine->appendText(obj->_clickMessage.c_str());
327 }
328
329 _handled = true;
330 }
331
332 return _handled;
333 }
334
readOperand()335 Script::Operand *Script::readOperand() {
336 byte operandType = _data->readByte();
337
338 debug(7, "%x: readOperand: 0x%x", _data->pos(), operandType);
339
340 Context *cont = &_world->_player->_context;
341 switch (operandType) {
342 case 0xA0: // TEXT$
343 return new Operand(_inputText, TEXT_INPUT);
344 case 0xA1:
345 return new Operand(_inputClick, CLICK_INPUT);
346 case 0xC0: // STORAGE@
347 return new Operand(_world->_storageScene, SCENE);
348 case 0xC1: // SCENE@
349 return new Operand(_world->_player->_currentScene, SCENE);
350 case 0xC2: // PLAYER@
351 return new Operand(_world->_player, CHR);
352 case 0xC3: // MONSTER@
353 return new Operand(_engine->getMonster(), CHR);
354 case 0xC4: // RANDOMSCN@
355 return new Operand(_world->_orderedScenes[_engine->_rnd->getRandomNumber(_world->_orderedScenes.size())], SCENE);
356 case 0xC5: // RANDOMCHR@
357 return new Operand(_world->_orderedChrs[_engine->_rnd->getRandomNumber(_world->_orderedChrs.size())], CHR);
358 case 0xC6: // RANDOMOBJ@
359 return new Operand(_world->_orderedObjs[_engine->_rnd->getRandomNumber(_world->_orderedObjs.size())], OBJ);
360 case 0xB0: // VISITS#
361 return new Operand(cont->_visits, NUMBER);
362 case 0xB1: // RANDOM# for Star Trek, but VISITS# for some other games?
363 return new Operand(1 + _engine->_rnd->getRandomNumber(100), NUMBER);
364 case 0xB5: // RANDOM# // A random number between 1 and 100.
365 return new Operand(1 + _engine->_rnd->getRandomNumber(100), NUMBER);
366 case 0xB2: // LOOP#
367 return new Operand(_loopCount, NUMBER);
368 case 0xB3: // VICTORY#
369 return new Operand(cont->_kills, NUMBER);
370 case 0xB4: // BADCOPY#
371 return new Operand(0, NUMBER); // \?\?\??
372 case 0xFF:
373 {
374 // user variable
375 int value = _data->readByte();
376
377 // TODO: Verify that we're using the right index.
378 return new Operand(cont->_userVariables[value - 1], NUMBER);
379 }
380 case 0xD0:
381 return new Operand(cont->_statVariables[PHYS_STR_BAS], NUMBER);
382 case 0xD1:
383 return new Operand(cont->_statVariables[PHYS_HIT_BAS], NUMBER);
384 case 0xD2:
385 return new Operand(cont->_statVariables[PHYS_ARM_BAS], NUMBER);
386 case 0xD3:
387 return new Operand(cont->_statVariables[PHYS_ACC_BAS], NUMBER);
388 case 0xD4:
389 return new Operand(cont->_statVariables[SPIR_STR_BAS], NUMBER);
390 case 0xD5:
391 return new Operand(cont->_statVariables[SPIR_HIT_BAS], NUMBER);
392 case 0xD6:
393 return new Operand(cont->_statVariables[SPIR_ARM_BAS], NUMBER);
394 case 0xD7:
395 return new Operand(cont->_statVariables[SPIR_ACC_BAS], NUMBER);
396 case 0xD8:
397 return new Operand(cont->_statVariables[PHYS_SPE_BAS], NUMBER);
398 case 0xE0:
399 return new Operand(cont->_statVariables[PHYS_STR_CUR], NUMBER);
400 case 0xE1:
401 return new Operand(cont->_statVariables[PHYS_HIT_CUR], NUMBER);
402 case 0xE2:
403 return new Operand(cont->_statVariables[PHYS_ARM_CUR], NUMBER);
404 case 0xE3:
405 return new Operand(cont->_statVariables[PHYS_ACC_CUR], NUMBER);
406 case 0xE4:
407 return new Operand(cont->_statVariables[SPIR_STR_CUR], NUMBER);
408 case 0xE5:
409 return new Operand(cont->_statVariables[SPIR_HIT_CUR], NUMBER);
410 case 0xE6:
411 return new Operand(cont->_statVariables[SPIR_ARM_CUR], NUMBER);
412 case 0xE7:
413 return new Operand(cont->_statVariables[SPIR_ACC_CUR], NUMBER);
414 case 0xE8:
415 return new Operand(cont->_statVariables[PHYS_SPE_CUR], NUMBER);
416 default:
417 if (operandType >= 0x20 && operandType < 0x80) {
418 _data->seek(-1, SEEK_CUR);
419 return readStringOperand();
420 } else {
421 debug("Dunno what %x is (index=%d)!\n", operandType, _data->pos()-1);
422 }
423 return NULL;
424 }
425 }
426
assign(byte operandType,int uservar,uint16 value)427 void Script::assign(byte operandType, int uservar, uint16 value) {
428 Context *cont = &_world->_player->_context;
429
430 switch (operandType) {
431 case 0xFF:
432 cont->_userVariables[uservar - 1] = value;
433 break;
434 case 0xD0:
435 cont->_statVariables[PHYS_STR_BAS] = value;
436 break;
437 case 0xD1:
438 cont->_statVariables[PHYS_HIT_BAS] = value;
439 break;
440 case 0xD2:
441 cont->_statVariables[PHYS_ARM_BAS] = value;
442 break;
443 case 0xD3:
444 cont->_statVariables[PHYS_ACC_BAS] = value;
445 break;
446 case 0xD4:
447 cont->_statVariables[SPIR_STR_BAS] = value;
448 break;
449 case 0xD5:
450 cont->_statVariables[SPIR_HIT_BAS] = value;
451 break;
452 case 0xD6:
453 cont->_statVariables[SPIR_ARM_BAS] = value;
454 break;
455 case 0xD7:
456 cont->_statVariables[SPIR_ACC_BAS] = value;
457 break;
458 case 0xD8:
459 cont->_statVariables[PHYS_SPE_BAS] = value;
460 break;
461 case 0xE0:
462 cont->_statVariables[PHYS_STR_CUR] = value;
463 break;
464 case 0xE1:
465 cont->_statVariables[PHYS_HIT_CUR] = value;
466 break;
467 case 0xE2:
468 cont->_statVariables[PHYS_ARM_CUR] = value;
469 break;
470 case 0xE3:
471 cont->_statVariables[PHYS_ACC_CUR] = value;
472 break;
473 case 0xE4:
474 cont->_statVariables[SPIR_STR_CUR] = value;
475 break;
476 case 0xE5:
477 cont->_statVariables[SPIR_HIT_CUR] = value;
478 break;
479 case 0xE6:
480 cont->_statVariables[SPIR_ARM_CUR] = value;
481 break;
482 case 0xE7:
483 cont->_statVariables[SPIR_ACC_CUR] = value;
484 break;
485 case 0xE8:
486 cont->_statVariables[PHYS_SPE_CUR] = value;
487 break;
488 default:
489 debug("No idea what I'm supposed to assign! (%x at %d)!\n", operandType, _data->pos()-1);
490 }
491 }
492
readStringOperand()493 Script::Operand *Script::readStringOperand() {
494 Common::String *str;
495 bool allDigits = true;
496
497 str = new Common::String();
498
499 while (true) {
500 byte c = _data->readByte();
501 if (c >= 0x20 && c < 0x80)
502 *str += c;
503 else
504 break;
505 if ((c < '0' || c > '9') && !(c == '-' && str->empty()))
506 allDigits = false;
507 }
508 _data->seek(-1, SEEK_CUR);
509
510 if (allDigits && !str->empty()) {
511 int r = atol(str->c_str());
512 delete str;
513
514 return new Operand(r, NUMBER);
515 } else {
516 // TODO: This string could be a room name or something like that.
517 return new Operand(str, STRING);
518 }
519 }
520
readOperator()521 const char *Script::readOperator() {
522 byte cmd = _data->readByte();
523
524 debug(7, "readOperator: 0x%x", cmd);
525 switch (cmd) {
526 case 0x81:
527 return "=";
528 case 0x82:
529 return "<";
530 case 0x83:
531 return ">";
532 case 0x8f:
533 return "+";
534 case 0x90:
535 return "-";
536 case 0x91:
537 return "*";
538 case 0x92:
539 return "/";
540 case 0x93:
541 return "==";
542 case 0x94:
543 return ">>";
544 case 0xfd:
545 return ";";
546 default:
547 warning("UNKNOWN OP %x", cmd);
548 }
549 return NULL;
550 }
551
processIf()552 void Script::processIf() {
553 int logicalOp = 0; // 0 => initial, 1 => and, 2 => or
554 bool result = true;
555 bool done = false;
556
557 do {
558 Operand *lhs = readOperand();
559 const char *op = readOperator();
560 Operand *rhs = readOperand();
561
562 bool condResult = eval(lhs, op, rhs);
563
564 delete lhs;
565 delete rhs;
566
567 if (logicalOp == 1) {
568 result = (result && condResult);
569 } else if (logicalOp == 2) {
570 result = (result || condResult);
571 } else { // logicalOp == 0
572 result = condResult;
573 }
574
575 byte logical = _data->readByte();
576
577 if (logical == 0x84) {
578 logicalOp = 1; // and
579 } else if (logical == 0x85) {
580 logicalOp = 2; // or
581 } else if (logical == 0xFE) {
582 done = true; // then
583 }
584 } while (!done);
585
586 if (result == false) {
587 skipBlock();
588 }
589 }
590
skipIf()591 void Script::skipIf() {
592 do {
593 Operand *lhs = readOperand();
594 readOperator();
595 Operand *rhs = readOperand();
596
597 delete lhs;
598 delete rhs;
599 } while (_data->readByte() != 0xFE);
600 }
601
skipBlock()602 void Script::skipBlock() {
603 int nesting = 1;
604
605 while (true) {
606 byte op = _data->readByte();
607
608 if (_data->eos())
609 return;
610
611 if (op == 0x80) { // IF
612 nesting++;
613 skipIf();
614 } else if (op == 0x88 || op == 0x87) { // END or EXIT
615 nesting--;
616 if (nesting == 0) {
617 return;
618 }
619 } else switch (op) {
620 case 0x8B: // PRINT
621 case 0x8C: // SOUND
622 case 0x8E: // LET
623 case 0x95: // MENU
624 while (_data->readByte() != 0xFD)
625 ;
626 }
627 }
628 }
629
630 enum {
631 kCompEqNumNum,
632 kCompEqObjScene,
633 kCompEqChrScene,
634 kCompEqObjChr,
635 kCompEqChrChr,
636 kCompEqSceneScene,
637 kCompEqStringTextInput,
638 kCompEqTextInputString,
639 kCompEqNumberTextInput,
640 kCompEqTextInputNumber,
641 kCompLtNumNum,
642 kCompLtStringTextInput,
643 kCompLtTextInputString,
644 kCompLtObjChr,
645 kCompLtChrObj,
646 kCompLtObjScene,
647 kCompGtNumNum,
648 kCompGtStringString,
649 kCompGtChrScene,
650 kMoveObjChr,
651 kMoveObjScene,
652 kMoveChrScene
653 };
654
655 static const char *typeNames[] = {
656 "OBJ",
657 "CHR",
658 "SCENE",
659 "NUMBER",
660 "STRING",
661 "CLICK_INPUT",
662 "TEXT_INPUT"
663 };
664
operandTypeToStr(int type)665 static const char *operandTypeToStr(int type) {
666 if (type < 0 || type > 6)
667 return "UNKNOWN";
668
669 return typeNames[type];
670 }
671
672 struct Comparator {
673 char op;
674 OperandType o1;
675 OperandType o2;
676 int cmp;
677 } static comparators[] = {
678 { '=', NUMBER, NUMBER, kCompEqNumNum },
679 { '=', OBJ, SCENE, kCompEqObjScene },
680 { '=', CHR, SCENE, kCompEqChrScene },
681 { '=', OBJ, CHR, kCompEqObjChr },
682 { '=', CHR, CHR, kCompEqChrChr },
683 { '=', SCENE, SCENE, kCompEqSceneScene },
684 { '=', STRING, TEXT_INPUT, kCompEqStringTextInput },
685 { '=', TEXT_INPUT, STRING, kCompEqTextInputString },
686 { '=', NUMBER, TEXT_INPUT, kCompEqNumberTextInput },
687 { '=', TEXT_INPUT, NUMBER, kCompEqTextInputNumber },
688
689 { '<', NUMBER, NUMBER, kCompLtNumNum },
690 { '<', STRING, TEXT_INPUT, kCompLtStringTextInput },
691 { '<', TEXT_INPUT, STRING, kCompLtTextInputString },
692 { '<', OBJ, CHR, kCompLtObjChr },
693 { '<', CHR, OBJ, kCompLtChrObj },
694 { '<', OBJ, SCENE, kCompLtObjScene },
695 { '<', CHR, CHR, kCompEqChrChr }, // Same logic as =
696 { '<', SCENE, SCENE, kCompEqSceneScene },
697 { '<', CHR, SCENE, kCompGtChrScene }, // Same logic as >
698
699 { '>', NUMBER, NUMBER, kCompGtNumNum },
700 { '>', TEXT_INPUT, STRING, kCompLtTextInputString }, // Same logic as <
701 //FIXME: this prevents the below cases from working due to exact
702 //matches taking precedence over conversions...
703 //{ '>', STRING, STRING, kCompGtStringString }, // Same logic as <
704 { '>', OBJ, CHR, kCompLtObjChr }, // Same logic as <
705 { '>', OBJ, SCENE, kCompLtObjScene }, // Same logic as <
706 { '>', CHR, SCENE, kCompGtChrScene },
707
708 { 'M', OBJ, CHR, kMoveObjChr },
709 { 'M', OBJ, SCENE, kMoveObjScene },
710 { 'M', CHR, SCENE, kMoveChrScene },
711 { 0, OBJ, OBJ, 0 }
712 };
713
compare(Operand * o1,Operand * o2,int comparator)714 bool Script::compare(Operand *o1, Operand *o2, int comparator) {
715 switch(comparator) {
716 case kCompEqNumNum:
717 return o1->_value.number == o2->_value.number;
718 case kCompEqObjScene:
719 for (ObjList::const_iterator it = o2->_value.scene->_objs.begin(); it != o2->_value.scene->_objs.end(); ++it)
720 if (*it == o1->_value.obj)
721 return true;
722 return false;
723 case kCompEqChrScene:
724 for (ChrList::const_iterator it = o2->_value.scene->_chrs.begin(); it != o2->_value.scene->_chrs.end(); ++it)
725 if (*it == o1->_value.chr)
726 return true;
727 return false;
728 case kCompEqObjChr:
729 for (ObjArray::const_iterator it = o2->_value.chr->_inventory.begin(); it != o2->_value.chr->_inventory.end(); ++it)
730 if (*it == o1->_value.obj)
731 return true;
732 return false;
733 case kCompEqChrChr:
734 return o1->_value.chr == o2->_value.chr;
735 case kCompEqSceneScene:
736 return o1->_value.scene == o2->_value.scene;
737 case kCompEqStringTextInput:
738 if (_inputText == NULL) {
739 return false;
740 } else {
741 Common::String s1(*_inputText), s2(*o1->_value.string);
742 s1.toLowercase();
743 s2.toLowercase();
744
745 return s1.contains(s2);
746 }
747 case kCompEqTextInputString:
748 return compare(o2, o1, kCompEqStringTextInput);
749 case kCompEqNumberTextInput:
750 if (_inputText == NULL) {
751 return false;
752 } else {
753 Common::String s1(*_inputText), s2(o1->toString());
754 s1.toLowercase();
755 s2.toLowercase();
756
757 return s1.contains(s2);
758 }
759 case kCompEqTextInputNumber:
760 if (_inputText == NULL) {
761 return false;
762 } else {
763 Common::String s1(*_inputText), s2(o2->toString());
764 s1.toLowercase();
765 s2.toLowercase();
766
767 return s1.contains(s2);
768 }
769 case kCompLtNumNum:
770 return o1->_value.number < o2->_value.number;
771 case kCompLtStringTextInput:
772 return !compare(o1, o2, kCompEqStringTextInput);
773 case kCompLtTextInputString:
774 return !compare(o2, o1, kCompEqStringTextInput);
775 case kCompLtObjChr:
776 return o1->_value.obj->_currentOwner != o2->_value.chr;
777 case kCompLtChrObj:
778 return compare(o2, o1, kCompLtObjChr);
779 case kCompLtObjScene:
780 return o1->_value.obj->_currentScene != o2->_value.scene;
781 case kCompGtNumNum:
782 return o1->_value.number > o2->_value.number;
783 case kCompGtStringString:
784 return o1->_value.string == o2->_value.string;
785 case kCompGtChrScene:
786 return (o1->_value.chr != NULL && o1->_value.chr->_currentScene != o2->_value.scene);
787 case kMoveObjChr:
788 if (o1->_value.obj->_currentOwner != o2->_value.chr) {
789 _world->move(o1->_value.obj, o2->_value.chr);
790 _handled = true; // TODO: Is this correct?
791 }
792 break;
793 case kMoveObjScene:
794 if (o1->_value.obj->_currentScene != o2->_value.scene) {
795 _world->move(o1->_value.obj, o2->_value.scene);
796 // Note: This shouldn't call setHandled() - see
797 // Sultan's Palace 'Food and Drink' scene.
798 }
799 break;
800 case kMoveChrScene:
801 _world->move(o1->_value.chr, o2->_value.scene);
802 _handled = true; // TODO: Is this correct?
803 break;
804 }
805
806 return false;
807 }
808
evaluatePair(Operand * lhs,const char * op,Operand * rhs)809 bool Script::evaluatePair(Operand *lhs, const char *op, Operand *rhs) {
810 debug(7, "HANDLING CASE: [lhs=%s/%s, op=%s rhs=%s/%s]",
811 operandTypeToStr(lhs->_type), lhs->toString().c_str(), op, operandTypeToStr(rhs->_type), rhs->toString().c_str());
812
813 for (int cmp = 0; comparators[cmp].op != 0; cmp++) {
814 if (comparators[cmp].op != op[0])
815 continue;
816
817 if (comparators[cmp].o1 == lhs->_type && comparators[cmp].o2 == rhs->_type)
818 return compare(lhs, rhs, comparators[cmp].cmp);
819 }
820
821 // Now, try partial matches.
822 Operand *c1, *c2;
823 for (int cmp = 0; comparators[cmp].op != 0; cmp++) {
824 if (comparators[cmp].op != op[0])
825 continue;
826
827 if (comparators[cmp].o1 == lhs->_type &&
828 (c2 = convertOperand(rhs, comparators[cmp].o2)) != NULL) {
829 bool res = compare(lhs, c2, comparators[cmp].cmp);
830 delete c2;
831 return res;
832 } else if (comparators[cmp].o2 == rhs->_type &&
833 (c1 = convertOperand(lhs, comparators[cmp].o1)) != NULL) {
834 bool res = compare(c1, rhs, comparators[cmp].cmp);
835 delete c1;
836 return res;
837 }
838 }
839
840 // Now, try double conversion.
841 for (int cmp = 0; comparators[cmp].op != 0; cmp++) {
842 if (comparators[cmp].op != op[0])
843 continue;
844
845 if (comparators[cmp].o1 == lhs->_type || comparators[cmp].o2 == rhs->_type)
846 continue;
847
848 if ((c1 = convertOperand(lhs, comparators[cmp].o1)) != NULL) {
849 if ((c2 = convertOperand(rhs, comparators[cmp].o2)) != NULL) {
850 bool res = compare(c1, c2, comparators[cmp].cmp);
851 delete c1;
852 delete c2;
853 return res;
854 }
855 delete c1;
856 }
857 }
858
859 warning("UNHANDLED CASE: [lhs=%s/%s, op=%s rhs=%s/%s]",
860 operandTypeToStr(lhs->_type), lhs->toString().c_str(), op, operandTypeToStr(rhs->_type), rhs->toString().c_str());
861
862 return false;
863 }
864
eval(Operand * lhs,const char * op,Operand * rhs)865 bool Script::eval(Operand *lhs, const char *op, Operand *rhs) {
866 bool result = false;
867
868 if (lhs->_type == CLICK_INPUT || rhs->_type == CLICK_INPUT) {
869 return evalClickCondition(lhs, op, rhs);
870 } else if (!strcmp(op, "==") || !strcmp(op, ">>")) {
871 // TODO: check if >> can be used for click inputs and if == can be used for other things
872 // exact string match
873 if (lhs->_type == TEXT_INPUT) {
874 if ((rhs->_type != STRING && rhs->_type != NUMBER) || _inputText == NULL) {
875 result = false;
876 } else {
877 result = _inputText->equalsIgnoreCase(rhs->toString());
878 }
879 } else if (rhs->_type == TEXT_INPUT) {
880 if ((lhs->_type != STRING && lhs->_type != NUMBER) || _inputText == NULL) {
881 result = false;
882 } else {
883 result = _inputText->equalsIgnoreCase(lhs->toString());
884 }
885 } else {
886 error("UNHANDLED CASE: [lhs=%s/%s, rhs=%s/%s]",
887 operandTypeToStr(lhs->_type), lhs->toString().c_str(), operandTypeToStr(rhs->_type), rhs->toString().c_str());
888 }
889 if (!strcmp(op, ">>")) {
890 result = !result;
891 }
892
893 return result;
894 } else {
895 return evaluatePair(lhs, op, rhs);
896 }
897
898 return false;
899 }
900
convertOperand(Operand * operand,int type)901 Script::Operand *Script::convertOperand(Operand *operand, int type) {
902 if (operand->_type == type)
903 error("Incorrect conversion to type %d", type);
904
905 if (type == SCENE) {
906 if (operand->_type == STRING || operand->_type == NUMBER) {
907 Common::String key(operand->toString());
908 key.toLowercase();
909 if (_world->_scenes.contains(key))
910 return new Operand(_world->_scenes[key], SCENE);
911 }
912 } else if (type == OBJ) {
913 if (operand->_type == STRING || operand->_type == NUMBER) {
914 Common::String key = operand->toString();
915 key.toLowercase();
916 if (_world->_objs.contains(key))
917 return new Operand(_world->_objs[key], OBJ);
918 } else if (operand->_type == CLICK_INPUT) {
919 if (_inputClick->_classType == OBJ)
920 return new Operand(_inputClick, OBJ);
921 }
922 } else if (type == CHR) {
923 if (operand->_type == STRING || operand->_type == NUMBER) {
924 Common::String key = operand->toString();
925 key.toLowercase();
926 if (_world->_chrs.contains(key))
927 return new Operand(_world->_chrs[key], CHR);
928 } else if (operand->_type == CLICK_INPUT) {
929 if (_inputClick->_classType == CHR)
930 return new Operand(_inputClick, CHR);
931 }
932 }
933
934 return NULL;
935 }
936
evalClickEquality(Operand * lhs,Operand * rhs,bool partialMatch)937 bool Script::evalClickEquality(Operand *lhs, Operand *rhs, bool partialMatch) {
938 bool result = false;
939 if (lhs->_value.obj == NULL || rhs->_value.obj == NULL) {
940 result = false;
941 } else if (lhs->_value.obj == rhs->_value.obj) {
942 result = true;
943 } else if (rhs->_type == STRING) {
944 Common::String str = rhs->toString();
945 str.toLowercase();
946
947 debug(9, "evalClickEquality(%s, %s, %d)", lhs->_value.designed->_name.c_str(), rhs->_value.designed->_name.c_str(), partialMatch);
948 debug(9, "l: %s r: %s)", operandTypeToStr(lhs->_type), operandTypeToStr(rhs->_type));
949 debug(9, "class: %d", lhs->_value.inputClick->_classType);
950
951 if (lhs->_value.inputClick->_classType == CHR || lhs->_value.inputClick->_classType == OBJ) {
952 Common::String name = lhs->_value.designed->_name;
953 name.toLowercase();
954
955 if (partialMatch)
956 result = name.contains(str);
957 else
958 result = name.equals(str);
959 }
960
961 debug(9, "result: %d", result);
962 }
963 return result;
964 }
965
evalClickCondition(Operand * lhs,const char * op,Operand * rhs)966 bool Script::evalClickCondition(Operand *lhs, const char *op, Operand *rhs) {
967 // TODO: check if >> can be used for click inputs
968 if (strcmp(op, "==") && strcmp(op, "=") && strcmp(op, "<") && strcmp(op, ">")) {
969 error("Unknown operation '%s' for Script::evalClickCondition", op);
970 }
971
972 bool partialMatch = strcmp(op, "==");
973 bool result;
974 if (lhs->_type == CLICK_INPUT) {
975 result = evalClickEquality(lhs, rhs, partialMatch);
976 } else {
977 result = evalClickEquality(rhs, lhs, partialMatch);
978 }
979 if (!strcmp(op, "<") || !strcmp(op, ">")) {
980 // CLICK$<FOO only matches if there was a click
981 if (_inputClick == NULL) {
982 result = false;
983 } else {
984 result = !result;
985 }
986 }
987 return result;
988 }
989
processMove()990 void Script::processMove() {
991 Operand *what = readOperand();
992 byte skip = _data->readByte();
993 if (skip != 0x8a)
994 error("Incorrect operator for MOVE: %02x", skip);
995
996 Operand *to = readOperand();
997
998 skip = _data->readByte();
999 if (skip != 0xfd)
1000 error("No end for MOVE: %02x", skip);
1001
1002 evaluatePair(what, "M", to);
1003
1004 delete what;
1005 delete to;
1006 }
1007
processLet()1008 void Script::processLet() {
1009 const char *lastOp = NULL;
1010 int16 result = 0;
1011 int operandType = _data->readByte();
1012 int uservar = 0;
1013
1014 if (operandType == 0xff) {
1015 uservar = _data->readByte();
1016 }
1017
1018 byte eq = _data->readByte(); // skip "=" operator
1019
1020 debug(7, "processLet: 0x%x, uservar: 0x%x, eq: 0x%x", operandType, uservar, eq);
1021
1022 do {
1023 Operand *operand = readOperand();
1024 // TODO assert that value is NUMBER
1025 int16 value = operand->_value.number;
1026 delete operand;
1027 if (lastOp != NULL) {
1028 if (lastOp[0] == '+')
1029 result += value;
1030 else if (lastOp[0] == '-')
1031 result -= value;
1032 else if (lastOp[0] == '/')
1033 result = (int16)(value == 0 ? 0 : result / value);
1034 else if (lastOp[0] == '*')
1035 result *= value;
1036 } else {
1037 result = value;
1038 }
1039 lastOp = readOperator();
1040
1041 if (lastOp[0] == ';')
1042 break;
1043 } while (true);
1044 //System.out.println("processLet " + buildStringFromOffset(oldIndex - 1, index - oldIndex + 1) + "}");
1045
1046 assign(operandType, uservar, result);
1047 }
1048
1049 enum {
1050 BLOCK_START,
1051 BLOCK_END,
1052 STATEMENT,
1053 OPERATOR,
1054 OPCODE
1055 };
1056
1057 struct Mapping {
1058 const char *cmd;
1059 int type;
1060 } static const mapping[] = {
1061 { "IF{", STATEMENT }, // 0x80
1062 { "=", OPERATOR },
1063 { "<", OPERATOR },
1064 { ">", OPERATOR },
1065 { "}AND{", OPCODE },
1066 { "}OR{", OPCODE },
1067 { "\?\?\?(0x86)", OPCODE },
1068 { "EXIT\n", BLOCK_END },
1069 { "END\n", BLOCK_END }, // 0x88
1070 { "MOVE{", STATEMENT },
1071 { "}TO{", OPCODE },
1072 { "PRINT{", STATEMENT },
1073 { "SOUND{", STATEMENT },
1074 { "\?\?\?(0x8d)", OPCODE },
1075 { "LET{", STATEMENT },
1076 { "+", OPERATOR },
1077 { "-", OPERATOR }, // 0x90
1078 { "*", OPERATOR },
1079 { "/", OPERATOR },
1080 { "==", OPERATOR },
1081 { ">>", OPERATOR },
1082 { "MENU{", STATEMENT },
1083 { "\?\?\?(0x96)", OPCODE },
1084 { "\?\?\?(0x97)", OPCODE },
1085 { "\?\?\?(0x98)", OPCODE }, // 0x98
1086 { "\?\?\?(0x99)", OPCODE },
1087 { "\?\?\?(0x9a)", OPCODE },
1088 { "\?\?\?(0x9b)", OPCODE },
1089 { "\?\?\?(0x9c)", OPCODE },
1090 { "\?\?\?(0x9d)", OPCODE },
1091 { "\?\?\?(0x9e)", OPCODE },
1092 { "\?\?\?(0x9f)", OPCODE },
1093 { "TEXT$", OPCODE }, // 0xa0
1094 { "CLICK$", OPCODE },
1095 { "\?\?\?(0xa2)", OPCODE },
1096 { "\?\?\?(0xa3)", OPCODE },
1097 { "\?\?\?(0xa4)", OPCODE },
1098 { "\?\?\?(0xa5)", OPCODE },
1099 { "\?\?\?(0xa6)", OPCODE },
1100 { "\?\?\?(0xa7)", OPCODE },
1101 { "\?\?\?(0xa8)", OPCODE }, // 0xa8
1102 { "\?\?\?(0xa9)", OPCODE },
1103 { "\?\?\?(0xaa)", OPCODE },
1104 { "\?\?\?(0xab)", OPCODE },
1105 { "\?\?\?(0xac)", OPCODE },
1106 { "\?\?\?(0xad)", OPCODE },
1107 { "\?\?\?(0xae)", OPCODE },
1108 { "\?\?\?(0xaf)", OPCODE },
1109 { "VISITS#", OPCODE }, // 0xb0 // The number of scenes the player has visited, including repeated visits.
1110 { "RANDOM#", OPCODE }, // RANDOM# for Star Trek, but VISITS# for some other games?
1111 { "LOOP#", OPCODE }, // The number of commands the player has given in the current scene.
1112 { "VICTORY#", OPCODE }, // The number of characters killed.
1113 { "BADCOPY#", OPCODE },
1114 { "RANDOM#", OPCODE }, // A random number between 1 and 100.
1115 { "\?\?\?(0xb6)", OPCODE },
1116 { "\?\?\?(0xb7)", OPCODE },
1117 { "\?\?\?(0xb8)", OPCODE }, // 0xb8
1118 { "\?\?\?(0xb9)", OPCODE },
1119 { "\?\?\?(0xba)", OPCODE },
1120 { "\?\?\?(0xbb)", OPCODE },
1121 { "\?\?\?(0xbc)", OPCODE },
1122 { "\?\?\?(0xbd)", OPCODE },
1123 { "\?\?\?(0xbe)", OPCODE },
1124 { "\?\?\?(0xbf)", OPCODE },
1125 { "STORAGE@", OPCODE }, // 0xc0
1126 { "SCENE@", OPCODE },
1127 { "PLAYER@", OPCODE },
1128 { "MONSTER@", OPCODE },
1129 { "RANDOMSCN@", OPCODE },
1130 { "RANDOMCHR@", OPCODE },
1131 { "RANDOMOBJ@", OPCODE },
1132 { "\?\?\?(0xc7)", OPCODE },
1133 { "\?\?\?(0xc8)", OPCODE }, // 0xc8
1134 { "\?\?\?(0xc9)", OPCODE },
1135 { "\?\?\?(0xca)", OPCODE },
1136 { "\?\?\?(0xcb)", OPCODE },
1137 { "\?\?\?(0xcc)", OPCODE },
1138 { "\?\?\?(0xcd)", OPCODE },
1139 { "\?\?\?(0xce)", OPCODE },
1140 { "\?\?\?(0xcf)", OPCODE },
1141 { "PHYS.STR.BAS#", OPCODE }, // 0xd0
1142 { "PHYS.HIT.BAS#", OPCODE },
1143 { "PHYS.ARM.BAS#", OPCODE },
1144 { "PHYS.ACC.BAS#", OPCODE },
1145 { "SPIR.STR.BAS#", OPCODE },
1146 { "SPIR.HIT.BAS#", OPCODE },
1147 { "SPIR.ARM.BAS#", OPCODE },
1148 { "SPIR.ACC.BAS#", OPCODE },
1149 { "PHYS.SPE.BAS#", OPCODE }, // 0xd8
1150 { "\?\?\?(0xd9)", OPCODE },
1151 { "\?\?\?(0xda)", OPCODE },
1152 { "\?\?\?(0xdb)", OPCODE },
1153 { "\?\?\?(0xdc)", OPCODE },
1154 { "\?\?\?(0xdd)", OPCODE },
1155 { "\?\?\?(0xde)", OPCODE },
1156 { "\?\?\?(0xdf)", OPCODE },
1157 { "PHYS.STR.CUR#", OPCODE }, // 0xe0
1158 { "PHYS.HIT.CUR#", OPCODE },
1159 { "PHYS.ARM.CUR#", OPCODE },
1160 { "PHYS.ACC.CUR#", OPCODE },
1161 { "SPIR.STR.CUR#", OPCODE },
1162 { "SPIR.HIT.CUR#", OPCODE },
1163 { "SPIR.ARM.CUR#", OPCODE },
1164 { "SPIR.ACC.CUR#", OPCODE },
1165 { "PHYS.SPE.CUR#", OPCODE }, // 0xe8
1166 { "\?\?\?(0xe9)", OPCODE },
1167 { "\?\?\?(0xea)", OPCODE },
1168 { "\?\?\?(0xeb)", OPCODE },
1169 { "\?\?\?(0xec)", OPCODE },
1170 { "\?\?\?(0xed)", OPCODE },
1171 { "\?\?\?(0xee)", OPCODE },
1172 { "\?\?\?(0xef)", OPCODE },
1173 { "\?\?\?(0xf0)", OPCODE },
1174 { "\?\?\?(0xf1)", OPCODE },
1175 { "\?\?\?(0xf2)", OPCODE },
1176 { "\?\?\?(0xf3)", OPCODE },
1177 { "\?\?\?(0xf4)", OPCODE },
1178 { "\?\?\?(0xf5)", OPCODE },
1179 { "\?\?\?(0xf6)", OPCODE },
1180 { "\?\?\?(0xf7)", OPCODE },
1181 { "\?\?\?(0xf8)", OPCODE }, // 0xf8
1182 { "\?\?\?(0xf9)", OPCODE },
1183 { "\?\?\?(0xfa)", OPCODE },
1184 { "\?\?\?(0xfb)", OPCODE },
1185 { "\?\?\?(0xfc)", OPCODE },
1186 { "}\n", OPCODE },
1187 { "}THEN\n", BLOCK_START },
1188 { "\?\?\?(0xff)", OPCODE } // Uservar
1189 };
1190
convertToText()1191 void Script::convertToText() {
1192 _data->seek(12);
1193
1194 int indentLevel = 0;
1195 ScriptText *scr = new ScriptText;
1196 scr->offset = _data->pos();
1197
1198 while(true) {
1199 int c = _data->readByte();
1200
1201 if (_data->eos())
1202 break;
1203
1204 if (c < 0x80) {
1205 if (c < 0x20) {
1206 warning("convertToText: Unknown code 0x%02x at %d", c, _data->pos());
1207 c = ' ';
1208 }
1209
1210 do {
1211 scr->line += c;
1212 c = _data->readByte();
1213
1214 if (c < 0x20) {
1215 warning("convertToText: Unknown code 0x%02x at %d", c, _data->pos());
1216 c = ' ';
1217 }
1218 } while (c < 0x80);
1219
1220 _data->seek(-1, SEEK_CUR);
1221 } else if (c == 0xff) {
1222 int value = _data->readByte();
1223 value -= 1;
1224 scr->line += (char)('A' + (value / 9));
1225 scr->line += (char)('0' + (value % 9) + 1);
1226 scr->line += '#';
1227 } else {
1228 const char *cmd = mapping[c - 0x80].cmd;
1229 int type = mapping[c - 0x80].type;
1230
1231 if (type == STATEMENT) {
1232 for (int i = 0; i < indentLevel; i++)
1233 scr->line += ' ';
1234 } else if (type == BLOCK_START) {
1235 indentLevel += 2;
1236 } else if (type == BLOCK_END) {
1237 indentLevel -= 2;
1238 for (int i = 0; i < indentLevel; i++)
1239 scr->line += ' ';
1240 }
1241
1242 scr->line += cmd;
1243
1244 if (strchr(cmd, '\n')) {
1245 scr->line.deleteLastChar();
1246
1247 _scriptText.push_back(scr);
1248
1249 scr = new ScriptText;
1250 scr->offset = _data->pos();
1251 }
1252 }
1253 }
1254
1255 if (!scr->line.empty())
1256 _scriptText.push_back(scr);
1257 else
1258 delete scr;
1259 }
1260
1261 } // End of namespace Wage
1262