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