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