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 "lure/decode.h"
24 #include "lure/events.h"
25 #include "lure/game.h"
26 #include "lure/lure.h"
27 #include "lure/room.h"
28 #include "lure/screen.h"
29 #include "lure/sound.h"
30 #include "lure/strings.h"
31 #include "lure/surface.h"
32 #include "common/endian.h"
33 #include "common/config-manager.h"
34 #include "common/text-to-speech.h"
35 
36 namespace Lure {
37 
38 // These variables hold resources commonly used by the Surfaces, and must be initialized and freed
39 // by the static Surface methods initialize and deinitailse
40 
41 static MemoryBlock *int_font = NULL;
42 static MemoryBlock *int_dialog_frame = NULL;
43 static uint8 fontSize[256];
44 static int numFontChars;
45 
46 static const byte char8A[8] = {0x40, 0x20, 0x00, 0x90, 0x90, 0x90, 0x68, 0x00}; // accented `u
47 static const byte char8D[8] = {0x80, 0x40, 0x00, 0xc0, 0x40, 0x40, 0x60, 0x00}; // accented `i
48 static const byte char95[8] = {0x40, 0x20, 0x00, 0x60, 0x90, 0x90, 0x60, 0x00}; // accented `o
49 
initialize()50 void Surface::initialize() {
51 	Disk &disk = Disk::getReference();
52 	int_font = disk.getEntry(FONT_RESOURCE_ID);
53 	int_dialog_frame = disk.getEntry(DIALOG_RESOURCE_ID);
54 
55 	if (LureEngine::getReference().getLanguage() == Common::IT_ITA) {
56 		Common::copy(&char8A[0], &char8A[8], int_font->data() + (0x8A - 32) * 8);
57 		Common::copy(&char8D[0], &char8D[8], int_font->data() + (0x8D - 32) * 8);
58 		Common::copy(&char95[0], &char95[8], int_font->data() + (0x95 - 32) * 8);
59 	}
60 
61 	numFontChars = int_font->size() / 8;
62 	if (numFontChars > 256)
63 		error("Font data exceeded maximum allowable size");
64 
65 	// Calculate the size of each font character
66 	for (int ctr = 0; ctr < numFontChars; ++ctr) {
67 		byte *pChar = int_font->data() + (ctr * 8);
68 		fontSize[ctr] = 0;
69 
70 		for (int yp = 0; yp < FONT_HEIGHT; ++yp)  {
71 			byte v = *pChar++;
72 
73 			for (int xp = 0; xp < FONT_WIDTH; ++xp) {
74 				if ((v & 0x80) && (xp > fontSize[ctr]))
75 					fontSize[ctr] = xp;
76 				v = (v << 1) & 0xff;
77 			}
78 		}
79 
80 		// If character is empty, like for a space, give a default size
81 		if (fontSize[ctr] == 0) fontSize[ctr] = 2;
82 	}
83 }
84 
deinitialize()85 void Surface::deinitialize() {
86 	delete int_font;
87 	delete int_dialog_frame;
88 }
89 
90 /*--------------------------------------------------------------------------*/
91 
Surface(MemoryBlock * src,uint16 wdth,uint16 hght)92 Surface::Surface(MemoryBlock *src, uint16 wdth, uint16 hght): _data(src),
93 		_width(wdth), _height(hght) {
94 	if ((uint32) (wdth * hght) != src->size())
95 		error("Surface dimensions do not match size of passed data");
96 }
97 
Surface(uint16 wdth,uint16 hght)98 Surface::Surface(uint16 wdth, uint16 hght): _data(Memory::allocate(wdth*hght)),
99 	_width(wdth), _height(hght) {
100 }
101 
~Surface()102 Surface::~Surface() {
103 	delete _data;
104 }
105 
106 // textX / textY
107 // Returns the offset into a dialog for writing text
108 
textX()109 uint16 Surface::textX() { return LureEngine::getReference().isEGA() ? 10 : 12; }
110 
textY()111 uint16 Surface::textY() { return LureEngine::getReference().isEGA() ? 8 : 12; }
112 
113 // getDialogBounds
114 // Returns a suggested size for a dialog given a number of horizontal characters and rows
115 
getDialogBounds(Common::Point & size,int charWidth,int numLines,bool squashedLines)116 void Surface::getDialogBounds(Common::Point &size, int charWidth, int numLines, bool squashedLines) {
117 	size.x = Surface::textX() * 2 + FONT_WIDTH * charWidth;
118 	size.y = Surface::textY() * 2 + (squashedLines ? (FONT_HEIGHT - 1) : FONT_HEIGHT) * numLines;
119 }
120 
121 // egaCreateDialog
122 // Forms a dialog encompassing the entire surface
123 
egaCreateDialog(bool blackFlag)124 void Surface::egaCreateDialog(bool blackFlag) {
125 	byte lineColors1[3] = {6, 0, 9};
126 	byte lineColors2[3] = {7, 0, 12};
127 
128 	// Surface contents
129 	data().setBytes(blackFlag ? 0 : EGA_DIALOG_BG_COLOR, 0, data().size());
130 
131 	// Top/bottom lines
132 	for (int y = 2; y >= 0; --y) {
133 		data().setBytes(lineColors1[y], y * width(), width());
134 		data().setBytes(lineColors2[y], (height() - y - 1) * width(), width());
135 
136 		for (int p = y + 1; p < height() - y; ++p) {
137 			byte *line = data().data() + p * width();
138 			*(line + y) = lineColors2[y];
139 			*(line + width() - y - 1) = lineColors1[y];
140 		}
141 	}
142 }
143 
144 // vgaCreateDialog
145 // Forms a dialog encompassing the entire surface
146 
copyLine(byte * pSrc,byte * pDest,uint16 leftSide,uint16 center,uint16 rightSide)147 void copyLine(byte *pSrc, byte *pDest, uint16 leftSide, uint16 center, uint16 rightSide) {
148 	// Left area
149 	memcpy(pDest, pSrc, leftSide);
150 	pSrc += leftSide; pDest += leftSide;
151 	// Center area
152 	memset(pDest, *pSrc, center);
153 	++pSrc; pDest += center;
154 	// Right side
155 	memcpy(pDest, pSrc, rightSide);
156 	pSrc += rightSide; pDest += rightSide;
157 }
158 
159 #define VGA_DIALOG_EDGE_WIDTH 9
160 
vgaCreateDialog(bool blackFlag)161 void Surface::vgaCreateDialog(bool blackFlag) {
162 	byte *pSrc = int_dialog_frame->data();
163 	byte *pDest = _data->data();
164 	uint16 xCenter = _width - VGA_DIALOG_EDGE_WIDTH * 2;
165 	uint16 yCenter = _height - VGA_DIALOG_EDGE_WIDTH * 2;
166 	int y;
167 
168 	// Dialog top
169 	for (y = 0; y < 9; ++y) {
170 		copyLine(pSrc, pDest, VGA_DIALOG_EDGE_WIDTH - 2, xCenter + 2, VGA_DIALOG_EDGE_WIDTH);
171 		pSrc += (VGA_DIALOG_EDGE_WIDTH - 2) + 1 + VGA_DIALOG_EDGE_WIDTH;
172 		pDest += _width;
173 	}
174 
175 	// Dialog sides - note that the same source data gets used for all side lines
176 	for (y = 0; y < yCenter; ++y) {
177 		copyLine(pSrc, pDest, VGA_DIALOG_EDGE_WIDTH, xCenter, VGA_DIALOG_EDGE_WIDTH);
178 		pDest += _width;
179 	}
180 	pSrc += VGA_DIALOG_EDGE_WIDTH * 2 + 1;
181 
182 	// Dialog bottom
183 	for (y = 0; y < 9; ++y) {
184 		copyLine(pSrc, pDest, VGA_DIALOG_EDGE_WIDTH, xCenter + 1, VGA_DIALOG_EDGE_WIDTH - 1);
185 		pSrc += VGA_DIALOG_EDGE_WIDTH + 1 + (VGA_DIALOG_EDGE_WIDTH - 1);
186 		pDest += _width;
187 	}
188 
189 	// Final processing - if black flag set, clear dialog inside area
190 	if (blackFlag) {
191 		Common::Rect r = Common::Rect(VGA_DIALOG_EDGE_WIDTH, VGA_DIALOG_EDGE_WIDTH,
192 			_width - VGA_DIALOG_EDGE_WIDTH, _height-VGA_DIALOG_EDGE_WIDTH);
193 		fillRect(r, 0);
194 	}
195 }
196 
loadScreen(uint16 resourceId)197 void Surface::loadScreen(uint16 resourceId) {
198 	MemoryBlock *rawData = Disk::getReference().getEntry(resourceId);
199 	loadScreen(rawData);
200 	delete rawData;
201 }
202 
loadScreen(MemoryBlock * rawData)203 void Surface::loadScreen(MemoryBlock *rawData) {
204 	PictureDecoder decoder;
205 	uint16 v = READ_BE_UINT16(rawData->data());
206 	bool is5Bit = (v & 0xfffe) == 0x140;
207 	MemoryBlock *tmpScreen;
208 
209 	if (is5Bit)
210 		// 5-bit decompression
211 		tmpScreen = decoder.egaDecode(rawData, FULL_SCREEN_HEIGHT * FULL_SCREEN_WIDTH + 1);
212 	else
213 		// VGA decompression
214 		tmpScreen = decoder.vgaDecode(rawData, FULL_SCREEN_HEIGHT * FULL_SCREEN_WIDTH + 1);
215 
216 	empty();
217 	_data->copyFrom(tmpScreen, 0, MENUBAR_Y_SIZE * FULL_SCREEN_WIDTH,
218 		(FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE) * FULL_SCREEN_WIDTH);
219 	delete tmpScreen;
220 }
221 
writeChar(uint16 x,uint16 y,uint8 ascii,bool transparent,int color)222 int Surface::writeChar(uint16 x, uint16 y, uint8 ascii, bool transparent, int color) {
223 	byte *const addr = _data->data() + (y * _width) + x;
224 	if (color == DEFAULT_TEXT_COLOR)
225 		color = LureEngine::getReference().isEGA() ? EGA_DIALOG_TEXT_COLOR : VGA_DIALOG_TEXT_COLOR;
226 
227 	if ((ascii < 32) || (ascii >= 32 + numFontChars))
228 		error("Invalid ascii character passed for display '%d'", ascii);
229 
230 	uint8 v;
231 	byte *pFont = int_font->data() + ((ascii - 32) * 8);
232 	byte *pDest;
233 	uint8 charWidth = 0;
234 
235 	for (int y1 = 0; y1 < 8; ++y1) {
236 		v = *pFont++;
237 		pDest = addr + (y1 * _width);
238 
239 		for (int x1 = 0; x1 < 8; ++x1, ++pDest) {
240 			if (v & 0x80) {
241 				*pDest = color;
242 				if (x1+1 > charWidth) charWidth = x1 + 1;
243 			}
244 			else if (!transparent) *pDest = 0;
245 			v = (v << 1) & 0xff;
246 		}
247 	}
248 
249 	return charWidth;
250 }
251 
writeString(uint16 x,uint16 y,Common::String line,bool transparent,int color,bool varLength)252 void Surface::writeString(uint16 x, uint16 y, Common::String line, bool transparent,
253 						  int color, bool varLength) {
254 	writeSubstring(x, y, line, line.size(), transparent, color, varLength);
255 }
256 
writeSubstring(uint16 x,uint16 y,Common::String line,int len,bool transparent,int color,bool varLength)257 void Surface::writeSubstring(uint16 x, uint16 y, Common::String line, int len,
258 		  bool transparent, int color, bool varLength) {
259 
260 	const char *sPtr = line.c_str();
261 	if (color == DEFAULT_TEXT_COLOR)
262 		color = LureEngine::getReference().isEGA() ? EGA_DIALOG_TEXT_COLOR : VGA_DIALOG_TEXT_COLOR;
263 
264 	for (int index = 0; (index < len) && (*sPtr != '\0'); ++index, ++sPtr) {
265 		int charSize = varLength ? fontSize[(uint8)*sPtr - 32] + 2 : FONT_WIDTH;
266 		if (x + charSize >= width())
267 			// Passed the right hand edge of the surface
268 			break;
269 
270 		// Write next character
271 		writeChar(x, y, (uint8) *sPtr, transparent, color);
272 
273 		// Move to after the character in preparation for the next character
274 		x += charSize;
275 	}
276 }
277 
transparentCopyTo(Surface * dest)278 void Surface::transparentCopyTo(Surface *dest) {
279 	if (dest->width() != _width)
280 		error("Incompatible surface sizes for transparent copy");
281 
282 	byte *pSrc = _data->data();
283 	byte *pDest = dest->data().data();
284 	uint16 numBytes = MIN(_height,dest->height()) * FULL_SCREEN_WIDTH;
285 
286 	while (numBytes-- > 0) {
287 		if (*pSrc) *pDest = *pSrc;
288 
289 		++pSrc;
290 		++pDest;
291 	}
292 }
293 
copyTo(Surface * dest)294 void Surface::copyTo(Surface *dest) {
295 	copyTo(dest, 0, 0);
296 }
297 
copyTo(Surface * dest,uint16 x,uint16 y)298 void Surface::copyTo(Surface *dest, uint16 x, uint16 y) {
299 	if ((x == 0) && (dest->width() == _width)) {
300 		// Use fast data transfer
301 		uint32 dataSize = dest->data().size() - (y * _width);
302 		if (dataSize > _data->size()) dataSize = _data->size();
303 		dest->data().copyFrom(_data, 0, y * _width, dataSize);
304 	} else {
305 		// Use slower transfer
306 		Common::Rect rect;
307 		rect.left = 0; rect.top = 0;
308 		rect.right = _width-1; rect.bottom = _height-1;
309 		copyTo(dest, rect, x, y);
310 	}
311 }
312 
copyTo(Surface * dest,const Common::Rect & srcBounds,uint16 destX,uint16 destY,int transparentColor)313 void Surface::copyTo(Surface *dest, const Common::Rect &srcBounds,
314 					 uint16 destX, uint16 destY, int transparentColor) {
315 	int numBytes = srcBounds.right - srcBounds.left + 1;
316 	if (destX + numBytes > dest->width())
317 		numBytes = dest->width() - destX;
318 	if (numBytes <= 0) return;
319 
320 	for (uint16 y=0; y<=(srcBounds.bottom-srcBounds.top); ++y) {
321 		const uint32 srcPos = (srcBounds.top + y) * _width + srcBounds.left;
322 		const uint32 destPos = (destY+y) * dest->width() + destX;
323 
324 		if (transparentColor == -1) {
325 			// No trnnsparent color, so copy all the bytes of the line
326 			dest->data().copyFrom(_data, srcPos, destPos, numBytes);
327 		} else {
328 			byte *pSrc = _data->data() + srcPos;
329 			byte *pDest = dest->data().data() + destPos;
330 
331 			int bytesCtr = numBytes;
332 			while (bytesCtr-- > 0) {
333 				if (*pSrc != (uint8) transparentColor)
334 					*pDest = *pSrc;
335 				++pSrc;
336 				++pDest;
337 			}
338 		}
339 	}
340 }
341 
copyFrom(MemoryBlock * src,uint32 destOffset)342 void Surface::copyFrom(MemoryBlock *src, uint32 destOffset) {
343 	uint32 size = _data->size() - destOffset;
344 	if (src->size() > size) size = src->size();
345 	_data->copyFrom(src, 0, destOffset, size);
346 }
347 
348 // fillRect
349 // Fills a rectangular area with a color
350 
fillRect(const Common::Rect & r,uint8 color)351 void Surface::fillRect(const Common::Rect &r, uint8 color) {
352 	for (int yp = r.top; yp <= r.bottom; ++yp) {
353 		byte *const addr = _data->data() + (yp * _width) + r.left;
354 		memset(addr, color, r.width());
355 	}
356 }
357 
createDialog(bool blackFlag)358 void Surface::createDialog(bool blackFlag) {
359 	if (LureEngine::getReference().isEGA())
360 		egaCreateDialog(blackFlag);
361 	else
362 		vgaCreateDialog(blackFlag);
363 }
364 
copyToScreen(uint16 x,uint16 y)365 void Surface::copyToScreen(uint16 x, uint16 y) {
366 	OSystem &system = *g_system;
367 	system.copyRectToScreen(_data->data(), _width, x, y, _width, _height);
368 	system.updateScreen();
369 }
370 
centerOnScreen()371 void Surface::centerOnScreen() {
372 	OSystem &system = *g_system;
373 
374 	system.copyRectToScreen(_data->data(), _width,
375 		(FULL_SCREEN_WIDTH - _width) / 2, (FULL_SCREEN_HEIGHT - _height) / 2,
376 		_width, _height);
377 	system.updateScreen();
378 }
379 
textWidth(const char * s,int numChars)380 uint16 Surface::textWidth(const char *s, int numChars) {
381 	uint16 result = 0;
382 	if (numChars == 0) numChars = strlen(s);
383 
384 	while (numChars-- > 0) {
385 		uint8 charIndex = (uint8)*s++ - 32;
386 		assert(charIndex < numFontChars);
387 		result += fontSize[charIndex] + 2;
388 	}
389 
390 	return result;
391 }
392 
wordWrap(char * text,uint16 width,char ** & lines,uint8 & numLines)393 void Surface::wordWrap(char *text, uint16 width, char **&lines, uint8 &numLines) {
394 	debugC(ERROR_INTERMEDIATE, kLureDebugStrings, "wordWrap(text=%s, width=%d", text, width);
395 	numLines = 1;
396 	uint16 lineWidth = 0;
397 	char *s;
398 	bool newLine;
399 
400 	s = text;
401 
402 	// Scan through the text and insert NULLs to break the line into allowable widths
403 
404 	while (*s != '\0') {
405 		char *wordStart = s;
406 		while (*wordStart == ' ') ++wordStart;
407 		char *wordEnd = strchr(wordStart, ' ');
408 		char *wordEnd2 = strchr(wordStart, '\n');
409 		if ((!wordEnd) || ((wordEnd2) && (wordEnd2 < wordEnd))) {
410 			wordEnd = wordEnd2;
411 			newLine = (wordEnd2 != NULL);
412 		} else {
413 			newLine = false;
414 		}
415 
416 		debugC(ERROR_DETAILED, kLureDebugStrings, "word scanning: start=%xh, after=%xh, newLine=%d",
417 			(uint32)(wordStart - text), (uint32)((wordEnd == NULL) ? -1 : wordEnd - text), newLine ? 1 : 0);
418 
419 		if (wordEnd) {
420 			if (*wordEnd != '\0') --wordEnd;
421 		} else {
422 			wordEnd = strchr(wordStart, '\0') - 1;
423 		}
424 
425 		int wordBytes = (int)(wordEnd - s + 1);
426 		uint16 wordSize = (wordBytes == 0) ? 0 : textWidth(s, wordBytes);
427 		if (gDebugLevel >= ERROR_DETAILED) {
428 			char wordBuffer[MAX_DESC_SIZE];
429 			strncpy(wordBuffer, wordStart, wordBytes);
430 			wordBuffer[wordBytes] = '\0';
431 			debugC(ERROR_DETAILED, kLureDebugStrings, "word='%s', size=%d", wordBuffer, wordSize);
432 		}
433 
434 		if (lineWidth + wordSize > width) {
435 			// Break word onto next line
436 			*(wordStart - 1) = '\0';
437 			++numLines;
438 			lineWidth = 0;
439 			wordEnd = wordStart - 1;
440 		} else if (newLine) {
441 			// Break on newline
442 			++numLines;
443 			*++wordEnd = '\0';
444 			lineWidth = 0;
445 		} else {
446 			// Add word's length to total for line
447 			lineWidth += wordSize;
448 		}
449 
450 		s = wordEnd+1;
451 	}
452 
453 	// Set up a list for the start of each line
454 	lines = (char **) Memory::alloc(sizeof(char *) * numLines);
455 	lines[0] = text;
456 	debugC(ERROR_DETAILED, kLureDebugStrings, "wordWrap lines[0]='%s'", lines[0]);
457 	for (int ctr = 1; ctr < numLines; ++ctr) {
458 		lines[ctr] = strchr(lines[ctr-1], 0) + 1;
459 		debugC(ERROR_DETAILED, kLureDebugStrings, "wordWrap lines[%d]='%s'", ctr, lines[ctr]);
460 	}
461 
462 	debugC(ERROR_INTERMEDIATE, kLureDebugStrings, "wordWrap end - numLines=%d", numLines);
463 }
464 
newDialog(uint16 width,uint8 numLines,const char ** lines,bool varLength,int color,bool squashedLines)465 Surface *Surface::newDialog(uint16 width, uint8 numLines, const char **lines, bool varLength,
466 							int color, bool squashedLines) {
467 	Common::Point size;
468 	Surface::getDialogBounds(size, 0, numLines, squashedLines);
469 
470 	Surface *s = new Surface(width, size.y);
471 	s->createDialog();
472 	Common::String text;
473 
474 	uint16 yP = Surface::textY();
475 	for (uint8 ctr = 0; ctr < numLines; ++ctr) {
476 		text += lines[ctr];
477 		s->writeString(Surface::textX(), yP, lines[ctr], true, color, varLength);
478 		yP += squashedLines ? FONT_HEIGHT - 1 : FONT_HEIGHT;
479 	}
480 
481 	if (ConfMan.getBool("tts_narrator")) {
482 		Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
483 		if (ttsMan != nullptr) {
484 			ttsMan->stop();
485 			ttsMan->say(text.c_str());
486 		}
487 	}
488 
489 	return s;
490 }
491 
newDialog(uint16 width,const char * line,int color)492 Surface *Surface::newDialog(uint16 width, const char *line, int color) {
493 	char **lines;
494 	Common::String lineCopy(line);
495 	uint8 numLines;
496 	wordWrap(lineCopy.begin(), width - (Surface::textX() * 2), lines, numLines);
497 
498 	// Create the dialog
499 	Surface *result = newDialog(width, numLines, const_cast<const char **>(lines), true, color);
500 
501 	// Deallocate used resources
502 	free(lines);
503 
504 	return result;
505 }
506 
getScreen(uint16 resourceId)507 Surface *Surface::getScreen(uint16 resourceId) {
508 	MemoryBlock *block = Disk::getReference().getEntry(resourceId);
509 	PictureDecoder d;
510 	MemoryBlock *decodedData = d.decode(block);
511 	delete block;
512 	return new Surface(decodedData, FULL_SCREEN_WIDTH, decodedData->size() / FULL_SCREEN_WIDTH);
513 }
514 
getString(Common::String & line,int maxSize,bool isNumeric,bool varLength,int16 x,int16 y)515 bool Surface::getString(Common::String &line, int maxSize, bool isNumeric, bool varLength, int16 x, int16 y) {
516 	OSystem &system = *g_system;
517 	LureEngine &engine = LureEngine::getReference();
518 	Mouse &mouse = Mouse::getReference();
519 	Events &events = Events::getReference();
520 	Screen &screen = Screen::getReference();
521 	uint8 bgColor = *(screen.screen().data().data() + (y * FULL_SCREEN_WIDTH) + x);
522 	Common::String newLine(line);
523 	bool abortFlag = false;
524 	bool refreshFlag = false;
525 
526 	bool vKbdFlag = g_system->hasFeature(OSystem::kFeatureVirtualKeyboard);
527 	if (!vKbdFlag)
528 		mouse.cursorOff();
529 	else
530 		g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
531 
532 
533 	// Insert a cursor character at the end of the string
534 	newLine.insertChar('_', newLine.size());
535 
536 	while (!abortFlag) {
537 		// Display the string
538 		screen.screen().writeString(x, y, newLine, true, DEFAULT_TEXT_COLOR, varLength);
539 		screen.update();
540 		int stringSize = textWidth(newLine.c_str());
541 
542 		// Loop until the input string changes
543 		refreshFlag = false;
544 		while (!refreshFlag && !abortFlag) {
545 			abortFlag = engine.shouldQuit();
546 			if (abortFlag) break;
547 
548 			while (events.pollEvent()) {
549 				if (events.type() == Common::EVENT_KEYDOWN) {
550 					char ch = events.event().kbd.ascii;
551 					uint16 keycode = events.event().kbd.keycode;
552 
553 					if ((keycode == Common::KEYCODE_RETURN) || (keycode == Common::KEYCODE_KP_ENTER)) {
554 						// Return character
555 						screen.screen().fillRect(
556 							Common::Rect(x, y, x + maxSize - 1, y + FONT_HEIGHT), bgColor);
557 						screen.update();
558 						newLine.deleteLastChar();
559 						line = newLine;
560 						if (!vKbdFlag)
561 							mouse.cursorOn();
562 						return true;
563 					}
564 					else if (keycode == Common::KEYCODE_ESCAPE) {
565 						// Escape character
566 						screen.screen().fillRect(
567 							Common::Rect(x, y, x + maxSize - 1, y + FONT_HEIGHT), bgColor);
568 						screen.update();
569 						abortFlag = true;
570 					} else if (keycode == Common::KEYCODE_BACKSPACE) {
571 						// Delete the last character
572 						if (newLine.size() == 1) continue;
573 
574 						screen.screen().fillRect(
575 							Common::Rect(x, y, x + maxSize - 1, y + FONT_HEIGHT), bgColor);
576 						newLine.deleteChar(newLine.size() - 2);
577 						refreshFlag = true;
578 
579 					} else if ((ch >= ' ') && (stringSize + 8 < maxSize)) {
580 						if (((ch >= '0') && (ch <= '9')) || !isNumeric) {
581 							screen.screen().fillRect(
582 								Common::Rect(x, y, x + maxSize - 1, y + FONT_HEIGHT), bgColor);
583 							newLine.insertChar(ch, newLine.size() - 1);
584 							refreshFlag = true;
585 						}
586 					}
587 				}
588 			}
589 
590 			system.updateScreen();
591 			system.delayMillis(10);
592 		}
593 	}
594 
595 	if (!vKbdFlag)
596 		mouse.cursorOn();
597 	else
598 		g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
599 
600 	return false;
601 }
602 
603 
604 /*--------------------------------------------------------------------------*/
605 
show(const char * text)606 void Dialog::show(const char *text) {
607 	debugC(ERROR_BASIC, kLureDebugStrings, "Dialog::show text=%s", text);
608 	Screen &screen = Screen::getReference();
609 	Mouse &mouse = Mouse::getReference();
610 	Room &room = Room::getReference();
611 	mouse.cursorOff();
612 
613 	room.update();
614 	debugC(ERROR_DETAILED, kLureDebugStrings, "Dialog::show creating dialog");
615 	Surface *s = Surface::newDialog(INFO_DIALOG_WIDTH, text);
616 	debugC(ERROR_DETAILED, kLureDebugStrings, "Dialog::show created dialog");
617 	s->copyToScreen(INFO_DIALOG_X, INFO_DIALOG_Y);
618 	debugC(ERROR_DETAILED, kLureDebugStrings, "Dialog::show copied to screen");
619 
620 	// Wait for a keypress or mouse button
621 	Events::getReference().waitForPress();
622 
623 	screen.update();
624 	mouse.cursorOn();
625 
626 	delete s;
627 }
628 
show(uint16 stringId,const char * hotspotName,const char * characterName)629 void Dialog::show(uint16 stringId, const char *hotspotName, const char *characterName) {
630 	debugC(ERROR_BASIC, kLureDebugStrings, "Hotspot::showMessage stringId=%xh hotspot=%s, character=%s",
631 		stringId, hotspotName, characterName);
632 	char buffer[MAX_DESC_SIZE];
633 	StringData &sl = StringData::getReference();
634 
635 	sl.getString(stringId, buffer, hotspotName, characterName);
636 	show(buffer);
637 }
638 
show(uint16 stringId)639 void Dialog::show(uint16 stringId) {
640 	show(stringId, NULL, NULL);
641 }
642 
643 /*--------------------------------------------------------------------------*/
644 
645 const uint16 spanish_pre_e1_type_tl[] = {0x8000, 4, 0x4000, 5, 0x2000, 6, 0xc000, 7, 0, 0};
646 const uint16 spanish_others_tl[]      = {0x8000, 0, 0x4000, 1, 0x2000, 2, 0xc000, 3, 0, 0};
647 
648 const uint16 german_pre_k_type[]    = {106, 0};
649 const uint16 german_pre_k_type_tl[] = {0x8000, 0, 0xc000, 0, 0x4000, 1, 0xa000, 1, 0x2000, 2, 0, 0};
650 const uint16 german_pre_d[]         = {128, 0};
651 const uint16 german_pre_d_tl[]		= {0x8000, 6, 0x4000, 4, 0xa000, 4, 0x2000, 5, 0xc000, 6, 0, 0};
652 const uint16 german_pre_d_type[]    = {158, 236, 161, 266, 280, 287, 286, 294, 264, 0};
653 const uint16 german_pre_d_type_tl[] = {0x8000, 3, 0x4000, 4, 0xa000, 4, 0x2000, 5, 0xc000, 6, 0, 0};
654 const uint16 german_pre_e_type[]    = {160, 0};
655 const uint16 german_pre_e_type_tl[] = {0x8000, 7, 0xc000, 7, 0x4000, 8, 0xa000, 8, 0x2000, 9, 0, 0};
656 
657 struct GermanLanguageArticle {
658 	const uint16 *messageList;
659 	const uint16 *translations;
660 };
661 
662 const GermanLanguageArticle germanArticles[] = {
663 	{&german_pre_k_type[0], &german_pre_k_type_tl[0]},
664 	{&german_pre_d[0], &german_pre_d_tl[0]},
665 	{&german_pre_d_type[0], &german_pre_d_type_tl[0]},
666 	{&german_pre_e_type[0], &german_pre_e_type_tl[0]}
667 };
668 
669 
getArticle(uint16 msgId,uint16 objId)670 int TalkDialog::getArticle(uint16 msgId, uint16 objId) {
671 	Common::Language language = LureEngine::getReference().getLanguage();
672 	int id = objId & 0xe000;
673 
674 	if (language == Common::DE_DEU) {
675 		// Special handling for German language
676 
677 		for (int sectionIndex = 0; sectionIndex < 4; ++sectionIndex) {
678 			// Scan through the list of messages for this section
679 			bool msgFound = false;
680 			for (const uint16 *msgPtr = germanArticles[sectionIndex].messageList; *msgPtr != 0; ++msgPtr) {
681 				msgFound = *msgPtr == msgId;
682 				if (msgFound) break;
683 			}
684 
685 			if (msgFound) {
686 				// Scan against possible bit combinations
687 				for (const uint16 *p = germanArticles[sectionIndex].translations; *p != 0; p += 2) {
688 					if (*p == id)
689 						// Return the article index to use
690 						return *++p + 1;
691 				}
692 
693 				return 0;
694 			}
695 		}
696 
697 
698 		return 0;
699 
700 	} else if (language == Common::ES_ESP) {
701 		// Special handling for Spanish langugae
702 		const uint16 *tlData = (msgId == 158) ? spanish_pre_e1_type_tl : spanish_others_tl;
703 
704 		// Scan through the list of article bitflag mappings
705 		for (const uint16 *p = tlData; *p != 0; p += 2) {
706 			if (*p == id)
707 				// Return the article index to use
708 				return *++p + 1;
709 		}
710 
711 		return 0;
712 	}
713 
714 	return (id >> 13) + 1;
715 }
716 
vgaTalkDialog(Surface * s)717 void TalkDialog::vgaTalkDialog(Surface *s) {
718 	Resources &res = Resources::getReference();
719 
720 	// Draw the dialog
721 	byte *pSrc = res.getTalkDialogData().data();
722 	byte *pDest = s->data().data();
723 	int xPos, yPos;
724 
725 	// Handle the dialog top
726 	for (yPos = 0; yPos < TALK_DIALOG_EDGE_SIZE; ++yPos) {
727 		*pDest++ = *pSrc++;
728 		*pDest++ = *pSrc++;
729 
730 		for (xPos = 0; xPos < TALK_DIALOG_WIDTH - TALK_DIALOG_EDGE_SIZE - 2; ++xPos)
731 			*pDest++ = *pSrc;
732 		++pSrc;
733 
734 		for (xPos = 0; xPos < TALK_DIALOG_EDGE_SIZE; ++xPos)
735 			*pDest++ = *pSrc++;
736 	}
737 
738 	// Handle the middle section
739 	for (yPos = 0; yPos < _surface->height() - TALK_DIALOG_EDGE_SIZE * 2; ++yPos) {
740 		byte *pSrcTemp = pSrc;
741 
742 		// Left edge
743 		for (xPos = 0; xPos < TALK_DIALOG_EDGE_SIZE; ++xPos)
744 			*pDest++ = *pSrcTemp++;
745 
746 		// Middle section
747 		for (xPos = 0; xPos < _surface->width() - TALK_DIALOG_EDGE_SIZE * 2; ++xPos)
748 			*pDest++ = *pSrcTemp;
749 		++pSrcTemp;
750 
751 		// Right edge
752 		for (xPos = 0; xPos < TALK_DIALOG_EDGE_SIZE; ++xPos)
753 			*pDest++ = *pSrcTemp++;
754 	}
755 
756 	//  Bottom section
757 	pSrc += TALK_DIALOG_EDGE_SIZE * 2 + 1;
758 	for (yPos = 0; yPos < TALK_DIALOG_EDGE_SIZE; ++yPos) {
759 		for (xPos = 0; xPos < TALK_DIALOG_EDGE_SIZE; ++xPos)
760 			*pDest++ = *pSrc++;
761 
762 		for (xPos = 0; xPos < TALK_DIALOG_WIDTH - TALK_DIALOG_EDGE_SIZE - 2; ++xPos)
763 			*pDest++ = *pSrc;
764 		++pSrc;
765 
766 		*pDest++ = *pSrc++;
767 		*pDest++ = *pSrc++;
768 	}
769 }
770 
TalkDialog(uint16 characterId,uint16 destCharacterId,uint16 activeItemId,uint16 descId)771 TalkDialog::TalkDialog(uint16 characterId, uint16 destCharacterId, uint16 activeItemId, uint16 descId) {
772 	debugC(ERROR_DETAILED, kLureDebugAnimations, "TalkDialog(chars=%xh/%xh, item=%d, str=%d",
773 		characterId, destCharacterId, activeItemId, descId);
774 	StringData &strings = StringData::getReference();
775 	Resources &res = Resources::getReference();
776 	char srcCharName[MAX_DESC_SIZE];
777 	char destCharName[MAX_DESC_SIZE];
778 	char itemName[MAX_DESC_SIZE];
779 	int characterArticle = 0, hotspotArticle = 0;
780 	bool isEGA = LureEngine::getReference().isEGA();
781 
782 
783 	_characterId = characterId;
784 	_destCharacterId = destCharacterId;
785 	_activeItemId = activeItemId;
786 	_descId = descId;
787 
788 	HotspotData *talkingChar = res.getHotspot(characterId);
789 	HotspotData *destCharacter = (destCharacterId == 0) ? NULL :
790 		res.getHotspot(destCharacterId);
791 	HotspotData *itemHotspot = (activeItemId == 0) ? NULL :
792 		res.getHotspot(activeItemId);
793 	assert(talkingChar);
794 
795 	strings.getString(talkingChar->nameId & 0x1fff, srcCharName);
796 
797 	strcpy(destCharName, "");
798 	if (destCharacter != NULL) {
799 		strings.getString(destCharacter->nameId, destCharName);
800 		characterArticle = getArticle(descId, destCharacter->nameId);
801 	}
802 	strcpy(itemName, "");
803 	if (itemHotspot != NULL) {
804 		strings.getString(itemHotspot->nameId & 0x1fff, itemName);
805 		hotspotArticle = getArticle(descId, itemHotspot->nameId);
806 	}
807 
808 	strings.getString(descId, _desc, itemName, destCharName, hotspotArticle, characterArticle);
809 
810 	// Apply word wrapping to figure out the needed size of the dialog
811 	Surface::wordWrap(_desc, TALK_DIALOG_WIDTH - (TALK_DIALOG_EDGE_SIZE + 3) * 2,
812 		_lines, _numLines);
813 	_endLine = 0; _endIndex = 0;
814 
815 	debugC(ERROR_DETAILED, kLureDebugAnimations, "Creating talk dialog for %d lines", _numLines);
816 
817 	_surface = new Surface(TALK_DIALOG_WIDTH,
818 		(_numLines + 1) * FONT_HEIGHT + TALK_DIALOG_EDGE_SIZE * 4);
819 
820 	if (isEGA)
821 		_surface->createDialog();
822 	else
823 		vgaTalkDialog(_surface);
824 
825 	_wordCountdown = 0;
826 
827 	// Write out the character name
828 	uint16 charWidth = Surface::textWidth(srcCharName);
829 	byte white = LureEngine::getReference().isEGA() ?  EGA_DIALOG_WHITE_COLOR : VGA_DIALOG_WHITE_COLOR;
830 	_surface->writeString((TALK_DIALOG_WIDTH - charWidth) / 2, TALK_DIALOG_EDGE_SIZE + 2,
831 		srcCharName, true, white);
832 	debugC(ERROR_DETAILED, kLureDebugAnimations, "TalkDialog end");
833 }
834 
~TalkDialog()835 TalkDialog::~TalkDialog() {
836 	Memory::dealloc(_lines);
837 	delete _surface;
838 }
839 
copyTo(Surface * dest,uint16 x,uint16 y)840 void TalkDialog::copyTo(Surface *dest, uint16 x, uint16 y) {
841 	if (_endLine < _numLines) {
842 		if (_wordCountdown > 0) {
843 			// Handle delay between words
844 			--_wordCountdown;
845 
846 		} else {
847 			// Set a delay before the next word is displayed
848 			Game &game = Game::getReference();
849 			_wordCountdown = game.fastTextFlag() ? 0 : 1;
850 
851 			// Scan forward to find the next word break
852 			char ch = '\0';
853 			bool wordFlag = false;
854 
855 			while (!wordFlag) {
856 				ch = _lines[_endLine][++_endIndex];
857 				wordFlag = (ch == ' ') || (ch == '\0');
858 			}
859 
860 			// Write out the completed portion of the current line
861 			_surface->writeSubstring(TALK_DIALOG_EDGE_SIZE + 2,
862 				TALK_DIALOG_EDGE_SIZE + 4 + (_endLine + 1) * FONT_HEIGHT,
863 				_lines[_endLine], _endIndex, true);
864 
865 			// If at end of line, move to next line for next time
866 			if (ch == '\0') {
867 				++_endLine;
868 				_endIndex = -1;
869 			}
870 		}
871 	}
872 
873 	_surface->copyTo(dest, x, y);
874 }
875 
saveToStream(Common::WriteStream * stream)876 void TalkDialog::saveToStream(Common::WriteStream *stream) {
877 	stream->writeUint16LE(_characterId);
878 	stream->writeUint16LE(_destCharacterId);
879 	stream->writeUint16LE(_activeItemId);
880 	stream->writeUint16LE(_descId);
881 	stream->writeSint16LE(_endLine);
882 	stream->writeSint16LE(_endIndex);
883 	stream->writeSint16LE(_wordCountdown);
884 
885 }
886 
loadFromStream(Common::ReadStream * stream)887 TalkDialog *TalkDialog::loadFromStream(Common::ReadStream *stream) {
888 	uint16 characterId = stream->readUint16LE();
889 	if (characterId == 0)
890 		return NULL;
891 
892 	uint16 destCharacterId = stream->readUint16LE();
893 	uint16 activeItemId = stream->readUint16LE();
894 	uint16 descId = stream->readUint16LE();
895 
896 	TalkDialog *dialog = new TalkDialog(characterId, destCharacterId, activeItemId, descId);
897 	dialog->_endLine = stream->readSint16LE();
898 	dialog->_endIndex = stream->readSint16LE();
899 	dialog->_wordCountdown = stream->readSint16LE();
900 	return dialog;
901 }
902 
903 /*--------------------------------------------------------------------------*/
904 
905 #define SR_SEPARATOR_Y 21
906 #define SR_SEPARATOR_X 5
907 #define SR_SEPARATOR_HEIGHT 5
908 #define SR_SAVEGAME_NAMES_Y (SR_SEPARATOR_Y + SR_SEPARATOR_HEIGHT + 1)
909 
910 
toggleHightlight(int xs,int xe,int ys,int ye)911 void SaveRestoreDialog::toggleHightlight(int xs, int xe, int ys, int ye) {
912 	Screen &screen = Screen::getReference();
913 	byte *addr = screen.screen().data().data() + FULL_SCREEN_WIDTH * ys + xs;
914 	const byte colorList[4] = {EGA_DIALOG_TEXT_COLOR, EGA_DIALOG_WHITE_COLOR,
915 		VGA_DIALOG_TEXT_COLOR, VGA_DIALOG_WHITE_COLOR};
916 	const byte *colors = LureEngine::getReference().isEGA() ? &colorList[0] : &colorList[2];
917 
918 	for (int y = 0; y < ye - ys + 1; ++y, addr += FULL_SCREEN_WIDTH) {
919 		for (int x = 0; x < xe - xs + 1; ++x) {
920 			if (addr[x] == colors[0]) addr[x] = colors[1];
921 			else if (addr[x] == colors[1]) addr[x] = colors[0];
922 		}
923 	}
924 
925 	screen.update();
926 }
927 
show(bool saveDialog)928 bool SaveRestoreDialog::show(bool saveDialog) {
929 	OSystem &system = *g_system;
930 	Screen &screen = Screen::getReference();
931 	Mouse &mouse = Mouse::getReference();
932 	Events &events = Events::getReference();
933 	Resources &res = Resources::getReference();
934 	LureEngine &engine = LureEngine::getReference();
935 	int selectedLine = -1;
936 	int index;
937 
938 	// Figure out a list of present savegames
939 	Common::String **saveNames = (Common::String **)Memory::alloc(sizeof(Common::String *) * MAX_SAVEGAME_SLOTS);
940 	int numSaves = 0;
941 	while ((numSaves < MAX_SAVEGAME_SLOTS) &&
942 		((saveNames[numSaves] = engine.detectSave(numSaves + 1)) != NULL))
943 		++numSaves;
944 
945 	// For the save dialog, if all the slots have not been used up, create a
946 	// blank entry for a new savegame
947 	if (saveDialog && (numSaves < MAX_SAVEGAME_SLOTS))
948 		saveNames[numSaves++] = new Common::String();
949 
950 	// For the restore dialog, if there are no savegames, return immediately
951 	if (!saveDialog && (numSaves == 0)) {
952 		Memory::dealloc(saveNames);
953 		return false;
954 	}
955 
956 	Surface *s = new Surface(INFO_DIALOG_WIDTH, SR_SAVEGAME_NAMES_Y +
957 		numSaves * FONT_HEIGHT + FONT_HEIGHT + 2);
958 
959 	// Create the outer dialog and dividing line
960 	s->createDialog();
961 	byte *pDest = s->data().data() + (s->width() * SR_SEPARATOR_Y) + SR_SEPARATOR_X;
962 	uint8 rowColors[5] = {*(pDest-2), *(pDest-1), *(pDest-1), *(pDest-2), *(pDest+1)};
963 	for (int y = 0; y < SR_SEPARATOR_HEIGHT; ++y, pDest += s->width())
964 		memset(pDest, rowColors[y], s->width() - 12);
965 
966 	// Create title line
967 	Common::String title(res.stringList().getString(
968 		saveDialog ? S_SAVE_GAME : S_RESTORE_GAME));
969 	s->writeString((s->width() - s->textWidth(title.c_str())) / 2, FONT_HEIGHT+2, title, true);
970 
971 	// Write out any existing save names
972 	for (index = 0; index < numSaves; ++index)
973 		s->writeString(Surface::textX(), SR_SAVEGAME_NAMES_Y + (index * 8), saveNames[index]->c_str(), true);
974 
975 	// Display the dialog
976 	s->copyTo(&screen.screen(), SAVE_DIALOG_X, SAVE_DIALOG_Y);
977 	screen.update();
978 	mouse.pushCursorNum(CURSOR_ARROW);
979 	Sound.pause();
980 
981 	bool abortFlag = false;
982 	bool doneFlag = false;
983 	while (!abortFlag && !doneFlag) {
984 		// Provide highlighting of lines to select a save slot
985 		while (!abortFlag && !(mouse.lButton() && (selectedLine != -1))
986 				&& !mouse.rButton() && !mouse.mButton()) {
987 			abortFlag = engine.shouldQuit();
988 			if (abortFlag) break;
989 
990 			while (events.pollEvent()) {
991 				if ((events.type() == Common::EVENT_KEYDOWN) &&
992 					(events.event().kbd.keycode == Common::KEYCODE_ESCAPE)) {
993 					abortFlag = true;
994 					break;
995 				}
996 				if (events.type() == Common::EVENT_MOUSEMOVE ||
997 					events.type() == Common::EVENT_WHEELUP || events.type() == Common::EVENT_WHEELDOWN) {
998 					// Mouse movement
999 					int lineNum = 0;
1000 
1001 					if (events.type() == Common::EVENT_MOUSEMOVE) {
1002 						if ((mouse.x() < (SAVE_DIALOG_X + Surface::textX())) ||
1003 							(mouse.x() >= (SAVE_DIALOG_X + s->width() - Surface::textX())) ||
1004 							(mouse.y() < SAVE_DIALOG_Y + SR_SAVEGAME_NAMES_Y) ||
1005 							(mouse.y() >= SAVE_DIALOG_Y + SR_SAVEGAME_NAMES_Y + numSaves * FONT_HEIGHT))
1006 							// Outside displayed lines
1007 							lineNum = -1;
1008 						else
1009 							lineNum = (mouse.y() - (SAVE_DIALOG_Y + SR_SAVEGAME_NAMES_Y)) / FONT_HEIGHT;
1010 					} else if (events.type() == Common::EVENT_WHEELUP) {
1011 						if (selectedLine > 0) {
1012 							lineNum = selectedLine - 1;
1013 						}
1014 					} else if (events.type() == Common::EVENT_WHEELDOWN) {
1015 						if (selectedLine < numSaves - 1) {
1016 							lineNum = selectedLine + 1;
1017 						}
1018 					}
1019 
1020 					if (lineNum != selectedLine) {
1021 						if (selectedLine != -1)
1022 							// Deselect previously selected line
1023 							toggleHightlight(SAVE_DIALOG_X + Surface::textX(),
1024 								SAVE_DIALOG_X + s->width() - Surface::textX(),
1025 								SAVE_DIALOG_Y + SR_SAVEGAME_NAMES_Y + selectedLine * FONT_HEIGHT,
1026 								SAVE_DIALOG_Y + SR_SAVEGAME_NAMES_Y + (selectedLine + 1) * FONT_HEIGHT - 1);
1027 
1028 						// Highlight new line
1029 						selectedLine = lineNum;
1030 						if (selectedLine != -1)
1031 							toggleHightlight(SAVE_DIALOG_X + Surface::textX(),
1032 								SAVE_DIALOG_X + s->width() - Surface::textX(),
1033 								SAVE_DIALOG_Y + SR_SAVEGAME_NAMES_Y + selectedLine * FONT_HEIGHT,
1034 								SAVE_DIALOG_Y + SR_SAVEGAME_NAMES_Y + (selectedLine + 1) * FONT_HEIGHT - 1);
1035 					}
1036 				}
1037 			}
1038 
1039 			system.updateScreen();
1040 			system.delayMillis(10);
1041 		}
1042 
1043 		// Deselect selected row
1044 		if (selectedLine != -1)
1045 			toggleHightlight(SAVE_DIALOG_X + Surface::textX(),
1046 				SAVE_DIALOG_X + s->width() - Surface::textX(),
1047 				SAVE_DIALOG_Y + SR_SAVEGAME_NAMES_Y + selectedLine * FONT_HEIGHT,
1048 				SAVE_DIALOG_Y + SR_SAVEGAME_NAMES_Y + (selectedLine + 1) * FONT_HEIGHT - 1);
1049 
1050 		if (mouse.lButton() || mouse.rButton() || mouse.mButton()) {
1051 			abortFlag = mouse.rButton();
1052 			mouse.waitForRelease();
1053 		}
1054 		if (abortFlag) break;
1055 
1056 		// If in save mode, allow the entry of a new savename
1057 		if (saveDialog) {
1058 			if (!screen.screen().getString(*saveNames[selectedLine],
1059 				INFO_DIALOG_WIDTH - (Surface::textX() * 2),
1060 				false, true, SAVE_DIALOG_X + Surface::textX(),
1061 				SAVE_DIALOG_Y + SR_SAVEGAME_NAMES_Y + selectedLine * FONT_HEIGHT)) {
1062 				// Aborted out of name selection, so restore old name and
1063 				// go back to slot selection
1064 				screen.screen().writeString(
1065 					SAVE_DIALOG_X + Surface::textX(),
1066 					SAVE_DIALOG_Y + SR_SAVEGAME_NAMES_Y + selectedLine * FONT_HEIGHT,
1067 					saveNames[selectedLine]->c_str(), true);
1068 				selectedLine = -1;
1069 				continue;
1070 			}
1071 		}
1072 		doneFlag = true;
1073 	}
1074 
1075 	delete s;
1076 	Sound.resume();
1077 
1078 	int errorFlag = 0;
1079 	if (doneFlag) {
1080 		// Handle save or restore
1081 		if (saveDialog) {
1082 			doneFlag = engine.saveGame(selectedLine + 1, *saveNames[selectedLine]);
1083 			if (!doneFlag)
1084 				errorFlag = 1;
1085 		} else {
1086 			doneFlag = engine.loadGame(selectedLine + 1);
1087 			if (!doneFlag)
1088 				errorFlag = 2;
1089 		}
1090 	}
1091 
1092 	mouse.popCursor();
1093 
1094 	// Free savegame caption list
1095 	for (index = 0; index < numSaves; ++index) delete saveNames[index];
1096 	Memory::dealloc(saveNames);
1097 
1098 	if (errorFlag != 0) {
1099 		Room::getReference().update();
1100 		screen.update();
1101 
1102 		if (errorFlag == 1)
1103 			Dialog::show("Error occurred saving the game");
1104 		else if (errorFlag == 2)
1105 			Dialog::show("Error occurred loading the savegame");
1106 	}
1107 
1108 	return doneFlag;
1109 }
1110 
1111 /*--------------------------------------------------------------------------*/
1112 
1113 struct RestartRecordPos {
1114 	int16 x, y;
1115 };
1116 
1117 struct RestartRecord {
1118 	Common::Language Language;
1119 	int16 width, height;
1120 	RestartRecordPos BtnRestart;
1121 	RestartRecordPos BtnRestore;
1122 };
1123 
1124 static const RestartRecord buttonBounds[] = {
1125 	{ Common::EN_ANY, 48, 14, { 118, 152 }, { 168, 152 } },
1126 	{ Common::DE_DEU, 48, 14, { 106, 152 }, { 168, 152 } },
1127 	{ Common::UNK_LANG, 48, 14, { 112, 152 }, { 168, 152 } }
1128 };
1129 
1130 
show()1131 bool RestartRestoreDialog::show() {
1132 	Resources &res = Resources::getReference();
1133 	Events &events = Events::getReference();
1134 	Mouse &mouse = Mouse::getReference();
1135 	Screen &screen = Screen::getReference();
1136 	LureEngine &engine = LureEngine::getReference();
1137 
1138 	Sound.killSounds();
1139 	Sound.musicInterface_Play(188, true);
1140 	mouse.setCursorNum(CURSOR_ARROW);
1141 
1142 	// See if there are any savegames that can be restored
1143 	Common::String *firstSave = engine.detectSave(1);
1144 	bool restartFlag = (firstSave == NULL);
1145 	int highlightedButton = -1;
1146 
1147 	if (!restartFlag) {
1148 		delete firstSave;
1149 
1150 		// Get the correct button bounds record to use
1151 		const RestartRecord *btnRecord = &buttonBounds[0];
1152 		while ((btnRecord->Language != engine.getLanguage()) &&
1153 			   (btnRecord->Language != Common::UNK_LANG))
1154 			++btnRecord;
1155 
1156 		// Fade out the screen
1157 		screen.paletteFadeOut(RES_PALETTE_ENTRIES);
1158 
1159 		// Get the palette that will be used, and first fade out the prior screen
1160 		Palette p(RESTART_RESOURCE_ID - 1);
1161 
1162 		// Turn on the mouse
1163 		mouse.cursorOn();
1164 
1165 		// Load the restore/restart screen image
1166 		Surface *s = Surface::getScreen(RESTART_RESOURCE_ID);
1167 		s->copyTo(&screen.screen(), 0, MENUBAR_Y_SIZE);
1168 		delete s;
1169 
1170 		res.activeHotspots().clear();
1171 		Hotspot *btnHotspot = new Hotspot();
1172 
1173 		// Restart button
1174 		btnHotspot->setSize(btnRecord->width, btnRecord->height);
1175 		btnHotspot->setPosition(btnRecord->BtnRestart.x, btnRecord->BtnRestart.y);
1176 		btnHotspot->setAnimation(0x184B);
1177 		btnHotspot->copyTo(&screen.screen());
1178 
1179 		// Restore button
1180 		btnHotspot->setFrameNumber(1);
1181 		btnHotspot->setPosition(btnRecord->BtnRestore.x, btnRecord->BtnRestore.y);
1182 		btnHotspot->copyTo(&screen.screen());
1183 
1184 		screen.update();
1185 		screen.paletteFadeIn(&p);
1186 
1187 		// Event loop for making selection
1188 		bool buttonPressed = false;
1189 
1190 		while (!engine.shouldQuit()) {
1191 			// Handle events
1192 			while (events.pollEvent()) {
1193 				if ((events.type() == Common::EVENT_LBUTTONDOWN) && (highlightedButton != -1)) {
1194 					mouse.waitForRelease();
1195 					buttonPressed = true;
1196 					break;
1197 				}
1198 			}
1199 
1200 			if (buttonPressed)
1201 				break;
1202 
1203 			// Check if the pointer is over either button
1204 			int currentButton = -1;
1205 			if ((mouse.y() >= btnRecord->BtnRestart.y) &&
1206 				(mouse.y() < btnRecord->BtnRestart.y + btnRecord->height)) {
1207 				// Check whether the Restart or Restore button is highlighted
1208 				if ((mouse.x() >= btnRecord->BtnRestart.x) &&
1209 					(mouse.x() < btnRecord->BtnRestart.x + btnRecord->width))
1210 					currentButton = 0;
1211 				else if ((mouse.x() >= btnRecord->BtnRestore.x) &&
1212 					(mouse.x() < btnRecord->BtnRestore.x + btnRecord->width))
1213 					currentButton = 1;
1214 			}
1215 
1216 			// Take care of highlighting as the selected button changes
1217 			if (currentButton != highlightedButton) {
1218 				highlightedButton = currentButton;
1219 
1220 				// Restart button
1221 				btnHotspot->setFrameNumber((highlightedButton == 0) ? 2 : 0);
1222 				btnHotspot->setPosition(btnRecord->BtnRestart.x, btnRecord->BtnRestart.y);
1223 				btnHotspot->copyTo(&screen.screen());
1224 
1225 				// Restore button
1226 				btnHotspot->setFrameNumber((highlightedButton == 1) ? 3 : 1);
1227 				btnHotspot->setPosition(btnRecord->BtnRestore.x, btnRecord->BtnRestore.y);
1228 				btnHotspot->copyTo(&screen.screen());
1229 			}
1230 
1231 
1232 			screen.update();
1233 			g_system->delayMillis(10);
1234 		}
1235 
1236 		restartFlag = highlightedButton == 0;
1237 		delete btnHotspot;
1238 	}
1239 
1240 	Sound.killSounds();
1241 
1242 	if (!restartFlag && !engine.shouldQuit()) {
1243 		// Need to show Restore game dialog
1244 		if (!SaveRestoreDialog::show(false))
1245 			// User cancelled, so fall back on Restart
1246 			restartFlag = true;
1247 	}
1248 
1249 	return restartFlag;
1250 }
1251 
1252 /*--------------------------------------------------------------------------*/
1253 
1254 struct ItemDesc {
1255 	Common::Language language;
1256 	int16 x, y;
1257 	uint16 width, height;
1258 	uint16 animId;
1259 	uint8 startColor;
1260 };
1261 
1262 #define PROT_SPR_HEADER 0x1830
1263 #define WORDING_HEADER 0x1839
1264 #define NUMBER_HEADER 0x1842
1265 
1266 static const ItemDesc copyProtectElements[] = {
1267 	{Common::UNK_LANG, 104, 96, 32, 48, PROT_SPR_HEADER, 0},
1268 	{Common::UNK_LANG, 179, 96, 32, 48, PROT_SPR_HEADER, 0},
1269 
1270 	{Common::EN_ANY, 57, 40, 208, 40, WORDING_HEADER, 32},
1271 	{Common::FR_FRA, 57, 40, 208, 40, WORDING_HEADER, 32},
1272 	{Common::DE_DEU, 39, 30, 240, 53, WORDING_HEADER, 32},
1273 	{Common::NL_NLD, 57, 40, 208, 40, WORDING_HEADER, 32},
1274 	{Common::ES_ESP, 57, 40, 208, 40, WORDING_HEADER, 32},
1275 	{Common::IT_ITA, 57, 40, 208, 40, WORDING_HEADER, 32},
1276 	{Common::RU_RUS, 57, 40, 208, 40, WORDING_HEADER, 32},
1277 
1278 	{Common::UNK_LANG, 138, 168, 16, 8, NUMBER_HEADER, 32},
1279 	{Common::UNK_LANG, 145, 168, 16, 8, NUMBER_HEADER, 32},
1280 	{Common::UNK_LANG, 164, 168, 16, 8, NUMBER_HEADER, 32},
1281 	{Common::UNK_LANG, 171, 168, 16, 8, NUMBER_HEADER, 32},
1282 	{Common::UNK_LANG, 0, 0, 0, 0, 0, 0}
1283 };
1284 
1285 int pageNumbers[20] = {
1286 	4, 10, 16, 22, 5, 11, 17, 23, 6, 12, 18, 7, 13, 19, 8, 14, 20, 9, 15, 21};
1287 
CopyProtectionDialog()1288 CopyProtectionDialog::CopyProtectionDialog() {
1289 	// Get objects for the screen
1290 	LureEngine &engine = LureEngine::getReference();
1291 
1292 	const ItemDesc *ptr = &copyProtectElements[0];
1293 	while ((ptr->width != 0) || (ptr->height != 0)) {
1294 		if ((ptr->language == Common::UNK_LANG) || (ptr->language == engine.getLanguage())) {
1295 			Hotspot *h = new Hotspot();
1296 			h->setPosition(ptr->x, ptr->y);
1297 			h->setSize(ptr->width, ptr->height);
1298 			h->setColorOffset(ptr->startColor);
1299 			h->setAnimation(ptr->animId);
1300 
1301 			_hotspots.push_back(HotspotsList::value_type(h));
1302 		}
1303 
1304 		++ptr;
1305 	}
1306 }
1307 
show()1308 bool CopyProtectionDialog::show() {
1309 	Screen &screen = Screen::getReference();
1310 	Events &events = Events::getReference();
1311 	LureEngine &engine = LureEngine::getReference();
1312 
1313 	screen.setPaletteEmpty();
1314 	Palette p(COPY_PROTECTION_RESOURCE_ID - 1);
1315 
1316 	for (int tryCounter = 0; tryCounter < 3; ++tryCounter) {
1317 		// Copy the base screen to the output screen
1318 		Surface *s = Surface::getScreen(COPY_PROTECTION_RESOURCE_ID);
1319 		s->copyTo(&screen.screen(), 0, MENUBAR_Y_SIZE);
1320 		delete s;
1321 
1322 		// Get needed hotspots
1323 		HotspotsList::iterator hotspot0 = _hotspots.begin();
1324 		HotspotsList::iterator hotspot1 = _hotspots.begin();
1325 		for (int i = 0; i < 1; i++)
1326 			++hotspot1;
1327 		HotspotsList::iterator hotspot2 = _hotspots.begin();
1328 		for (int i = 0; i < 2; i++)
1329 			++hotspot2;
1330 		HotspotsList::iterator hotspot3 = _hotspots.begin();
1331 		for (int i = 0; i < 3; i++)
1332 			++hotspot3;
1333 		HotspotsList::iterator hotspot4 = _hotspots.begin();
1334 		for (int i = 0; i < 4; i++)
1335 			++hotspot4;
1336 		HotspotsList::iterator hotspot5 = _hotspots.begin();
1337 		for (int i = 0; i < 5; i++)
1338 			++hotspot5;
1339 		HotspotsList::iterator hotspot6 = _hotspots.begin();
1340 		for (int i = 0; i < 6; i++)
1341 			++hotspot6;
1342 
1343 		// Add wording header and display screen
1344 		(*hotspot2)->setFrameNumber(1);
1345 		(*hotspot2)->copyTo(&screen.screen());
1346 		screen.update();
1347 		screen.setPalette(&p);
1348 
1349 		// Cycle through displaying different characters until a key or mouse button is pressed
1350 		do {
1351 			chooseCharacters();
1352 		} while (!events.interruptableDelay(100));
1353 
1354 		// Change title text to selection
1355 		(*hotspot2)->setFrameNumber(0);
1356 		(*hotspot2)->copyTo(&screen.screen());
1357 		screen.update();
1358 
1359 		// Clear any prior try
1360 		_charIndex = 0;
1361 
1362 		while (!engine.shouldQuit()) {
1363 			while (events.pollEvent() && (_charIndex < 4)) {
1364 				if (events.type() == Common::EVENT_KEYDOWN) {
1365 					if ((events.event().kbd.keycode == Common::KEYCODE_BACKSPACE) && (_charIndex > 0)) {
1366 						// Remove the last number typed
1367 						--_charIndex;
1368 						HotspotsList::iterator tmpHotspot = _hotspots.begin();
1369 						for (int i = 0; i < _charIndex + 3; i++)
1370 							++tmpHotspot;
1371 						(*tmpHotspot)->setFrameNumber(10);   // Blank space
1372 						(*tmpHotspot)->copyTo(&screen.screen());
1373 
1374 						screen.update();
1375 					} else if ((events.event().kbd.ascii >= '0') &&
1376 							   (events.event().kbd.ascii <= '9')) {
1377 						HotspotsList::iterator tmpHotspot = _hotspots.begin();
1378 						for (int i = 0; i < _charIndex + 3; i++)
1379 							++tmpHotspot;
1380 						// Number pressed
1381 						(*tmpHotspot)->setFrameNumber(events.event().kbd.ascii - '0');
1382 						(*tmpHotspot)->copyTo(&screen.screen());
1383 
1384 						++_charIndex;
1385 					}
1386 
1387 					screen.update();
1388 				}
1389 			}
1390 
1391 			g_system->delayMillis(10);
1392 			if (_charIndex == 4)
1393 				break;
1394 		}
1395 
1396 		if (engine.shouldQuit())
1397 			return false;
1398 
1399 		// At this point, two page numbers have been entered - validate them
1400 		int page1 = ((*hotspot3)->frameNumber() * 10) + (*hotspot4)->frameNumber();
1401 		int page2 = ((*hotspot5)->frameNumber() * 10) + (*hotspot6)->frameNumber();
1402 
1403 		if ((page1 == pageNumbers[(*hotspot0)->frameNumber()]) &&
1404 		    (page2 == pageNumbers[(*hotspot1)->frameNumber()]))
1405 			return true;
1406 	}
1407 
1408 	// Copy protection failed
1409 	return false;
1410 }
1411 
chooseCharacters()1412 void CopyProtectionDialog::chooseCharacters() {
1413 	Screen &screen = Screen::getReference();
1414 	Common::RandomSource &rnd = LureEngine::getReference().rnd();
1415 	int char1 = rnd.getRandomNumber(19);
1416 	int char2 = rnd.getRandomNumber(19);
1417 
1418 	HotspotsList::iterator curHotspot = _hotspots.begin();
1419 	(*curHotspot)->setFrameNumber(char1);
1420 	(*curHotspot)->copyTo(&screen.screen());
1421 	++curHotspot;
1422 	(*curHotspot)->setFrameNumber(char2);
1423 	(*curHotspot)->copyTo(&screen.screen());
1424 
1425 	screen.update();
1426 }
1427 
AudioInitIcon()1428 AudioInitIcon::AudioInitIcon() : _visible(false) {
1429 	if (LureEngine::getReference().isEGA()) {
1430 		// The icon is not shown on EGA
1431 		_iconSurface = 0;
1432 	} else {
1433 		// Load icon
1434 		_iconSurface = new Surface(Disk::getReference().getEntry(AUDIO_INIT_ICON_RESOURCE_ID), 14, 14);
1435 
1436 		Screen &screen = Screen::getReference();
1437 
1438 		// Add the colors needed for displaying the icon to the current palette
1439 		Palette combinedPalette;
1440 		Palette defaultPalette(GAME_PALETTE_RESOURCE_ID);
1441 		combinedPalette.palette()->copyFrom(screen.getPalette().palette(), 0, 0, 4 * 0xF8);
1442 		combinedPalette.palette()->copyFrom(defaultPalette.palette(), 4 * 0xF8, 4 * 0xF8, 4 * 6);
1443 		screen.setPalette(&combinedPalette);
1444 	}
1445 }
1446 
~AudioInitIcon()1447 AudioInitIcon::~AudioInitIcon() {
1448 	if (_iconSurface)
1449 		delete _iconSurface;
1450 }
1451 
show()1452 void AudioInitIcon::show() {
1453 	if (!LureEngine::getReference().isEGA()) {
1454 		Screen &screen = Screen::getReference();
1455 
1456 		_iconSurface->copyTo(&screen.screen(), 0, 185);
1457 		screen.update();
1458 		_visible = true;
1459 	}
1460 }
1461 
hide()1462 void AudioInitIcon::hide() {
1463 	if (!LureEngine::getReference().isEGA()) {
1464 		Screen &screen = Screen::getReference();
1465 
1466 		screen.screen().fillRect(Common::Rect(0, 185, 14, 199), 0);
1467 		screen.update();
1468 		_visible = false;
1469 	}
1470 }
1471 
toggleVisibility()1472 void AudioInitIcon::toggleVisibility() {
1473 	if (_visible) {
1474 		hide();
1475 	} else {
1476 		show();
1477 	}
1478 }
1479 
1480 } // End of namespace Lure
1481