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 "cine/cine.h"
24 #include "cine/bg.h"
25 #include "cine/bg_list.h"
26 #include "cine/various.h"
27 #include "cine/pal.h"
28 
29 #include "common/config-manager.h"
30 #include "common/endian.h"
31 #include "common/events.h"
32 #include "common/str.h"
33 #include "common/system.h"
34 #include "common/textconsole.h"
35 
36 #include "graphics/cursorman.h"
37 #include "graphics/primitives.h"
38 
39 namespace Cine {
40 
41 byte *collisionPage;
42 FWRenderer *renderer = NULL;
43 
44 #define DEFAULT_MESSAGE_BG 1
45 #define DEFAULT_CMD_Y 185
46 
47 // Constants related to kLowPalFormat
48 #define kLowPalBytesPerColor 2
49 #define kLowPalNumColors 16
50 #define kLowPalNumBytes ((kLowPalNumColors) * (kLowPalBytesPerColor))
51 
52 /** Low resolution (9-bit) color format used in Cine's 16-color modes. */
53 #define kLowPalFormat Graphics::PixelFormat(kLowPalBytesPerColor, 3, 3, 3, 0, 8, 4, 0, 0)
54 
55 
56 // Constants related to kHighPalFormat
57 #define kHighPalBytesPerColor 3
58 #define kHighPalNumColors 256
59 #define kHighPalNumBytes ((kHighPalNumColors) * (kHighPalBytesPerColor))
60 
61 /** High resolution (24-bit) color format used in Cine's 256-color modes. */
62 #define kHighPalFormat Graphics::PixelFormat(kHighPalBytesPerColor, 8, 8, 8, 0, 0, 8, 16, 0)
63 
64 static const byte mouseCursorNormal[] = {
65 	0x00, 0x00, 0x40, 0x00, 0x60, 0x00, 0x70, 0x00,
66 	0x78, 0x00, 0x7C, 0x00, 0x7E, 0x00, 0x7F, 0x00,
67 	0x7F, 0x80, 0x7C, 0x00, 0x6C, 0x00, 0x46, 0x00,
68 	0x06, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
69 	0xC0, 0x00, 0xE0, 0x00, 0xF0, 0x00, 0xF8, 0x00,
70 	0xFC, 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x80,
71 	0xFF, 0xC0, 0xFF, 0xC0, 0xFE, 0x00, 0xFF, 0x00,
72 	0xCF, 0x00, 0x07, 0x80, 0x07, 0x80, 0x03, 0x80
73 };
74 
75 static const byte mouseCursorDisk[] = {
76 	0x7F, 0xFC, 0x9F, 0x12, 0x9F, 0x12, 0x9F, 0x12,
77 	0x9F, 0x12, 0x9F, 0xE2, 0x80, 0x02, 0x9F, 0xF2,
78 	0xA0, 0x0A, 0xA0, 0x0A, 0xA0, 0x0A, 0xA0, 0x0A,
79 	0xA0, 0x0A, 0xA0, 0x0A, 0x7F, 0xFC, 0x00, 0x00,
80 	0x7F, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE,
81 	0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE,
82 	0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE,
83 	0xFF, 0xFE, 0xFF, 0xFE, 0x7F, 0xFC, 0x00, 0x00
84 };
85 
86 static const byte mouseCursorCross[] = {
87 	0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
88 	0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x7C, 0x7C,
89 	0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
90 	0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
91 	0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80,
92 	0x03, 0x80, 0x03, 0x80, 0xFF, 0xFE, 0xFE, 0xFE,
93 	0xFF, 0xFE, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80,
94 	0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00
95 };
96 
97 static const struct MouseCursor {
98 	int hotspotX;
99 	int hotspotY;
100 	const byte *bitmap;
101 } mouseCursors[] = {
102 	{ 1, 1, mouseCursorNormal },
103 	{ 0, 0, mouseCursorDisk },
104 	{ 7, 7, mouseCursorCross }
105 };
106 
107 static const byte cursorPalette[] = {
108 	0, 0, 0, 0xff,
109 	0xff, 0xff, 0xff, 0xff
110 };
111 
plotPoint(int x,int y,int color,void * data)112 void plotPoint(int x, int y, int color, void *data) {
113 	byte *output = (byte *)data;
114 	if (x >= 0 && x < 320 && y >= 0 && y < 200) {
115 		output[y * 320 + x] = (byte)color;
116 	}
117 }
118 
119 /**
120  * Initialize renderer
121  */
FWRenderer()122 FWRenderer::FWRenderer() : _savedBackBuffers(), _background(NULL), _backupPal(), _cmd(""),
123 	_messageBg(DEFAULT_MESSAGE_BG), _cmdY(DEFAULT_CMD_Y), _backBuffer(new byte[_screenSize]),
124 	_activePal(), _changePal(0), _showCollisionPage(false), _fadeToBlackLastCalledMs(0) {
125 
126 	assert(_backBuffer);
127 
128 	memset(_backBuffer, 0, _screenSize);
129 	memset(_bgName, 0, sizeof(_bgName));
130 }
131 
removeSavedBackBuffers()132 void FWRenderer::removeSavedBackBuffers() {
133 	for (int i = 0; i < ARRAYSIZE(_savedBackBuffers); i++) {
134 		if (_savedBackBuffers[i]) {
135 			delete[] _savedBackBuffers[i];
136 			_savedBackBuffers[i] = nullptr;
137 		}
138 	}
139 }
140 
141 /**
142  * Destroy renderer
143  */
~FWRenderer()144 FWRenderer::~FWRenderer() {
145 	delete[] _background;
146 	delete[] _backBuffer;
147 
148 	removeSavedBackBuffers();
149 
150 	clearMenuStack();
151 }
152 
initialize()153 bool FWRenderer::initialize() {
154 	_backupPal = _activePal = Palette(kLowPalFormat, kLowPalNumColors);
155 	return true;
156 }
157 
158 
159 /**
160  * Reset renderer state
161  */
clear()162 void FWRenderer::clear() {
163 	delete[] _background;
164 
165 	_background = NULL;
166 	_backupPal.clear();
167 	_activePal.clear();
168 
169 	memset(_backBuffer, 0, _screenSize);
170 	removeSavedBackBuffers();
171 
172 	_cmd = "";
173 	_cmdY = DEFAULT_CMD_Y;
174 	_messageBg = DEFAULT_MESSAGE_BG;
175 	_changePal = 0;
176 	_showCollisionPage = false;
177 }
178 
getFadeInSourcePalette()179 const Cine::Palette& FWRenderer::getFadeInSourcePalette() {
180 	return _backupPal;
181 }
182 
183 /**
184  * Draw 1bpp sprite using selected color
185  * @param obj Object info
186  * @param fillColor Sprite color
187  */
fillSprite(const ObjectStruct & obj,uint8 color)188 void FWRenderer::fillSprite(const ObjectStruct &obj, uint8 color) {
189 	const byte *data = g_cine->_animDataTable[obj.frame].data();
190 	int x, y, width, height;
191 
192 	x = obj.x;
193 	y = obj.y;
194 	width = g_cine->_animDataTable[obj.frame]._realWidth;
195 	height = g_cine->_animDataTable[obj.frame]._height;
196 
197 	gfxFillSprite(data, width, height, _backBuffer, x, y, color);
198 }
199 
200 /**
201  * Draw 1bpp sprite using selected color on background
202  * @param obj Object info
203  * @param fillColor Sprite color
204  */
incrustMask(const BGIncrust & incrust,uint8 color)205 void FWRenderer::incrustMask(const BGIncrust &incrust, uint8 color) {
206 	const ObjectStruct &obj = g_cine->_objectTable[incrust.objIdx];
207 	const byte *data = g_cine->_animDataTable[obj.frame].data();
208 	int x, y, width, height;
209 
210 	x = obj.x;
211 	y = obj.y;
212 	width = g_cine->_animDataTable[obj.frame]._realWidth;
213 	height = g_cine->_animDataTable[obj.frame]._height;
214 
215 	gfxFillSprite(data, width, height, _background, x, y, color);
216 }
217 
218 /**
219  * Draw color sprite using with external mask
220  * @param obj Object info
221  * @param mask External mask
222  */
drawMaskedSprite(const ObjectStruct & obj,const byte * mask)223 void FWRenderer::drawMaskedSprite(const ObjectStruct &obj, const byte *mask) {
224 	const byte *data = g_cine->_animDataTable[obj.frame].data();
225 	int x, y, width, height;
226 
227 	x = obj.x;
228 	y = obj.y;
229 	width = g_cine->_animDataTable[obj.frame]._realWidth;
230 	height = g_cine->_animDataTable[obj.frame]._height;
231 
232 	assert(mask);
233 
234 	drawSpriteRaw(data, mask, width, height, _backBuffer, x, y);
235 }
236 
237 /**
238  * Draw color sprite
239  * @param obj Object info
240  */
drawSprite(const ObjectStruct & obj)241 void FWRenderer::drawSprite(const ObjectStruct &obj) {
242 	const byte *mask = g_cine->_animDataTable[obj.frame].mask();
243 	drawMaskedSprite(obj, mask);
244 }
245 
246 /**
247  * Draw color sprite on background
248  * @param obj Object info
249  */
incrustSprite(const BGIncrust & incrust)250 void FWRenderer::incrustSprite(const BGIncrust &incrust) {
251 	const ObjectStruct &obj = g_cine->_objectTable[incrust.objIdx];
252 
253 	const byte *data = g_cine->_animDataTable[obj.frame].data();
254 	const byte *mask = g_cine->_animDataTable[obj.frame].mask();
255 	int x, y, width, height;
256 
257 	x = obj.x;
258 	y = obj.y;
259 	width = g_cine->_animDataTable[obj.frame]._realWidth;
260 	height = g_cine->_animDataTable[obj.frame]._height;
261 
262 	// There was an assert(mask) here before but it made savegame loading
263 	// in Future Wars sometimes fail the assertion (e.g. see bug #3868).
264 	// Not drawing sprites that have no mask seems to work, but not sure
265 	// if this is really a correct way to fix this.
266 	if (mask) {
267 		drawSpriteRaw(data, mask, width, height, _background, x, y);
268 	} else { // mask == NULL
269 		warning("FWRenderer::incrustSprite: Skipping maskless sprite (frame=%d)", obj.frame);
270 	}
271 }
272 
273 /**
274  * Draw command box on screen
275  */
drawCommand()276 void FWRenderer::drawCommand() {
277 	if (disableSystemMenu == 0) {
278 		int x = 10, y = _cmdY;
279 
280 		drawPlainBox(x, y, 301, 11, 0);
281 		drawBorder(x - 1, y - 1, 302, 12, 2);
282 
283 		x += 2;
284 		y += 2;
285 
286 		for (unsigned int i = 0; i < _cmd.size(); i++) {
287 			x = drawChar(_cmd[i], x, y);
288 		}
289 	}
290 }
291 
drawString(const char * string,byte param)292 void FWRenderer::drawString(const char *string, byte param) {
293 	int width;
294 	byte minBrightnessColorIndex = 4;
295 
296 	bool useEnsureContrast = true;
297 	if (useEnsureContrast && g_cine->getGameType() == Cine::GType_OS) {
298 		bool paletteChanged = _activePal.ensureContrast(minBrightnessColorIndex);
299 		if (paletteChanged) {
300 			clearBackBuffer();
301 			setPalette();
302 		}
303 	}
304 
305 	// Both Future Wars and Operation Stealth 16 color PC versions do this
306 	int y = 80;
307 	if (param == 1) {
308 		y = 20;
309 	} else if (param == 2) {
310 		y = 140;
311 	}
312 
313 	width = getStringWidth(string);
314 
315 	if (width == 0) {
316 		return;
317 	}
318 
319 	width = MIN<int>(width + 20, 300);
320 
321 	drawMessage(string, (320 - width) / 2, y, width, minBrightnessColorIndex);
322 
323 	blit();
324 }
325 
326 /**
327  * Draw message in a box
328  * @param str Message to draw
329  * @param x Top left message box corner coordinate
330  * @param y Top left message box corner coordinate
331  * @param width Message box width
332  * @param color Message box background color (Or if negative draws only the text)
333  * @param draw Draw the message box and its contents? If false then draw nothing
334  * but simply return the maximum Y position used by the message box.
335  * @note Negative colors are used in Operation Stealth's timed cutscenes
336  * (e.g. when first meeting The Movement for the Liberation of Santa Paragua).
337  * @return The maximum Y position used by the message box (Inclusive)
338  */
drawMessage(const char * str,int x,int y,int width,int color,bool draw)339 int FWRenderer::drawMessage(const char *str, int x, int y, int width, int color, bool draw) {
340 	// Keep a vertically overflowing message box inside the main screen (Fixes bug #11708).
341 	if (draw) {
342 		int maxY = this->drawMessage(str, x, y, width, color, false);
343 		if (maxY > 199) {
344 			y -= (maxY - 199);
345 			if (y < 0) {
346 				y = 0;
347 			}
348 		}
349 	}
350 
351 	int i, tx, ty, tw;
352 	int line = 0, words = 0, cw = 0;
353 	int space = 0, extraSpace = 0;
354 
355 	if (draw && color >= 0) {
356 		if (useTransparentDialogBoxes())
357 			drawTransparentBox(x, y, width, 4);
358 		else
359 			drawPlainBox(x, y, width, 4, color);
360 	}
361 	tx = x + 4;
362 	ty = str[0] ? y - 5 : y + 4;
363 	tw = width - 8;
364 
365 	for (i = 0; str[i]; i++, line--) {
366 		// Fit line of text into textbox
367 		if (!line) {
368 			while (str[i] == ' ')
369 				i++;
370 			line = fitLine(str + i, tw, words, cw);
371 
372 			if (str[i + line] != '\0' && str[i + line] != 0x7C && words) {
373 				space = (tw - cw) / words;
374 				extraSpace = (tw - cw) % words;
375 			} else {
376 				space = 5;
377 				extraSpace = 0;
378 			}
379 
380 			ty += 9;
381 			if (draw && color >= 0) {
382 				if (useTransparentDialogBoxes())
383 					drawTransparentBox(x, ty, width, 9);
384 				else
385 					drawPlainBox(x, ty, width, 9, color);
386 			}
387 			tx = x + 4;
388 		}
389 
390 		// draw characters
391 		if (str[i] == ' ') {
392 			tx += space + extraSpace;
393 
394 			if (extraSpace) {
395 				extraSpace = 0;
396 			}
397 		} else {
398 			tx = drawChar(str[i], tx, ty, draw);
399 		}
400 	}
401 
402 	ty += 9;
403 	if (draw && color >= 0) {
404 		if (useTransparentDialogBoxes())
405 			drawTransparentBox(x, ty, width, 4);
406 		else
407 			drawPlainBox(x, ty, width, 4, color);
408 		drawDoubleBorder(x, y, width, ty - y + 4, (useTransparentDialogBoxes() ? transparentDialogBoxStartColor() : 0) + 2);
409 	}
410 
411 	return ty + 4;
412 }
413 
414 /**
415  * Draw rectangle on screen
416  * @param x Top left corner coordinate
417  * @param y Top left corner coordinate
418  * @param width Rectangle width (Negative values draw the box horizontally flipped)
419  * @param height Rectangle height (Negative values draw the box vertically flipped)
420  * @param color Fill color
421  * @note An on-screen rectangle's drawn width is always at least one.
422  * @note An on-screen rectangle's drawn height is always at least one.
423  */
drawPlainBox(int x,int y,int width,int height,byte color)424 void FWRenderer::drawPlainBox(int x, int y, int width, int height, byte color) {
425 	// Handle horizontally flipped boxes
426 	if (width < 0) {
427 		width = ABS(width);
428 		x -= width;
429 	}
430 
431 	// Handle vertically flipped boxes
432 	if (height < 0) {
433 		height = ABS(height);
434 		y -= height;
435 	}
436 
437 	// Clip the rectangle to screen dimensions
438 	Common::Rect boxRect(x, y, x + width, y + height);
439 	Common::Rect screenRect(320, 200);
440 	boxRect.clip(screenRect);
441 
442 	byte *dest = _backBuffer + boxRect.top * 320 + boxRect.left;
443 	for (int i = 0; i < boxRect.height(); i++) {
444 		memset(dest + i * 320, color, boxRect.width());
445 	}
446 }
447 
useTransparentDialogBoxes()448 bool FWRenderer::useTransparentDialogBoxes() {
449 	return _activePal.colorCount() == 16 &&
450 		((g_cine->getPlatform() == Common::kPlatformAmiga) ||
451 		ConfMan.getBool("transparentdialogboxes"));
452 }
453 
transparentDialogBoxStartColor()454 byte FWRenderer::transparentDialogBoxStartColor() {
455 	return 16;
456 }
457 
drawTransparentBox(int x,int y,int width,int height)458 void FWRenderer::drawTransparentBox(int x, int y, int width, int height) {
459 	byte startColor = transparentDialogBoxStartColor();
460 
461 	// Handle horizontally flipped boxes
462 	if (width < 0) {
463 		width = ABS(width);
464 		x -= width;
465 	}
466 
467 	// Handle vertically flipped boxes
468 	if (height < 0) {
469 		height = ABS(height);
470 		y -= height;
471 	}
472 
473 	// Clip the rectangle to screen dimensions
474 	Common::Rect boxRect(x, y, x + width, y + height);
475 	Common::Rect screenRect(320, 200);
476 	boxRect.clip(screenRect);
477 
478 	byte *dest = _backBuffer + boxRect.top * 320 + boxRect.left;
479 	const int lineAdd = 320 - boxRect.width();
480 	for (int i = 0; i < boxRect.height(); ++i) {
481 		for (int j = 0; j < boxRect.width(); ++j, ++dest) {
482 			if (*dest < startColor)
483 				*dest += startColor;
484 		}
485 		dest += lineAdd;
486 	}
487 }
488 
489 /**
490  * Draw empty rectangle
491  * @param x Top left corner coordinate
492  * @param y Top left corner coordinate
493  * @param width Rectangle width
494  * @param height Rectangle height
495  * @param color Line color
496  */
drawBorder(int x,int y,int width,int height,byte color)497 void FWRenderer::drawBorder(int x, int y, int width, int height, byte color) {
498 	drawLine(x, y, width, 1, color);
499 	drawLine(x, y + height, width, 1, color);
500 	drawLine(x, y, 1, height, color);
501 	drawLine(x + width, y, 1, height + 1, color);
502 }
503 
504 /**
505  * Draw empty 2 color rectangle (inner line color is black)
506  * @param x Top left corner coordinate
507  * @param y Top left corner coordinate
508  * @param width Rectangle width
509  * @param height Rectangle height
510  * @param color Outter line color
511  */
drawDoubleBorder(int x,int y,int width,int height,byte color)512 void FWRenderer::drawDoubleBorder(int x, int y, int width, int height, byte color) {
513 	drawBorder(x + 1, y + 1, width - 2, height - 2, 0);
514 	drawBorder(x, y, width, height, color);
515 }
516 
517 /**
518  * Draw text character on screen
519  * @param character Character to draw
520  * @param x Character coordinate
521  * @param y Character coordinate
522  * @param draw Draw the character?
523  */
drawChar(char character,int x,int y,bool draw)524 int FWRenderer::drawChar(char character, int x, int y, bool draw) {
525 	int width;
526 
527 	if (character == ' ') {
528 		x += 5;
529 	} else if ((width = g_cine->_textHandler.fontParamTable[(unsigned char)character].characterWidth)) {
530 		int idx = g_cine->_textHandler.fontParamTable[(unsigned char)character].characterIdx;
531 		if (draw) {
532 			drawSpriteRaw(g_cine->_textHandler.textTable[idx][FONT_DATA], g_cine->_textHandler.textTable[idx][FONT_MASK], FONT_WIDTH, FONT_HEIGHT, _backBuffer, x, y);
533 		}
534 		x += width + 1;
535 	}
536 
537 	return x;
538 }
539 
540 /**
541  * Clears the character glyph to black
542  * This function is called "undrawChar", because the original only applies
543  * this drawing after the original glyph has been drawn.
544  * Possible TODO: Find a better name.
545  * @param character Character to undraw
546  * @param x Character coordinate
547  * @param y Character coordinate
548  */
undrawChar(char character,int x,int y)549 int FWRenderer::undrawChar(char character, int x, int y) {
550 	int width;
551 
552 	if (character == ' ') {
553 		x += 5;
554 	} else if ((width = g_cine->_textHandler.fontParamTable[(unsigned char)character].characterWidth)) {
555 		int idx = g_cine->_textHandler.fontParamTable[(unsigned char)character].characterIdx;
556 		const byte *sprite = g_cine->_textHandler.textTable[idx][FONT_DATA];
557 		for (uint i = 0; i < FONT_HEIGHT; ++i) {
558 			byte *dst = _backBuffer + (y + i) * 320 + x;
559 			for (uint j = 0; j < FONT_WIDTH; ++j, ++dst) {
560 				// The original does this based on whether bit 1 of the pixel
561 				// is set. Since that's the only bit ever set in (FW) this
562 				// check should be fine.
563 				// TODO: Check how Operation Stealth Amiga works
564 				if (*sprite++) {
565 					*dst = 0;
566 				}
567 			}
568 		}
569 		x += width + 1;
570 	}
571 
572 	return x;
573 }
574 
getStringWidth(const char * str)575 int FWRenderer::getStringWidth(const char *str) {
576 	int padding = (g_cine->getGameType() == Cine::GType_OS) ? 2 : 1;
577 	const char *p = str;
578 	int width = 0;
579 	int maxWidth = 0;
580 
581 	while (*p) {
582 		unsigned char currChar = (unsigned char)*p;
583 		if (currChar == '|') {
584 			maxWidth = MAX<int>(width, maxWidth);
585 			width = 0;
586 		} else if (currChar == ' ')
587 			width += 5;
588 		else
589 			width += g_cine->_textHandler.fontParamTable[currChar].characterWidth + padding;
590 		p++;
591 	}
592 
593 	maxWidth = MAX<int>(width, maxWidth);
594 
595 	return width;
596 }
597 
598 /**
599  * Draw Line
600  * @param x Line end coordinate
601  * @param y Line end coordinate
602  * @param width Horizontal line length
603  * @param height Vertical line length
604  * @param color Line color
605  * @note Either width or height must be equal to 1
606  */
drawLine(int x,int y,int width,int height,byte color)607 void FWRenderer::drawLine(int x, int y, int width, int height, byte color) {
608 	// this line is a special case of rectangle ;-)
609 	drawPlainBox(x, y, width, height, color);
610 }
611 
612 /**
613  * Hide invisible parts of the sprite
614  * @param[in,out] mask Mask to be updated
615  * @param it Overlay info from overlayList
616  */
remaskSprite(byte * mask,Common::List<overlay>::iterator it)617 void FWRenderer::remaskSprite(byte *mask, Common::List<overlay>::iterator it) {
618 	AnimData &sprite = g_cine->_animDataTable[g_cine->_objectTable[it->objIdx].frame];
619 	int x, y, width, height, idx;
620 	int mx, my, mw, mh;
621 
622 	x = g_cine->_objectTable[it->objIdx].x;
623 	y = g_cine->_objectTable[it->objIdx].y;
624 	width = sprite._realWidth;
625 	height = sprite._height;
626 
627 	for (++it; it != g_cine->_overlayList.end(); ++it) {
628 		if (it->type != 5) {
629 			continue;
630 		}
631 
632 		idx = ABS(g_cine->_objectTable[it->objIdx].frame);
633 		mx = g_cine->_objectTable[it->objIdx].x;
634 		my = g_cine->_objectTable[it->objIdx].y;
635 		mw = g_cine->_animDataTable[idx]._realWidth;
636 		mh = g_cine->_animDataTable[idx]._height;
637 
638 		gfxUpdateSpriteMask(mask, x, y, width, height, g_cine->_animDataTable[idx].data(), mx, my, mw, mh);
639 	}
640 }
641 
642 /**
643  * Draw background to backbuffer
644  */
drawBackground()645 void FWRenderer::drawBackground() {
646 	assert(_background);
647 	memcpy(_backBuffer, _background, _screenSize);
648 }
649 
clearBackBuffer()650 void FWRenderer::clearBackBuffer() {
651 	if (_backBuffer) {
652 		memset(_backBuffer, 0, _screenSize);
653 	}
654 }
655 
656 /**
657  * Draw one overlay
658  * @param it Overlay info
659  */
renderOverlay(const Common::List<overlay>::iterator & it)660 void FWRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
661 	int idx, len, width;
662 	ObjectStruct *obj;
663 	AnimData *sprite;
664 	byte *mask;
665 
666 	switch (it->type) {
667 	// color sprite
668 	case 0:
669 		if (g_cine->_objectTable[it->objIdx].frame < 0) {
670 			return;
671 		}
672 		sprite = &g_cine->_animDataTable[g_cine->_objectTable[it->objIdx].frame];
673 		len = sprite->_realWidth * sprite->_height;
674 		mask = new byte[len];
675 		if (sprite->mask() != NULL) {
676 			memcpy(mask, sprite->mask(), len);
677 		} else {
678 			// This case happens in French Amiga Future Wars (Bug #10643) when
679 			// walking left from the scene with the open manhole cover. This
680 			// seems to work fine at least in this case.
681 			memset(mask, 0, len);
682 		}
683 		remaskSprite(mask, it);
684 		drawMaskedSprite(g_cine->_objectTable[it->objIdx], mask);
685 		delete[] mask;
686 		break;
687 
688 	// game message
689 	case 2:
690 		if (it->objIdx >= g_cine->_messageTable.size()) {
691 			return;
692 		}
693 
694 		_messageLen += g_cine->_messageTable[it->objIdx].size();
695 		drawMessage(g_cine->_messageTable[it->objIdx].c_str(), it->x, it->y, it->width, it->color);
696 		waitForPlayerClick = 1;
697 		break;
698 
699 	// action failure message
700 	case 3:
701 		idx = it->objIdx * 4 + g_cine->_rnd.getRandomNumber(3);
702 		len = strlen(failureMessages[idx]);
703 		_messageLen += len;
704 		width = 6 * len + 20;
705 		width = width > 300 ? 300 : width;
706 
707 		drawMessage(failureMessages[idx], (320 - width) / 2, 80, width, 4);
708 		waitForPlayerClick = 1;
709 		break;
710 
711 	// bitmap
712 	case 4:
713 		assert(it->objIdx < NUM_MAX_OBJECT);
714 		obj = &g_cine->_objectTable[it->objIdx];
715 
716 		if (obj->frame < 0) {
717 			return;
718 		}
719 
720 		if (!g_cine->_animDataTable[obj->frame].data()) {
721 			return;
722 		}
723 
724 		fillSprite(*obj);
725 		break;
726 
727 	default:
728 		break;
729 	}
730 }
731 
732 /**
733  * Draw overlays
734  */
drawOverlays()735 void FWRenderer::drawOverlays() {
736 	// WORKAROUND: Show player behind stairs by moving him behind everything
737 	// in the scene right after leaving Dr. Why's control room.
738 	if (g_cine->getGameType() == Cine::GType_OS &&
739 		g_cine->_overlayList.size() >= 2 &&
740 		g_cine->_overlayList.back().objIdx == 1 &&
741 		g_cine->_objectTable.size() >= 2 &&
742 		g_cine->_objectTable[1].x == 231 &&
743 		g_cine->_objectTable[1].y >= 142 &&
744 		scumm_stricmp(renderer->getBgName(), "56VIDE.PI1") == 0) {
745 		Cine::overlay playerOverlay = g_cine->_overlayList.back();
746 		g_cine->_overlayList.pop_back();
747 		g_cine->_overlayList.push_front(playerOverlay);
748 	}
749 
750 	Common::List<overlay>::iterator it;
751 
752 	for (it = g_cine->_overlayList.begin(); it != g_cine->_overlayList.end(); ++it) {
753 		renderOverlay(it);
754 	}
755 }
756 
757 /**
758  * Draw another frame
759  */
drawFrame(bool wait)760 void FWRenderer::drawFrame(bool wait) {
761 	drawBackground();
762 	drawOverlays();
763 
764 	if (!_cmd.empty()) {
765 		drawCommand();
766 	}
767 
768 	// DIFFERENCE FROM DISASSEMBLY:
769 	// Waiting for g_cine->getTimerDelay() since last call to this function
770 	// from mainLoop() was in Future Wars and Operation Stealth disassembly here.
771 	// The wait did nothing else but simply wait for the waiting period to end.
772 	// It has been moved to manageEvents() function call in executePlayerInput()
773 	// to make better use of the waiting period. Now it is used to read mouse button
774 	// status and possibly update the command line while moving the mouse
775 	// (e.g. "EXAMINE DOOR" -> "EXAMINE BUTTON").
776 
777 	if (reloadBgPalOnNextFlip) {
778 		_activePal = getFadeInSourcePalette();
779 		reloadBgPalOnNextFlip = 0;
780 		_changePal = 1; // From disassembly
781 	}
782 
783 	if (_changePal) { // From disassembly
784 		setPalette();
785 		_changePal = 0; // From disassembly
786 	}
787 
788 	const int menus = _menuStack.size();
789 	for (int i = 0; i < menus; ++i)
790 		_menuStack[i]->drawMenu(*this, (i == menus - 1));
791 
792 	blit();
793 
794 	if (gfxFadeInRequested) {
795 		fadeFromBlack();
796 		gfxFadeInRequested = 0;
797 	}
798 }
799 
800 /**
801  * Turn on or off the showing of the collision page.
802  * If turned on the blitting routine shows the collision page instead of the back buffer.
803  * @note Useful for debugging collision page related problems.
804  */
showCollisionPage(bool state)805 void FWRenderer::showCollisionPage(bool state) {
806 	_showCollisionPage = state;
807 }
808 
blitBackBuffer()809 void FWRenderer::blitBackBuffer() {
810 	blit(false);
811 }
812 
blit(bool useCollisionPage)813 void FWRenderer::blit(bool useCollisionPage) {
814 	// Show the back buffer or the collision page. Normally the back
815 	// buffer but showing the collision page is useful for debugging.
816 	byte *source = (useCollisionPage ? collisionPage : _backBuffer);
817 	g_system->copyRectToScreen(source, 320, 0, 0, 320, 200);
818 	g_system->updateScreen();
819 }
820 
821 /**
822  * Update screen
823  */
blit()824 void FWRenderer::blit() {
825 	blit(_showCollisionPage);
826 }
827 
hasSavedBackBuffer(BackBufferSource source)828 bool FWRenderer::hasSavedBackBuffer(BackBufferSource source) {
829 	return source >= 0 && source < MAX_BACK_BUFFER_SOURCES && _savedBackBuffers[source];
830 }
831 
saveBackBuffer(BackBufferSource source)832 void FWRenderer::saveBackBuffer(BackBufferSource source) {
833 	if (_backBuffer && source >= 0 && source < MAX_BACK_BUFFER_SOURCES) {
834 		if (!_savedBackBuffers[source]) {
835 			_savedBackBuffers[source] = new byte[_screenSize];
836 		}
837 		memcpy(_savedBackBuffers[source], _backBuffer, _screenSize);
838 	}
839 }
840 
popSavedBackBuffer(BackBufferSource source)841 void FWRenderer::popSavedBackBuffer(BackBufferSource source) {
842 	restoreSavedBackBuffer(source);
843 	removeSavedBackBuffer(source);
844 }
845 
restoreSavedBackBuffer(BackBufferSource source)846 void FWRenderer::restoreSavedBackBuffer(BackBufferSource source) {
847 	if (_backBuffer && hasSavedBackBuffer(source)) {
848 		memcpy(_backBuffer, _savedBackBuffers[source], _screenSize);
849 		blitBackBuffer();
850 	}
851 }
852 
removeSavedBackBuffer(BackBufferSource source)853 void FWRenderer::removeSavedBackBuffer(BackBufferSource source) {
854 	delete[] _savedBackBuffers[source];
855 	_savedBackBuffers[source] = nullptr;
856 }
857 
858 /**
859  * Set player command string
860  * @param cmd New command string
861  */
setCommand(Common::String cmd)862 void FWRenderer::setCommand(Common::String cmd) {
863 	_cmd = cmd;
864 }
865 
getCommand()866 Common::String FWRenderer::getCommand() {
867 	return _cmd;
868 }
869 
setBlackPalette(bool updateChangePal)870 void FWRenderer::setBlackPalette(bool updateChangePal) {
871 	_activePal.fillWithBlack();
872 	if (updateChangePal) {
873 		_changePal = 1; // From disassembly when called from main loop's initialization section
874 	}
875 }
876 
setPalette()877 void FWRenderer::setPalette() {
878 	assert(_activePal.isValid() && !_activePal.empty());
879 	_activePal.setGlobalOSystemPalette();
880 }
881 
addBackground(const char * bgName,uint16 bgIdx)882 int16 FWRenderer::addBackground(const char *bgName, uint16 bgIdx) {
883 	error("Future Wars renderer doesn't support multiple backgrounds");
884 }
885 
886 /**
887  * Load background into renderer
888  * @param bg Raw background data
889  * @todo Combine with OSRenderer's version of loadBg16
890  */
loadBg16(const byte * bg,const char * name,unsigned int idx)891 void FWRenderer::loadBg16(const byte *bg, const char *name, unsigned int idx) {
892 	assert(idx == 0);
893 
894 	if (!_background) {
895 		_background = new byte[_screenSize];
896 	}
897 
898 	assert(_background);
899 
900 	Common::strlcpy(_bgName, name, sizeof(_bgName));
901 
902 	// Load the 16 color palette
903 	_backupPal.load(bg, kLowPalNumBytes, kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
904 
905 	// Jump over the palette data to the background data
906 	bg += kLowPalNumBytes;
907 
908 	gfxConvertSpriteToRaw(_background, bg, 160, 200);
909 }
910 
911 /**
912  * Placeholder for Operation Stealth implementation
913  */
loadCt16(const byte * ct,const char * name)914 void FWRenderer::loadCt16(const byte *ct, const char *name) {
915 	error("Future Wars renderer doesn't support multiple backgrounds");
916 }
917 
918 /**
919  * Placeholder for Operation Stealth implementation
920  */
loadBg256(const byte * bg,const char * name,unsigned int idx)921 void FWRenderer::loadBg256(const byte *bg, const char *name, unsigned int idx) {
922 	error("Future Wars renderer doesn't support multiple backgrounds");
923 }
924 
925 /**
926  * Placeholder for Operation Stealth implementation
927  */
loadCt256(const byte * ct,const char * name)928 void FWRenderer::loadCt256(const byte *ct, const char *name) {
929 	error("Future Wars renderer doesn't support multiple backgrounds");
930 }
931 
932 /**
933  * Placeholder for Operation Stealth implementation
934  */
selectBg(unsigned int idx)935 void FWRenderer::selectBg(unsigned int idx) {
936 	error("Future Wars renderer doesn't support multiple backgrounds");
937 }
938 
939 /**
940  * Placeholder for Operation Stealth implementation
941  */
selectScrollBg(unsigned int idx)942 void FWRenderer::selectScrollBg(unsigned int idx) {
943 	error("Future Wars renderer doesn't support multiple backgrounds");
944 }
945 
946 /**
947  * Placeholder for Operation Stealth implementation
948  */
setScroll(unsigned int shift)949 void FWRenderer::setScroll(unsigned int shift) {
950 	error("Future Wars renderer doesn't support multiple backgrounds");
951 }
952 
953 /**
954  * Future Wars has no scrolling backgrounds so scroll value is always zero.
955  */
getScroll() const956 uint FWRenderer::getScroll() const {
957 	return 0;
958 }
959 
960 /**
961  * Placeholder for Operation Stealth implementation
962  */
removeBg(unsigned int idx)963 void FWRenderer::removeBg(unsigned int idx) {
964 	error("Future Wars renderer doesn't support multiple backgrounds");
965 }
966 
saveBgNames(Common::OutSaveFile & fHandle)967 void FWRenderer::saveBgNames(Common::OutSaveFile &fHandle) {
968 	fHandle.write(_bgName, 13);
969 }
970 
getBgName(uint idx) const971 const char *FWRenderer::getBgName(uint idx) const {
972 	assert(idx == 0);
973 	return _bgName;
974 }
975 
976 /**
977  * Restore active and backup palette from save
978  * @param fHandle Savefile open for reading
979  */
restorePalette(Common::SeekableReadStream & fHandle,int version)980 void FWRenderer::restorePalette(Common::SeekableReadStream &fHandle, int version) {
981 	byte buf[kLowPalNumBytes];
982 
983 	// Load the active 16 color palette from file
984 	fHandle.read(buf, kLowPalNumBytes);
985 	_activePal.load(buf, sizeof(buf), kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
986 
987 	// Load the backup 16 color palette from file
988 	fHandle.read(buf, kLowPalNumBytes);
989 	_backupPal.load(buf, sizeof(buf), kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
990 
991 	_changePal = 1; // From disassembly
992 }
993 
994 /**
995  * Write active and backup palette to save
996  * @param fHandle Savefile open for writing
997  */
savePalette(Common::OutSaveFile & fHandle)998 void FWRenderer::savePalette(Common::OutSaveFile &fHandle) {
999 	byte buf[kLowPalNumBytes];
1000 
1001 	// Make sure the active palette has the correct format and color count
1002 	assert(_activePal.colorFormat() == kLowPalFormat);
1003 	assert(_activePal.colorCount() == kLowPalNumColors);
1004 
1005 	// Make sure the backup palette has the correct format and color count
1006 	assert(_backupPal.colorFormat() == kLowPalFormat);
1007 	assert(_backupPal.colorCount() == kLowPalNumColors);
1008 
1009 	// Write the active palette to the file
1010 	_activePal.save(buf, sizeof(buf), CINE_BIG_ENDIAN);
1011 	fHandle.write(buf, kLowPalNumBytes);
1012 
1013 	// Write the backup palette to the file
1014 	_backupPal.save(buf, sizeof(buf), CINE_BIG_ENDIAN);
1015 	fHandle.write(buf, kLowPalNumBytes);
1016 }
1017 
1018 /**
1019  * Write active and backup palette to save
1020  * @param fHandle Savefile open for writing
1021  * @todo Add support for saving the palette in the 16 color version of Operation Stealth.
1022  *       Possibly combine with FWRenderer's savePalette-method?
1023  */
savePalette(Common::OutSaveFile & fHandle)1024 void OSRenderer::savePalette(Common::OutSaveFile &fHandle) {
1025 	byte buf[kHighPalNumBytes];
1026 
1027 	// We can have 16 color palette in many cases
1028 	fHandle.writeUint16LE(_activePal.colorCount());
1029 
1030 	// Write the active 256 color palette.
1031 	_activePal.save(buf, sizeof(buf), CINE_LITTLE_ENDIAN);
1032 	fHandle.write(buf, kHighPalNumBytes);
1033 
1034 	// Write the backup 256 color palette.
1035 	_backupPal.save(buf, sizeof(buf), CINE_LITTLE_ENDIAN);
1036 	fHandle.write(buf, kHighPalNumBytes);
1037 }
1038 
1039 /**
1040  * Restore active and backup palette from save
1041  * @param fHandle Savefile open for reading
1042  */
restorePalette(Common::SeekableReadStream & fHandle,int version)1043 void OSRenderer::restorePalette(Common::SeekableReadStream &fHandle, int version) {
1044 	byte buf[kHighPalNumBytes];
1045 	uint colorCount = (version > 0) ? fHandle.readUint16LE() : kHighPalNumColors;
1046 
1047 	// Load the active color palette
1048 	fHandle.read(buf, kHighPalNumBytes);
1049 
1050 	if (colorCount == kHighPalNumColors) {
1051 		// Load the active 256 color palette from file
1052 		_activePal.load(buf, sizeof(buf), kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
1053 	} else {
1054 		// Load the active 16 color palette from file
1055 		_activePal.load(buf, sizeof(buf), kLowPalFormat, kLowPalNumColors, CINE_LITTLE_ENDIAN);
1056 	}
1057 
1058 	// Load the backup color palette
1059 	fHandle.read(buf, kHighPalNumBytes);
1060 
1061 	if (colorCount == kHighPalNumColors) {
1062 		_backupPal.load(buf, sizeof(buf), kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
1063 	} else {
1064 		_backupPal.load(buf, sizeof(buf), kLowPalFormat, kLowPalNumColors, CINE_LITTLE_ENDIAN);
1065 	}
1066 
1067 	_changePal = 1; // From disassembly
1068 }
1069 
1070 /**
1071  * Rotate active palette
1072  * @param a First color to rotate
1073  * @param b Last color to rotate
1074  * @param c Possibly rotation step, must be 0 or 1 at the moment
1075  */
rotatePalette(int firstIndex,int lastIndex,int mode)1076 void FWRenderer::rotatePalette(int firstIndex, int lastIndex, int mode) {
1077 	if (mode == 1) {
1078 		_activePal.rotateRight(firstIndex, lastIndex);
1079 	} else if (mode == 2) {
1080 		_activePal.rotateLeft(firstIndex, lastIndex);
1081 	} else {
1082 		_activePal = _backupPal;
1083 	}
1084 	setPalette();
1085 }
1086 
rotatePalette(int firstIndex,int lastIndex,int mode)1087 void OSRenderer::rotatePalette(int firstIndex, int lastIndex, int mode) {
1088 	if (mode == 1) {
1089 		_activePal.rotateRight(firstIndex, lastIndex);
1090 	} else if (mode == 2) {
1091 		_activePal.rotateLeft(firstIndex, lastIndex);
1092 	} else if (_currentBg > 0 && _currentBg < 8) {
1093 		_activePal = _bgTable[_currentBg].pal;
1094 	} else { // background indices 0 and 8 use backup palette
1095 		_activePal = _backupPal;
1096 	}
1097 	setPalette();
1098 }
1099 
1100 /**
1101  * Copy part of backup palette to active palette and transform
1102  * @param first First color to transform
1103  * @param last Last color to transform
1104  * @param r Red channel transformation
1105  * @param g Green channel transformation
1106  * @param b Blue channel transformation
1107  */
transformPalette(int first,int last,int r,int g,int b)1108 void FWRenderer::transformPalette(int first, int last, int r, int g, int b) {
1109 	if (!_activePal.isValid() || _activePal.empty()) {
1110 		_activePal = Cine::Palette(kLowPalFormat, kLowPalNumColors);
1111 	}
1112 
1113 	_backupPal.saturatedAddColor(_activePal, first, last, r, g, b);
1114 	_changePal = 1; // From disassembly
1115 	gfxFadeOutCompleted = 0;
1116 }
1117 
fadeDelayMs()1118 uint FWRenderer::fadeDelayMs() {
1119 	// For PC wait for vertical retrace and wait for three timer interrupt ticks.
1120 	// On PC vertical retrace is 70Hz (1000ms / 70 ~= 14.29ms) and
1121 	// timer interrupt tick is set to (10923000ms / 1193180) ~= 9.15ms.
1122 	// So 14.29ms + 3 * 9.15ms ~= 41.74ms ~= 42ms. That's the maximum to wait for PC.
1123 	// Because the vertical retrace might come earlier the minimum to wait is
1124 	// 0ms + 3 * 9.15ms (The wait for three timer ticks is absolute) = 27.45ms ~= 27ms.
1125 	// So the wait on PC is something between 27ms and 42ms.
1126 	// Probably something else on Amiga (Didn't they have 50Hz or 60Hz monitors?).
1127 	return 42;
1128 }
1129 
fadeToBlackMinMs()1130 uint FWRenderer::fadeToBlackMinMs() {
1131 	return 1000;
1132 }
1133 
1134 /**
1135  * Fade to black
1136  * \bug Operation Stealth sometimes seems to fade to black using
1137  * transformPalette resulting in double fadeout
1138  */
fadeToBlack()1139 void FWRenderer::fadeToBlack() {
1140 	assert(_activePal.isValid() && !_activePal.empty());
1141 
1142 	bool skipFade = false;
1143 	uint32 now = g_system->getMillis();
1144 
1145 	// HACK: Try to cirmumvent double fade outs by throttling function call.
1146 	if (hacksEnabled && _fadeToBlackLastCalledMs != 0 && (now - _fadeToBlackLastCalledMs) < fadeToBlackMinMs()) {
1147 		skipFade = true;
1148 		warning("Skipping fade to black (Time since last called = %d ms < throttling value of %d ms)",
1149 			now - _fadeToBlackLastCalledMs, fadeToBlackMinMs());
1150 	} else {
1151 		_fadeToBlackLastCalledMs = now;
1152 	}
1153 
1154 	for (int i = (skipFade ? 7 : 0); i < 8; i++) {
1155 		// Fade out the whole palette by 1/7th
1156 		// (Operation Stealth used 36 / 252, which is 1 / 7. Future Wars used 1 / 7 directly).
1157 		_activePal.saturatedAddNormalizedGray(_activePal, 0, _activePal.colorCount() - 1, -1, 7);
1158 
1159 		setPalette();
1160 		g_system->updateScreen();
1161 		g_system->delayMillis(fadeDelayMs());
1162 	}
1163 
1164 	clearBackBuffer();
1165 	forbidBgPalReload = gfxFadeOutCompleted = 1;
1166 
1167 	// HACK: This is not present in disassembly
1168 	// but this is an attempt to prevent flashing a
1169 	// normally illuminated screen and then fading it in by
1170 	// resetting possible pending background palette reload.
1171 	if (hacksEnabled) {
1172 		reloadBgPalOnNextFlip = 0;
1173 	}
1174 }
1175 
fadeFromBlack()1176 void FWRenderer::fadeFromBlack() {
1177 	assert(_activePal.isValid() && !_activePal.empty());
1178 
1179 	const Palette& sourcePalette = getFadeInSourcePalette();
1180 
1181 	// Initialize active palette to source palette's format and size if they differ
1182 	if (_activePal.colorFormat() != sourcePalette.colorFormat() || _activePal.colorCount() != sourcePalette.colorCount()) {
1183 		_activePal = Cine::Palette(sourcePalette.colorFormat(), sourcePalette.colorCount());
1184 	}
1185 
1186 	for (int i = 7; i >= 0; i--) {
1187 		sourcePalette.saturatedAddNormalizedGray(_activePal, 0, _activePal.colorCount() - 1, -i, 7);
1188 
1189 		setPalette();
1190 		g_system->updateScreen();
1191 		g_system->delayMillis(fadeDelayMs());
1192 	}
1193 
1194 	forbidBgPalReload = gfxFadeOutCompleted = 0;
1195 }
1196 
1197 // Menu implementation
1198 
pushMenu(Menu * menu)1199 void FWRenderer::pushMenu(Menu *menu) {
1200 	_menuStack.push(menu);
1201 }
1202 
popMenu()1203 Menu *FWRenderer::popMenu() {
1204 	if (_menuStack.empty())
1205 		return 0;
1206 
1207 	Menu *menu = _menuStack.top();
1208 	_menuStack.pop();
1209 	return menu;
1210 }
1211 
clearMenuStack()1212 void FWRenderer::clearMenuStack() {
1213 	Menu *menu = 0;
1214 	while ((menu = popMenu()) != 0)
1215 		delete menu;
1216 }
1217 
SelectionMenu(Common::Point p,int width,Common::StringArray elements)1218 SelectionMenu::SelectionMenu(Common::Point p, int width, Common::StringArray elements)
1219 	: Menu(kSelectionMenu), _pos(p), _width(width), _elements(elements), _selection(-1) {
1220 }
1221 
setSelection(int selection)1222 void SelectionMenu::setSelection(int selection) {
1223 	if (selection >= getElementCount() || selection < -1) {
1224 		warning("Invalid selection %d", selection);
1225 		selection = -1;
1226 	}
1227 
1228 	_selection = selection;
1229 }
1230 
drawMenu(FWRenderer & r,bool top)1231 void SelectionMenu::drawMenu(FWRenderer &r, bool top) {
1232 	const int height = getElementCount() * 9 + 10;
1233 	int x = _pos.x;
1234 	int y = _pos.y;
1235 
1236 	if (x + _width > 319)
1237 		x = 319 - _width;
1238 
1239 	if (y + height > 199)
1240 		y = 199 - height;
1241 
1242 	byte doubleBorderColor = (r.useTransparentDialogBoxes() ? r.transparentDialogBoxStartColor() : 0) + 2;
1243 
1244 	if (r.useTransparentDialogBoxes()) {
1245 		r.drawTransparentBox(x, y, _width, height);
1246 		r.drawDoubleBorder(x, y, _width, height, doubleBorderColor);
1247 	} else {
1248 		r.drawPlainBox(x, y, _width, height, r._messageBg);
1249 		r.drawDoubleBorder(x, y, _width, height, doubleBorderColor);
1250 	}
1251 
1252 	int lineY = y + 4;
1253 
1254 	const int elemCount = getElementCount();
1255 	for (int i = 0; i < elemCount; ++i, lineY += 9) {
1256 		int charX = x + 4;
1257 
1258 		if (i == _selection) {
1259 			int color = (r.useTransparentDialogBoxes() ? 2 : 0);
1260 
1261 			if (!top) {
1262 				color += (r.useTransparentDialogBoxes() ? r.transparentDialogBoxStartColor() : 0);
1263 			}
1264 
1265 			r.drawPlainBox(x + 2, lineY - 1, _width - 3, 9, color);
1266 		}
1267 
1268 		const int size = _elements[i].size();
1269 		for (int j = 0; j < size; ++j) {
1270 			if (r.useTransparentDialogBoxes() && i == _selection) {
1271 				charX = r.undrawChar(_elements[i][j], charX, lineY);
1272 			} else {
1273 				charX = r.drawChar(_elements[i][j], charX, lineY);
1274 			}
1275 		}
1276 	}
1277 }
1278 
TextInputMenu(Common::Point p,int width,const char * info)1279 TextInputMenu::TextInputMenu(Common::Point p, int width, const char *info)
1280 	: Menu(kTextInputMenu), _pos(p), _width(width), _info(info), _input(), _cursor(0) {
1281 }
1282 
setInput(const char * input,int cursor)1283 void TextInputMenu::setInput(const char *input, int cursor) {
1284 	_input = input;
1285 	_cursor = cursor;
1286 }
1287 
drawMenu(FWRenderer & r,bool top)1288 void TextInputMenu::drawMenu(FWRenderer &r, bool top) {
1289 	const int x = _pos.x;
1290 	const int y = _pos.y;
1291 
1292 	int i, tx, ty, tw;
1293 	int line = 0, words = 0, cw = 0;
1294 	int space = 0, extraSpace = 0;
1295 
1296 	if (r.useTransparentDialogBoxes())
1297 		r.drawTransparentBox(x, y, _width, 4);
1298 	else
1299 		r.drawPlainBox(x, y, _width, 4, r._messageBg);
1300 	tx = x + 4;
1301 	ty = _info[0] ? y - 5 : y + 4;
1302 	tw = _width - 8;
1303 
1304 	const int infoSize = _info.size();
1305 
1306 	// input box info message
1307 	for (i = 0; i < infoSize; i++, line--) {
1308 		// fit line of text
1309 		if (!line) {
1310 			line = fitLine(_info.c_str() + i, tw, words, cw);
1311 
1312 			if (i + line < infoSize && words) {
1313 				space = (tw - cw) / words;
1314 				extraSpace = (tw - cw) % words;
1315 			} else {
1316 				space = 5;
1317 				extraSpace = 0;
1318 			}
1319 
1320 			ty += 9;
1321 			if (r.useTransparentDialogBoxes())
1322 				r.drawTransparentBox(x, ty, _width, 9);
1323 			else
1324 				r.drawPlainBox(x, ty, _width, 9, r._messageBg);
1325 			tx = x + 4;
1326 		}
1327 
1328 		// draw characters
1329 		if (_info[i] == ' ') {
1330 			tx += space + extraSpace;
1331 
1332 			if (extraSpace) {
1333 				extraSpace = 0;
1334 			}
1335 		} else {
1336 			tx = r.drawChar(_info[i], tx, ty);
1337 		}
1338 	}
1339 
1340 	// input area background
1341 	ty += 9;
1342 	if (r.useTransparentDialogBoxes())
1343 		r.drawTransparentBox(x, ty, _width, 9);
1344 	else
1345 		r.drawPlainBox(x, ty, _width, 9, r._messageBg);
1346 	r.drawPlainBox(x + 16, ty - 1, _width - 32, 9, 0);
1347 	tx = x + 20;
1348 
1349 	// text in input area
1350 	const int inputSize = _input.size();
1351 	for (i = 0; i < inputSize; i++) {
1352 		tx = r.drawChar(_input[i], tx, ty);
1353 
1354 		if (_cursor == i + 2) {
1355 			r.drawLine(tx, ty - 1, 1, 9, 2);
1356 		}
1357 	}
1358 
1359 	if (_input.empty() || _cursor == 1) {
1360 		r.drawLine(x + 20, ty - 1, 1, 9, 2);
1361 	}
1362 
1363 	ty += 9;
1364 	if (r.useTransparentDialogBoxes())
1365 		r.drawTransparentBox(x, ty, _width, 4);
1366 	else
1367 		r.drawPlainBox(x, ty, _width, 4, r._messageBg);
1368 	r.drawDoubleBorder(x, y, _width, ty - y + 4, (r.useTransparentDialogBoxes() ? r.transparentDialogBoxStartColor() : 0) + 2);
1369 }
1370 
1371 // -------------------
1372 
1373 /**
1374  * Initialize Operation Stealth renderer
1375  */
OSRenderer()1376 OSRenderer::OSRenderer() : FWRenderer(), _bgTable(), _currentBg(0), _scrollBg(0),
1377 	_bgShift(0) {
1378 
1379 	_bgTable.resize(9); // Resize the background table to its required size
1380 }
1381 
1382 /**
1383  * Destroy Operation Stealth renderer
1384  */
~OSRenderer()1385 OSRenderer::~OSRenderer() {
1386 	for (uint i = 0; i < _bgTable.size(); i++) {
1387 		_bgTable[i].clear();
1388 	}
1389 }
1390 
initialize()1391 bool OSRenderer::initialize() {
1392 	_backupPal = _activePal = Palette(kHighPalFormat, kHighPalNumColors);
1393 	return true;
1394 }
1395 
1396 /**
1397  * Reset Operation Stealth renderer state
1398  */
clear()1399 void OSRenderer::clear() {
1400 	for (uint i = 0; i < _bgTable.size(); i++) {
1401 		_bgTable[i].clear();
1402 	}
1403 
1404 	_currentBg = 0;
1405 	_scrollBg = 0;
1406 	_bgShift = 0;
1407 
1408 	FWRenderer::clear();
1409 }
1410 
1411 /**
1412  * Draw 1bpp sprite using selected color on backgrounds
1413  * @param obj Object info
1414  * @param fillColor Sprite color
1415  */
incrustMask(const BGIncrust & incrust,uint8 color)1416 void OSRenderer::incrustMask(const BGIncrust &incrust, uint8 color) {
1417 	const ObjectStruct &obj = g_cine->_objectTable[incrust.objIdx];
1418 	const byte *data = g_cine->_animDataTable[obj.frame].data();
1419 	int x, y, width, height;
1420 
1421 	x = incrust.x;
1422 	y = incrust.y;
1423 	width = g_cine->_animDataTable[obj.frame]._realWidth;
1424 	height = g_cine->_animDataTable[obj.frame]._height;
1425 
1426 	if (_bgTable[incrust.bgIdx].bg) {
1427 		gfxFillSprite(data, width, height, _bgTable[incrust.bgIdx].bg, x, y, color);
1428 	}
1429 }
1430 
getFadeInSourcePalette()1431 const Cine::Palette& OSRenderer::getFadeInSourcePalette() {
1432 	assert(_currentBg <= 8);
1433 
1434 	if (_currentBg == 0) {
1435 		return _backupPal;
1436 	} else {
1437 		return _bgTable[_currentBg].pal;
1438 	}
1439 }
1440 
1441 /**
1442  * Draw color sprite
1443  * @param obj Object info
1444  */
drawSprite(const ObjectStruct & obj)1445 void OSRenderer::drawSprite(const ObjectStruct &obj) {
1446 	const byte *data = g_cine->_animDataTable[obj.frame].data();
1447 	int x, y, width, height, transColor;
1448 
1449 	x = obj.x;
1450 	y = obj.y;
1451 	transColor = obj.part;
1452 	width = g_cine->_animDataTable[obj.frame]._realWidth;
1453 	height = g_cine->_animDataTable[obj.frame]._height;
1454 
1455 	drawSpriteRaw2(data, transColor, width, height, _backBuffer, x, y);
1456 }
1457 
1458 /**
1459  * Draw color sprite
1460  * @param obj Object info
1461  */
incrustSprite(const BGIncrust & incrust)1462 void OSRenderer::incrustSprite(const BGIncrust &incrust) {
1463 	const ObjectStruct &obj = g_cine->_objectTable[incrust.objIdx];
1464 	const byte *data = g_cine->_animDataTable[incrust.frame].data();
1465 	int x, y, width, height, transColor;
1466 
1467 	x = incrust.x;
1468 	y = incrust.y;
1469 	transColor = obj.part;
1470 	width = g_cine->_animDataTable[incrust.frame]._realWidth;
1471 	height = g_cine->_animDataTable[incrust.frame]._height;
1472 
1473 	if (_bgTable[incrust.bgIdx].bg) {
1474 		// HACK: Fix transparency colors of shadings near walls
1475 		// in labyrinth scene in Operation Stealth after loading a savegame
1476 		// saved in the labyrinth.
1477 		if (hacksEnabled && incrust.objIdx == 1 && incrust.frame < 16 && transColor == 5 &&
1478 			scumm_stricmp(currentPrcName, "LABY.PRC") == 0) {
1479 			transColor = 0;
1480 		}
1481 
1482 		drawSpriteRaw2(data, transColor, width, height, _bgTable[incrust.bgIdx].bg, x, y);
1483 	}
1484 }
1485 
1486 /**
1487  * Draw text character on screen
1488  * @param character Character to draw
1489  * @param x Character coordinate
1490  * @param y Character coordinate
1491  * @param draw Draw the character?
1492  */
drawChar(char character,int x,int y,bool draw)1493 int OSRenderer::drawChar(char character, int x, int y, bool draw) {
1494 	int width;
1495 
1496 	if (character == ' ') {
1497 		x += 5;
1498 	} else if ((width = g_cine->_textHandler.fontParamTable[(unsigned char)character].characterWidth)) {
1499 		int idx = g_cine->_textHandler.fontParamTable[(unsigned char)character].characterIdx;
1500 		if (draw) {
1501 			drawSpriteRaw2(g_cine->_textHandler.textTable[idx][FONT_DATA], 0, FONT_WIDTH, FONT_HEIGHT, _backBuffer, x, y);
1502 		}
1503 		x += width + 1;
1504 	}
1505 
1506 	return x;
1507 }
1508 
1509 /**
1510  * Draw background to backbuffer
1511  */
drawBackground()1512 void OSRenderer::drawBackground() {
1513 	byte *main;
1514 
1515 	main = _bgTable[_currentBg].bg;
1516 	assert(main);
1517 
1518 	if (!_bgShift) {
1519 		memcpy(_backBuffer, main, _screenSize);
1520 	} else {
1521 		unsigned int rowShift = _bgShift % 200;
1522 		byte *scroll = _bgTable[_scrollBg].bg;
1523 		assert(scroll);
1524 
1525 		if (!rowShift) {
1526 			memcpy(_backBuffer, scroll, _screenSize);
1527 		} else {
1528 			int mainShift = rowShift * _screenWidth;
1529 			int mainSize = _screenSize - mainShift;
1530 
1531 			if (mainSize > 0) { // Just a precaution
1532 				memcpy(_backBuffer, main + mainShift, mainSize);
1533 			}
1534 			if (mainShift > 0) { // Just a precaution
1535 				memcpy(_backBuffer + mainSize, scroll, mainShift);
1536 			}
1537 		}
1538 	}
1539 }
1540 
1541 /**
1542  * Draw one overlay
1543  * @param it Overlay info
1544  * @todo Add handling of type 22 overlays
1545  */
renderOverlay(const Common::List<overlay>::iterator & it)1546 void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
1547 	int len, idx, width, height;
1548 	ObjectStruct *obj;
1549 	AnimData *sprite;
1550 	byte color, transparentColor;
1551 	bool useTopLeftForTransCol = false;
1552 
1553 	switch (it->type) {
1554 	// color sprite
1555 	case 0:
1556 		if (g_cine->_objectTable[it->objIdx].frame < 0) {
1557 			break;
1558 		}
1559 
1560 		sprite = &g_cine->_animDataTable[g_cine->_objectTable[it->objIdx].frame];
1561 		obj = &g_cine->_objectTable[it->objIdx];
1562 		transparentColor = obj->part & 0x0F;
1563 
1564 		// HACK: Correct transparency color from 6 to 0 for the first frame of sea animation
1565 		// in 16 color DOS version of Operation Stealth in the flower shop scene
1566 		// (The scene in which the player arrives immediately after leaving the airport).
1567 		if (hacksEnabled && it->objIdx == 141 && obj->frame == 100 && obj->part == 6 && sprite->_bpp == 4 &&
1568 			scumm_stricmp(currentPrcName, "AIRPORT.PRC") == 0 &&
1569 			scumm_stricmp(renderer->getBgName(), "21.PI1") == 0) {
1570 			useTopLeftForTransCol = true;
1571 		}
1572 
1573 		// HACK: Correct transparency color from 8 to 51 for the player's walking animation
1574 		// in 256 color DOS version of Operation Stealth in the scene right after
1575 		// leaving Dr. Why's control room.
1576 		if (hacksEnabled && it->objIdx == 1 && obj->part == 8 && sprite->_bpp == 5 &&
1577 			scumm_stricmp(currentPrcName, "ILE.PRC") == 0 &&
1578 			scumm_stricmp(renderer->getBgName(), "56VIDE.PI1") == 0) {
1579 			useTopLeftForTransCol = true;
1580 		}
1581 
1582 		// HACK: Correct transparency color from 1 to 3 for the player emerging from a manhole
1583 		// in 256 color DOS version of Operation Stealth when entering the Dr. Why's island.
1584 		if (hacksEnabled && it->objIdx == 43 && obj->frame >= 100 && obj->frame <= 102 &&
1585 			obj->part == 1 && sprite->_bpp == 5 &&
1586 			scumm_stricmp(currentPrcName, "SOUSMAR2.PRC") == 0 &&
1587 			scumm_stricmp(renderer->getBgName(), "56.PI1") == 0) {
1588 			useTopLeftForTransCol = true;
1589 		}
1590 
1591 		if (useTopLeftForTransCol) {
1592 			// Use top left corner value for transparency
1593 			transparentColor = sprite->getColor(0, 0);
1594 		}
1595 
1596 		drawSprite(&(*it), sprite->data(), sprite->_realWidth, sprite->_height, _backBuffer, g_cine->_objectTable[it->objIdx].x, g_cine->_objectTable[it->objIdx].y, transparentColor, sprite->_bpp);
1597 		break;
1598 
1599 	// game message
1600 	case 2:
1601 		if (it->objIdx >= g_cine->_messageTable.size()) {
1602 			return;
1603 		}
1604 
1605 		_messageLen += g_cine->_messageTable[it->objIdx].size();
1606 		drawMessage(g_cine->_messageTable[it->objIdx].c_str(), it->x, it->y, it->width, it->color);
1607 		if (it->color >= 0) { // This test isn't in Future Wars's implementation
1608 			waitForPlayerClick = 1;
1609 		}
1610 		break;
1611 
1612 	// action failure message
1613 	case 3:
1614 		idx = it->objIdx * 4 + g_cine->_rnd.getRandomNumber(3);
1615 		len = strlen(failureMessages[idx]);
1616 		_messageLen += len;
1617 		width = 6 * len + 20;
1618 		width = width > 300 ? 300 : width;
1619 
1620 		// The used color here differs from Future Wars
1621 		drawMessage(failureMessages[idx], (320 - width) / 2, 80, width, _messageBg);
1622 		waitForPlayerClick = 1;
1623 		break;
1624 
1625 	// bitmap
1626 	case 4:
1627 		if (g_cine->_objectTable[it->objIdx].frame >= 0) {
1628 			FWRenderer::renderOverlay(it);
1629 		}
1630 		break;
1631 
1632 	// masked background
1633 	case 20:
1634 		assert(it->objIdx < NUM_MAX_OBJECT);
1635 		lastType20OverlayBgIdx = it->x; // A global variable updated here!
1636 		obj = &g_cine->_objectTable[it->objIdx];
1637 		sprite = &g_cine->_animDataTable[obj->frame];
1638 
1639 		if (obj->frame < 0 || it->x < 0 || it->x > 8 || !_bgTable[it->x].bg || sprite->_bpp != 1) {
1640 			break;
1641 		}
1642 
1643 		maskBgOverlay(it->x, _bgTable[it->x].bg, sprite->data(), sprite->_realWidth, sprite->_height, _backBuffer, obj->x, obj->y);
1644 		break;
1645 
1646 	// line drawing
1647 	case 22:
1648 		assert(it->objIdx < NUM_MAX_OBJECT);
1649 		obj = &g_cine->_objectTable[it->objIdx];
1650 		color = obj->part & 0x0F;
1651 		width = obj->frame;
1652 		height = obj->costume;
1653 		// Using Bresenham's algorithm, looks good enough for visual purposes in Operation Stealth
1654 		Graphics::drawLine(obj->x, obj->y, width, height, color, plotPoint, _backBuffer);
1655 		break;
1656 
1657 	// something else
1658 	default:
1659 		FWRenderer::renderOverlay(it);
1660 		break;
1661 	}
1662 }
1663 
1664 /**
1665  * Copy part of backup palette to active palette and transform
1666  * @param first First color to transform
1667  * @param last Last color to transform
1668  * @param r Red channel transformation
1669  * @param g Green channel transformation
1670  * @param b Blue channel transformation
1671  */
transformPalette(int first,int last,int r,int g,int b)1672 void OSRenderer::transformPalette(int first, int last, int r, int g, int b) {
1673 	// Background indices 0 and 8 use backup palette
1674 	const Cine::Palette& srcPal =
1675 		(_currentBg > 0 && _currentBg < 8) ? _bgTable[_currentBg].pal : _backupPal;
1676 
1677 	// Initialize active palette to current background's palette format and size if they differ
1678 	if (_activePal.colorFormat() != srcPal.colorFormat() || _activePal.colorCount() != srcPal.colorCount()) {
1679 		_activePal = Cine::Palette(srcPal.colorFormat(), srcPal.colorCount());
1680 	}
1681 
1682 	// If asked to change whole 16 color palette then
1683 	// assume it means the whole palette regardless of size.
1684 	// In Operation Stealth DOS 16 color and 256 color disassembly mapping was from 0-15 to 0-255.
1685 	if (first == 0 && last == 15) {
1686 		last = srcPal.colorCount() - 1;
1687 	}
1688 
1689 	srcPal.saturatedAddColor(_activePal, first, last, r, g, b, kLowPalFormat);
1690 	_changePal = 1; // From disassembly
1691 	gfxFadeOutCompleted = 0;
1692 }
1693 
addBackground(const char * bgName,uint16 bgIdx)1694 int16 OSRenderer::addBackground(const char *bgName, uint16 bgIdx) {
1695 	byte *ptr, *dataPtr;
1696 
1697 	int16 fileIdx = findFileInBundle(bgName);
1698 	if (fileIdx < 0) {
1699 		warning("OSRenderer::addBackground(\"%s\", %d): Could not find background in file bundle.", bgName, bgIdx);
1700 		return -1;
1701 	}
1702 	checkDataDisk(-1);
1703 	ptr = dataPtr = readBundleFile(fileIdx);
1704 
1705 	uint16 bpp = READ_BE_UINT16(ptr); ptr += 2;
1706 
1707 	if (!_bgTable[bgIdx].bg) {
1708 		_bgTable[bgIdx].bg = new byte[_screenSize];
1709 	}
1710 
1711 	Common::strlcpy(_bgTable[bgIdx].name, bgName, sizeof(_bgTable[bgIdx].name));
1712 
1713 	if (bpp == 8) {
1714 		_bgTable[bgIdx].pal.load(ptr, kHighPalNumBytes, kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
1715 		memcpy(_bgTable[bgIdx].bg, ptr + kHighPalNumBytes, _screenSize);
1716 	} else {
1717 		_bgTable[bgIdx].pal.load(ptr, kLowPalNumBytes, kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
1718 		gfxConvertSpriteToRaw(_bgTable[bgIdx].bg, ptr + kLowPalNumBytes, 160, 200);
1719 	}
1720 	free(dataPtr);
1721 	return 0;
1722 }
1723 
1724 /**
1725  * Load 16 color background into renderer
1726  * @param bg Raw background data
1727  * @param name Background filename
1728  * @param pos Background index
1729  * @todo Combine with FWRenderer's version of loadBg16
1730  */
loadBg16(const byte * bg,const char * name,unsigned int idx)1731 void OSRenderer::loadBg16(const byte *bg, const char *name, unsigned int idx) {
1732 	assert(idx < 9);
1733 
1734 	if (!_bgTable[idx].bg) {
1735 		_bgTable[idx].bg = new byte[_screenSize];
1736 	}
1737 
1738 	assert(_bgTable[idx].bg);
1739 
1740 	Common::strlcpy(_bgTable[idx].name, name, sizeof(_bgTable[idx].name));
1741 
1742 	// Load the 16 color palette
1743 	_backupPal.load(bg, kLowPalNumBytes, kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
1744 
1745 	// Jump over the palette data to the background data
1746 	bg += kLowPalNumBytes;
1747 
1748 	gfxConvertSpriteToRaw(_bgTable[idx].bg, bg, 160, 200);
1749 }
1750 
1751 /**
1752  * Load 16 color CT data as background into renderer
1753  * @param ct Raw CT data
1754  * @param name Background filename
1755  */
loadCt16(const byte * ct,const char * name)1756 void OSRenderer::loadCt16(const byte *ct, const char *name) {
1757 	assert(collisionPage);
1758 
1759 	// Make the 9th background point directly to the collision page
1760 	// and load the picture into it.
1761 	setBackground8ToCollisionPage();
1762 	_bgTable[kCollisionPageBgIdxAlias].pal.load(ct, kLowPalNumBytes, kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
1763 	gfxConvertSpriteToRaw(_bgTable[kCollisionPageBgIdxAlias].bg, ct + kLowPalNumBytes, 160, 200);
1764 }
1765 
setBackground8ToCollisionPage()1766 void OSRenderer::setBackground8ToCollisionPage() {
1767 	byte* oldBg = _bgTable[kCollisionPageBgIdxAlias].bg;
1768 	if (oldBg && oldBg != collisionPage) {
1769 		delete[] _bgTable[kCollisionPageBgIdxAlias].bg;
1770 	}
1771 	_bgTable[kCollisionPageBgIdxAlias].bg = collisionPage;
1772 }
1773 
1774 /**
1775  * Load 256 color background into renderer
1776  * @param bg Raw background data
1777  * @param name Background filename
1778  * @param pos Background index
1779  */
loadBg256(const byte * bg,const char * name,unsigned int idx)1780 void OSRenderer::loadBg256(const byte *bg, const char *name, unsigned int idx) {
1781 	assert(idx < 9);
1782 
1783 	if (!_bgTable[idx].bg) {
1784 		_bgTable[idx].bg = new byte[_screenSize];
1785 	}
1786 
1787 	assert(_bgTable[idx].bg);
1788 
1789 	Common::strlcpy(_bgTable[idx].name, name, sizeof(_bgTable[idx].name));
1790 	_backupPal.load(bg, kHighPalNumBytes, kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
1791 
1792 	memcpy(_bgTable[idx].bg, bg + kHighPalNumBytes, _screenSize);
1793 }
1794 
1795 /**
1796  * Load 256 color CT data as background into renderer
1797  * @param ct Raw CT data
1798  * @param name Background filename
1799  */
loadCt256(const byte * ct,const char * name)1800 void OSRenderer::loadCt256(const byte *ct, const char *name) {
1801 	assert(collisionPage);
1802 
1803 	// Make the 9th background point directly to the collision page
1804 	// and load the picture into it.
1805 	setBackground8ToCollisionPage();
1806 	_bgTable[kCollisionPageBgIdxAlias].pal.load(ct, kHighPalNumBytes, kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
1807 	memcpy(_bgTable[kCollisionPageBgIdxAlias].bg, ct + kHighPalNumBytes, _screenSize);
1808 }
1809 
1810 /**
1811  * Select active background and load its palette
1812  * @param idx Background index
1813  */
selectBg(unsigned int idx)1814 void OSRenderer::selectBg(unsigned int idx) {
1815 	assert(idx < 9);
1816 
1817 	if (idx <= 8 && _bgTable[idx].bg) {
1818 		_currentBg = idx;
1819 		if (!forbidBgPalReload) {
1820 			reloadBgPalOnNextFlip = 1;
1821 		}
1822 	} else
1823 		warning("OSRenderer::selectBg(%d) - attempt to select null background", idx);
1824 }
1825 
1826 /**
1827  * Select scroll background
1828  * @param idx Scroll background index
1829  */
selectScrollBg(unsigned int idx)1830 void OSRenderer::selectScrollBg(unsigned int idx) {
1831 	assert(idx < 9);
1832 
1833 	if (idx <= 8 && _bgTable[idx].bg) {
1834 		_scrollBg = idx;
1835 	}
1836 }
1837 
1838 /**
1839  * Set background scroll
1840  * @param shift Background scroll in pixels
1841  */
setScroll(unsigned int shift)1842 void OSRenderer::setScroll(unsigned int shift) {
1843 	_bgShift = shift % 400;
1844 }
1845 
1846 /**
1847  * Get background scroll
1848  * @return Background scroll in pixels
1849  */
getScroll() const1850 uint OSRenderer::getScroll() const {
1851 	return _bgShift;
1852 }
1853 
1854 /**
1855  * Unload background from renderer
1856  * @param idx Background to unload
1857  */
removeBg(unsigned int idx)1858 void OSRenderer::removeBg(unsigned int idx) {
1859 	assert(idx > 0 && idx < 9);
1860 
1861 	if (_currentBg == idx) {
1862 		_currentBg = 0;
1863 	}
1864 
1865 	if (_scrollBg == idx) {
1866 		_scrollBg = 0;
1867 	}
1868 
1869 	_bgTable[idx].clear();
1870 }
1871 
saveBgNames(Common::OutSaveFile & fHandle)1872 void OSRenderer::saveBgNames(Common::OutSaveFile &fHandle) {
1873 	for (int i = 0; i < 8; i++) {
1874 		fHandle.write(_bgTable[i].name, 13);
1875 	}
1876 }
1877 
getBgName(uint idx) const1878 const char *OSRenderer::getBgName(uint idx) const {
1879 	assert(idx < 9);
1880 	return _bgTable[idx].name;
1881 }
1882 
setMouseCursor(int cursor)1883 void setMouseCursor(int cursor) {
1884 	static int currentMouseCursor = -1;
1885 	assert(cursor >= 0 && cursor < 3);
1886 	if (currentMouseCursor != cursor) {
1887 		byte mouseCursor[16 * 16];
1888 		const MouseCursor *mc = &mouseCursors[cursor];
1889 		const byte *src = mc->bitmap;
1890 		for (int i = 0; i < 32; ++i) {
1891 			int offs = i * 8;
1892 			for (byte mask = 0x80; mask != 0; mask >>= 1) {
1893 				if (src[0] & mask) {
1894 					mouseCursor[offs] = 1;
1895 				} else if (src[32] & mask) {
1896 					mouseCursor[offs] = 0;
1897 				} else {
1898 					mouseCursor[offs] = 0xFF;
1899 				}
1900 				++offs;
1901 			}
1902 			++src;
1903 		}
1904 		CursorMan.replaceCursor(mouseCursor, 16, 16, mc->hotspotX, mc->hotspotY, 0xFF);
1905 		CursorMan.replaceCursorPalette(cursorPalette, 0, 2);
1906 		currentMouseCursor = cursor;
1907 	}
1908 }
1909 
gfxFillSprite(const byte * spritePtr,uint16 width,uint16 height,byte * page,int16 x,int16 y,uint8 fillColor)1910 void gfxFillSprite(const byte *spritePtr, uint16 width, uint16 height, byte *page, int16 x, int16 y, uint8 fillColor) {
1911 	int16 i, j;
1912 
1913 	for (i = 0; i < height; i++) {
1914 		byte *destPtr = page + x + y * 320;
1915 		destPtr += i * 320;
1916 
1917 		for (j = 0; j < width; j++) {
1918 			if (x + j >= 0 && x + j < 320 && i + y >= 0 && i + y < 200 && !*spritePtr) {
1919 				*destPtr = fillColor;
1920 			}
1921 
1922 			destPtr++;
1923 			spritePtr++;
1924 		}
1925 	}
1926 }
1927 
gfxDrawMaskedSprite(const byte * spritePtr,const byte * maskPtr,uint16 width,uint16 height,byte * page,int16 x,int16 y)1928 void gfxDrawMaskedSprite(const byte *spritePtr, const byte *maskPtr, uint16 width, uint16 height, byte *page, int16 x, int16 y) {
1929 	int16 i, j;
1930 
1931 	for (i = 0; i < height; i++) {
1932 		byte *destPtr = page + x + y * 320;
1933 		destPtr += i * 320;
1934 
1935 		for (j = 0; j < width; j++) {
1936 			if (x + j >= 0 && x + j < 320 && i + y >= 0 && i + y < 200 && *maskPtr == 0) {
1937 				*destPtr = *spritePtr;
1938 			}
1939 			++destPtr;
1940 			++spritePtr;
1941 			++maskPtr;
1942 		}
1943 	}
1944 }
1945 
gfxUpdateSpriteMask(byte * destMask,int16 x,int16 y,int16 width,int16 height,const byte * srcMask,int16 xm,int16 ym,int16 maskWidth,int16 maskHeight)1946 void gfxUpdateSpriteMask(byte *destMask, int16 x, int16 y, int16 width, int16 height, const byte *srcMask, int16 xm, int16 ym, int16 maskWidth, int16 maskHeight) {
1947 	int16 i, j, d, spritePitch, maskPitch;
1948 
1949 	spritePitch = width;
1950 	maskPitch = maskWidth;
1951 
1952 	// crop update area to overlapping parts of masks
1953 	if (y > ym) {
1954 		d = y - ym;
1955 		srcMask += d * maskPitch;
1956 		maskHeight -= d;
1957 	} else if (y < ym) {
1958 		d = ym - y;
1959 		destMask += d * spritePitch;
1960 		height -= d;
1961 	}
1962 
1963 	if (x > xm) {
1964 		d = x - xm;
1965 		srcMask += d;
1966 		maskWidth -= d;
1967 	} else if (x < xm) {
1968 		d = xm - x;
1969 		destMask += d;
1970 		width -= d;
1971 	}
1972 
1973 	// update mask
1974 	for (j = 0; j < MIN(maskHeight, height); ++j) {
1975 		for (i = 0; i < MIN(maskWidth, width); ++i) {
1976 			destMask[i] |= srcMask[i] ^ 1;
1977 		}
1978 		destMask += spritePitch;
1979 		srcMask += maskPitch;
1980 	}
1981 }
1982 
gfxUpdateIncrustMask(byte * destMask,int16 x,int16 y,int16 width,int16 height,const byte * srcMask,int16 xm,int16 ym,int16 maskWidth,int16 maskHeight)1983 void gfxUpdateIncrustMask(byte *destMask, int16 x, int16 y, int16 width, int16 height, const byte *srcMask, int16 xm, int16 ym, int16 maskWidth, int16 maskHeight) {
1984 	int16 i, j, d, spritePitch, maskPitch;
1985 
1986 	spritePitch = width;
1987 	maskPitch = maskWidth;
1988 
1989 	// crop update area to overlapping parts of masks
1990 	if (y > ym) {
1991 		d = y - ym;
1992 		srcMask += d * maskPitch;
1993 		maskHeight -= d;
1994 	} else if (y < ym) {
1995 		d = ym - y > height ? height : ym - y;
1996 		memset(destMask, 1, d * spritePitch);
1997 		destMask += d * spritePitch;
1998 		height -= d;
1999 	}
2000 
2001 	if (x > xm) {
2002 		d = x - xm;
2003 		xm = x;
2004 		srcMask += d;
2005 		maskWidth -= d;
2006 	}
2007 
2008 	d = xm - x;
2009 	maskWidth += d;
2010 
2011 	// update mask
2012 	for (j = 0; j < MIN(maskHeight, height); ++j) {
2013 		for (i = 0; i < width; ++i) {
2014 			destMask[i] |= i < d || i >= maskWidth ? 1 : srcMask[i - d];
2015 		}
2016 		destMask += spritePitch;
2017 		srcMask += maskPitch;
2018 	}
2019 
2020 	if (j < height) {
2021 		memset(destMask, 1, (height - j) * spritePitch);
2022 	}
2023 }
2024 
gfxDrawLine(int16 x1,int16 y1,int16 x2,int16 y2,byte color,byte * page)2025 void gfxDrawLine(int16 x1, int16 y1, int16 x2, int16 y2, byte color, byte *page) {
2026 	if (x1 == x2) {
2027 		if (y1 > y2) {
2028 			SWAP(y1, y2);
2029 		}
2030 		while (y1 <= y2) {
2031 			*(page + (y1 * 320 + x1)) = color;
2032 			y1++;
2033 		}
2034 	} else {
2035 		if (x1 > x2) {
2036 			SWAP(x1, x2);
2037 		}
2038 		while (x1 <= x2) {
2039 			*(page + (y1 * 320 + x1)) = color;
2040 			x1++;
2041 		}
2042 	}
2043 
2044 }
2045 
gfxDrawPlainBoxRaw(int16 x1,int16 y1,int16 x2,int16 y2,byte color,byte * page)2046 void gfxDrawPlainBoxRaw(int16 x1, int16 y1, int16 x2, int16 y2, byte color, byte *page) {
2047 	int16 t;
2048 
2049 	if (x1 > x2) {
2050 		SWAP(x1, x2);
2051 	}
2052 
2053 	if (y1 > y2) {
2054 		SWAP(y1, y2);
2055 	}
2056 
2057 	t = x1;
2058 	while (y1 <= y2) {
2059 		x1 = t;
2060 		while (x1 <= x2) {
2061 			*(page + y1 * 320 + x1) = color;
2062 			x1++;
2063 		}
2064 		y1++;
2065 	}
2066 }
2067 
gfxGetBit(int16 x,int16 y,const byte * ptr,int16 width)2068 int16 gfxGetBit(int16 x, int16 y, const byte *ptr, int16 width) {
2069 	const byte *ptrToData = (ptr) + y * width + x;
2070 
2071 	if (x > width) {
2072 		return 0;
2073 	}
2074 
2075 	if (*ptrToData) {
2076 		return 0;
2077 	}
2078 
2079 	return 1;
2080 }
2081 
gfxResetRawPage(byte * pageRaw)2082 void gfxResetRawPage(byte *pageRaw) {
2083 	memset(pageRaw, 0, 320 * 200);
2084 }
2085 
gfxConvertSpriteToRaw(byte * dst,const byte * src,uint16 w,uint16 h)2086 void gfxConvertSpriteToRaw(byte *dst, const byte *src, uint16 w, uint16 h) {
2087 	// Output is 4 bits per pixel.
2088 	// Pixels are in 16 pixel chunks (8 bytes of source per 16 pixels of output).
2089 	// The source data is interleaved so that
2090 	// 1st big-endian 16-bit value contains all bit position 0 values for 16 pixels,
2091 	// 2nd big-endian 16-bit value contains all bit position 1 values for 16 pixels,
2092 	// 3rd big-endian 16-bit value contains all bit position 2 values for 16 pixels,
2093 	// 4th big-endian 16-bit value contains all bit position 3 values for 16 pixels.
2094 	// 1st pixel's bits are in the 16th bits,
2095 	// 2nd pixel's bits are in the 15th bits,
2096 	// 3rd pixel's bits are in the 14th bits etc.
2097 	for (int y = 0; y < h; ++y) {
2098 		for (int x = 0; x < w / 8; ++x) {
2099 			for (int bit = 0; bit < 16; ++bit) {
2100 				uint8 color = 0;
2101 				for (int p = 0; p < 4; ++p) {
2102 					if (READ_BE_UINT16(src + p * 2) & (1 << (15 - bit))) {
2103 						color |= 1 << p;
2104 					}
2105 				}
2106 				*dst++ = color;
2107 			}
2108 			src += 8;
2109 		}
2110 	}
2111 }
2112 
drawSpriteRaw(const byte * spritePtr,const byte * maskPtr,int16 width,int16 height,byte * page,int16 x,int16 y)2113 void drawSpriteRaw(const byte *spritePtr, const byte *maskPtr, int16 width, int16 height, byte *page, int16 x, int16 y) {
2114 	int16 i, j;
2115 
2116 	// FIXME: Is it a bug if maskPtr == NULL?
2117 	if (!maskPtr)
2118 		warning("drawSpriteRaw: maskPtr == NULL");
2119 
2120 	for (i = 0; i < height; i++) {
2121 		byte *destPtr = page + x + y * 320;
2122 		destPtr += i * 320;
2123 
2124 		for (j = 0; j < width; j++) {
2125 			if ((!maskPtr || !(*maskPtr)) && x + j >= 0 && x + j < 320 && i + y >= 0 && i + y < 200) {
2126 				*(destPtr++) = *(spritePtr++);
2127 			} else {
2128 				destPtr++;
2129 				spritePtr++;
2130 			}
2131 
2132 			if (maskPtr)
2133 				maskPtr++;
2134 		}
2135 	}
2136 }
2137 
drawSprite(overlay * overlayPtr,const byte * spritePtr,int16 width,int16 height,byte * page,int16 x,int16 y,byte transparentColor,byte bpp)2138 void OSRenderer::drawSprite(overlay *overlayPtr, const byte *spritePtr, int16 width, int16 height, byte *page, int16 x, int16 y, byte transparentColor, byte bpp) {
2139 	byte *pMask = NULL;
2140 
2141 	// draw the mask based on next objects in the list
2142 	Common::List<overlay>::iterator it;
2143 	for (it = g_cine->_overlayList.begin(); it != g_cine->_overlayList.end(); ++it) {
2144 		if (&(*it) == overlayPtr) {
2145 			break;
2146 		}
2147 	}
2148 
2149 	while (it != g_cine->_overlayList.end()) {
2150 		overlay *pCurrentOverlay = &(*it);
2151 		if ((pCurrentOverlay->type == 5) || ((pCurrentOverlay->type == 21) && (pCurrentOverlay->x == overlayPtr->objIdx))) {
2152 			AnimData *sprite = &g_cine->_animDataTable[g_cine->_objectTable[it->objIdx].frame];
2153 
2154 			if (pMask == NULL) {
2155 				pMask = new byte[width * height];
2156 
2157 				for (int i = 0; i < height; i++) {
2158 					for (int j = 0; j < width; j++) {
2159 						byte spriteColor = spritePtr[width * i + j];
2160 						pMask[width * i + j] = spriteColor;
2161 					}
2162 				}
2163 			}
2164 
2165 			for (int i = 0; i < sprite->_realWidth; i++) {
2166 				for (int j = 0; j < sprite->_height; j++) {
2167 					int inMaskX = (g_cine->_objectTable[it->objIdx].x + i) - x;
2168 					int inMaskY = (g_cine->_objectTable[it->objIdx].y + j) - y;
2169 
2170 					if (inMaskX >= 0 && inMaskX < width) {
2171 						if (inMaskY >= 0 && inMaskY < height) {
2172 							if (sprite->_bpp == 1) {
2173 								if (!sprite->getColor(i, j)) {
2174 									pMask[inMaskY * width + inMaskX] = page[x + y * 320 + inMaskX + inMaskY * 320];
2175 								}
2176 							}
2177 						}
2178 					}
2179 				}
2180 			}
2181 		}
2182 		++it;
2183 	}
2184 
2185 	// now, draw with the mask we created
2186 	if (pMask) {
2187 		spritePtr = pMask;
2188 	}
2189 
2190 	// ignore transparent color in 1bpp
2191 	if (bpp == 1) {
2192 		transparentColor = 1;
2193 	}
2194 
2195 	{
2196 		for (int i = 0; i < height; i++) {
2197 			byte *destPtr = page + x + y * 320;
2198 			destPtr += i * 320;
2199 
2200 			for (int j = 0; j < width; j++) {
2201 				byte color = *(spritePtr++);
2202 				if ((transparentColor != color) && x + j >= 0 && x + j < 320 && i + y >= 0 && i + y < 200) {
2203 					*(destPtr++) = color;
2204 				} else {
2205 					destPtr++;
2206 				}
2207 			}
2208 		}
2209 	}
2210 
2211 	delete[] pMask;
2212 }
2213 
drawSpriteRaw2(const byte * spritePtr,byte transColor,int16 width,int16 height,byte * page,int16 x,int16 y)2214 void drawSpriteRaw2(const byte *spritePtr, byte transColor, int16 width, int16 height, byte *page, int16 x, int16 y) {
2215 	int16 i, j;
2216 
2217 	for (i = 0; i < height; i++) {
2218 		byte *destPtr = page + x + y * 320;
2219 		destPtr += i * 320;
2220 
2221 		for (j = 0; j < width; j++) {
2222 			if ((*spritePtr != transColor) && (x + j >= 0 && x + j < 320 && i + y >= 0 && i + y < 200)) {
2223 				*destPtr = *spritePtr;
2224 			}
2225 			destPtr++;
2226 			spritePtr++;
2227 		}
2228 	}
2229 }
2230 
maskBgOverlay(int targetBgIdx,const byte * bgPtr,const byte * maskPtr,int16 width,int16 height,byte * page,int16 x,int16 y)2231 void maskBgOverlay(int targetBgIdx, const byte *bgPtr, const byte *maskPtr, int16 width, int16 height,
2232 				   byte *page, int16 x, int16 y) {
2233 	int16 i, j, tmpWidth, tmpHeight;
2234 	Common::List<BGIncrust>::iterator it;
2235 	const byte *backup = maskPtr;
2236 
2237 	// background pass
2238 	for (i = 0; i < height; i++) {
2239 		byte *destPtr = page + x + y * 320;
2240 		const byte *srcPtr = bgPtr + x + y * 320;
2241 		destPtr += i * 320;
2242 		srcPtr += i * 320;
2243 
2244 		for (j = 0; j < width; j++) {
2245 			if ((!maskPtr || !(*maskPtr)) && (x + j >= 0
2246 					&& x + j < 320 && i + y >= 0 && i + y < 200)) {
2247 				*destPtr = *srcPtr;
2248 			}
2249 
2250 			destPtr++;
2251 			srcPtr++;
2252 
2253 			if (maskPtr)
2254 				maskPtr++;
2255 		}
2256 	}
2257 
2258 	maskPtr = backup;
2259 
2260 	// incrust pass
2261 	for (it = g_cine->_bgIncrustList.begin(); it != g_cine->_bgIncrustList.end(); ++it) {
2262 		// HACK: Remove drawing of red corners around doors in rat maze in Operation Stealth
2263 		// by skipping drawing of possible collision table data to non-collision table page.
2264 		if (hacksEnabled && it->bgIdx == kCollisionPageBgIdxAlias && targetBgIdx != kCollisionPageBgIdxAlias) {
2265 			continue;
2266 		}
2267 
2268 		tmpWidth = g_cine->_animDataTable[it->frame]._realWidth;
2269 		tmpHeight = g_cine->_animDataTable[it->frame]._height;
2270 		byte *mask = (byte *)malloc(tmpWidth * tmpHeight);
2271 
2272 		if (it->param == 0) {
2273 			generateMask(g_cine->_animDataTable[it->frame].data(), mask, tmpWidth * tmpHeight, it->part);
2274 			gfxUpdateIncrustMask(mask, it->x, it->y, tmpWidth, tmpHeight, maskPtr, x, y, width, height);
2275 			gfxDrawMaskedSprite(g_cine->_animDataTable[it->frame].data(), mask, tmpWidth, tmpHeight, page, it->x, it->y);
2276 		} else {
2277 			memcpy(mask, g_cine->_animDataTable[it->frame].data(), tmpWidth * tmpHeight);
2278 			gfxUpdateIncrustMask(mask, it->x, it->y, tmpWidth, tmpHeight, maskPtr, x, y, width, height);
2279 			gfxFillSprite(mask, tmpWidth, tmpHeight, page, it->x, it->y);
2280 		}
2281 
2282 		free(mask);
2283 	}
2284 }
2285 
2286 } // End of namespace Cine
2287