diff options
Diffstat (limited to 'engines/agi/graphics.cpp')
-rw-r--r-- | engines/agi/graphics.cpp | 2313 |
1 files changed, 1281 insertions, 1032 deletions
diff --git a/engines/agi/graphics.cpp b/engines/agi/graphics.cpp index c5cede71ef..3b1b99f458 100644 --- a/engines/agi/graphics.cpp +++ b/engines/agi/graphics.cpp @@ -23,773 +23,1314 @@ #include "common/config-manager.h" #include "common/file.h" #include "common/textconsole.h" +#include "engines/util.h" #include "graphics/cursorman.h" #include "graphics/palette.h" #include "agi/agi.h" #include "agi/graphics.h" +#include "agi/mouse_cursor.h" +#include "agi/palette.h" +#include "agi/picture.h" +#include "agi/text.h" namespace Agi { -#define DEV_X0(x) ((x) << 1) -#define DEV_X1(x) (((x) << 1) + 1) -#define DEV_Y(x) (x) +#include "agi/font.h" -#ifndef MAX_INT -# define MAX_INT (int)((unsigned)~0 >> 1) -#endif +GfxMgr::GfxMgr(AgiBase *vm, GfxFont *font) : _vm(vm), _font(font) { + _agipalFileNum = 0; -#include "agi/font.h" + memset(&_paletteGfxMode, 0, sizeof(_paletteGfxMode)); + memset(&_paletteTextMode, 0, sizeof(_paletteTextMode)); -/** - * 16 color RGB palette. - * This array contains the 6-bit RGB values of the EGA palette exported - * to the console drivers. - */ -static const uint8 egaPalette[16 * 3] = { - 0x00, 0x00, 0x00, - 0x00, 0x00, 0x2a, - 0x00, 0x2a, 0x00, - 0x00, 0x2a, 0x2a, - 0x2a, 0x00, 0x00, - 0x2a, 0x00, 0x2a, - 0x2a, 0x15, 0x00, - 0x2a, 0x2a, 0x2a, - 0x15, 0x15, 0x15, - 0x15, 0x15, 0x3f, - 0x15, 0x3f, 0x15, - 0x15, 0x3f, 0x3f, - 0x3f, 0x15, 0x15, - 0x3f, 0x15, 0x3f, - 0x3f, 0x3f, 0x15, - 0x3f, 0x3f, 0x3f -}; + memset(&_mouseCursor, 0, sizeof(_mouseCursor)); + memset(&_mouseCursorBusy, 0, sizeof(_mouseCursorBusy)); -/** - * Atari ST AGI palette. - * Used by all of the tested Atari ST AGI games - * from Donald Duck's Playground (1986) to Manhunter II (1989). - * 16 RGB colors. 3 bits per color component. - */ -#if 0 -static const uint8 atariStAgiPalette[16 * 3] = { - 0x0, 0x0, 0x0, - 0x0, 0x0, 0x7, - 0x0, 0x4, 0x0, - 0x0, 0x5, 0x4, - 0x5, 0x0, 0x0, - 0x5, 0x3, 0x6, - 0x4, 0x3, 0x0, - 0x5, 0x5, 0x5, - 0x3, 0x3, 0x2, - 0x0, 0x5, 0x7, - 0x0, 0x6, 0x0, - 0x0, 0x7, 0x6, - 0x7, 0x2, 0x3, - 0x7, 0x4, 0x7, - 0x7, 0x7, 0x4, - 0x7, 0x7, 0x7 -}; -#endif + initPriorityTable(); -/** - * Second generation Apple IIGS AGI palette. - * A 16-color, 12-bit RGB palette. - * - * Used by at least the following Apple IIGS AGI versions: - * 1.003 (Leisure Suit Larry I v1.0E, intro says 1987) - * 1.005 (AGI Demo 2 1987-06-30?) - * 1.006 (King's Quest I v1.0S 1988-02-23) - * 1.007 (Police Quest I v2.0B 1988-04-21 8:00am) - * 1.013 (King's Quest II v2.0A 1988-06-16 (CE)) - * 1.013 (Mixed-Up Mother Goose v2.0A 1988-05-31 10:00am) - * 1.014 (King's Quest III v2.0A 1988-08-28 (CE)) - * 1.014 (Space Quest II v2.0A, LOGIC.141 says 1988) - * 2.004 (Manhunter I v2.0E 1988-10-05 (CE)) - * 2.006 (King's Quest IV v1.0K 1988-11-22 (CE)) - * 3.001 (Black Cauldron v1.0O 1989-02-24 (CE)) - * 3.003 (Gold Rush! v1.0M 1989-02-28 (CE)) - */ -#if 0 -// FIXME: Identical to amigaAgiPaletteV2 -static const uint8 appleIIgsAgiPaletteV2[16 * 3] = { - 0x0, 0x0, 0x0, - 0x0, 0x0, 0xF, - 0x0, 0x8, 0x0, - 0x0, 0xD, 0xB, - 0xC, 0x0, 0x0, - 0xB, 0x7, 0xD, - 0x8, 0x5, 0x0, - 0xB, 0xB, 0xB, - 0x7, 0x7, 0x7, - 0x0, 0xB, 0xF, - 0x0, 0xE, 0x0, - 0x0, 0xF, 0xD, - 0xF, 0x9, 0x8, - 0xD, 0x9, 0xF, // Only this differs from the 1st generation palette - 0xE, 0xE, 0x0, - 0xF, 0xF, 0xF -}; -#endif + _renderStartVisualOffsetY = 0; + _renderStartDisplayOffsetY = 0; -/** - * First generation Amiga & Apple IIGS AGI palette. - * A 16-color, 12-bit RGB palette. - * - * Used by at least the following Amiga AGI versions: - * 2.082 (King's Quest I v1.0U 1986) - * 2.082 (Space Quest I v1.2 1986) - * 2.090 (King's Quest III v1.01 1986-11-08) - * 2.107 (King's Quest II v2.0J 1987-01-29) - * x.yyy (Black Cauldron v2.00 1987-06-14) - * x.yyy (Larry I v1.05 1987-06-26) - * - * Also used by at least the following Apple IIGS AGI versions: - * 1.002 (Space Quest I, intro says v2.2 1987) - */ -static const uint8 amigaAgiPaletteV1[16 * 3] = { - 0x0, 0x0, 0x0, - 0x0, 0x0, 0xF, - 0x0, 0x8, 0x0, - 0x0, 0xD, 0xB, - 0xC, 0x0, 0x0, - 0xB, 0x7, 0xD, - 0x8, 0x5, 0x0, - 0xB, 0xB, 0xB, - 0x7, 0x7, 0x7, - 0x0, 0xB, 0xF, - 0x0, 0xE, 0x0, - 0x0, 0xF, 0xD, - 0xF, 0x9, 0x8, - 0xF, 0x7, 0x0, - 0xE, 0xE, 0x0, - 0xF, 0xF, 0xF -}; + _upscaledHires = DISPLAY_UPSCALED_DISABLED; + _displayScreenWidth = DISPLAY_DEFAULT_WIDTH; + _displayScreenHeight = DISPLAY_DEFAULT_HEIGHT; + _displayFontWidth = 8; + _displayFontHeight = 8; + + _displayWidthMulAdjust = 0; // visualPos * (2+0) = displayPos + _displayHeightMulAdjust = 0; // visualPos * (1+0) = displayPos + + _pixels = 0; + _displayPixels = 0; + + _activeScreen = NULL; + _gameScreen = NULL; + _priorityScreen = NULL; + _displayScreen = NULL; +} /** - * Second generation Amiga AGI palette. - * A 16-color, 12-bit RGB palette. + * Initialize graphics device. * - * Used by at least the following Amiga AGI versions: - * 2.202 (Space Quest II v2.0F. Intro says 1988. ScummVM 0.10.0 detects as 1986-12-09) + * @see deinit_video() */ -static const uint8 amigaAgiPaletteV2[16 * 3] = { - 0x0, 0x0, 0x0, - 0x0, 0x0, 0xF, - 0x0, 0x8, 0x0, - 0x0, 0xD, 0xB, - 0xC, 0x0, 0x0, - 0xB, 0x7, 0xD, - 0x8, 0x5, 0x0, - 0xB, 0xB, 0xB, - 0x7, 0x7, 0x7, - 0x0, 0xB, 0xF, - 0x0, 0xE, 0x0, - 0x0, 0xF, 0xD, - 0xF, 0x9, 0x8, - 0xD, 0x0, 0xF, - 0xE, 0xE, 0x0, - 0xF, 0xF, 0xF -}; +int GfxMgr::initVideo() { + bool forceHires = false; + + // Set up palettes + initPalette(_paletteTextMode, PALETTE_EGA); + + switch (_vm->_renderMode) { + case Common::kRenderEGA: + initPalette(_paletteGfxMode, PALETTE_EGA); + break; + case Common::kRenderCGA: + initPalette(_paletteGfxMode, PALETTE_CGA, 4, 8); + break; + case Common::kRenderVGA: + initPalette(_paletteGfxMode, PALETTE_VGA, 256, 8); + break; + case Common::kRenderHercG: + initPalette(_paletteGfxMode, PALETTE_HERCULES_GREEN, 2, 8); + forceHires = true; + break; + case Common::kRenderHercA: + initPalette(_paletteGfxMode, PALETTE_HERCULES_AMBER, 2, 8); + forceHires = true; + break; + case Common::kRenderAmiga: + if (!ConfMan.getBool("altamigapalette")) { + // Set the correct Amiga palette depending on AGI interpreter version + if (_vm->getVersion() < 0x2936) + initPalette(_paletteGfxMode, PALETTE_AMIGA_V1, 16, 4); + else if (_vm->getVersion() == 0x2936) + initPalette(_paletteGfxMode, PALETTE_AMIGA_V2, 16, 4); + else if (_vm->getVersion() > 0x2936) + initPalette(_paletteGfxMode, PALETTE_AMIGA_V3, 16, 4); + } else { + // Set the old common alternative Amiga palette + initPalette(_paletteGfxMode, PALETTE_AMIGA_ALT); + } + break; + case Common::kRenderApple2GS: + switch (_vm->getGameID()) { + case GID_SQ1: + // Special one, only used for Space Quest 1 on Apple IIgs. Is the same as Amiga v1 palette + initPalette(_paletteGfxMode, PALETTE_APPLE_II_GS_SQ1, 16, 4); + break; + default: + // Regular "standard" Apple IIgs palette, used by everything else + initPalette(_paletteGfxMode, PALETTE_APPLE_II_GS, 16, 4); + break; + } + break; + case Common::kRenderAtariST: + initPalette(_paletteGfxMode, PALETTE_ATARI_ST, 16, 3); + break; + case Common::kRenderMacintosh: + switch (_vm->getGameID()) { + case GID_KQ3: + case GID_PQ1: + initPaletteCLUT(_paletteGfxMode, PALETTE_MACINTOSH_CLUT, 16); + break; + case GID_GOLDRUSH: + // We use the common KQ3/PQ1 palette at the moment. + // It seems the Gold Rush palette, that came with the game is quite ugly. + initPaletteCLUT(_paletteGfxMode, PALETTE_MACINTOSH_CLUT, 16); + break; + case GID_SQ2: + initPaletteCLUT(_paletteGfxMode, PALETTE_MACINTOSH_CLUT3, 16); + break; + default: + initPaletteCLUT(_paletteGfxMode, PALETTE_MACINTOSH_CLUT3, 16); + break; + } + break; + default: + error("initVideo: unsupported render mode"); + break; + } + + //bool forcedUpscale = true; + + if (_font->isFontHires() || forceHires) { + // Upscaling enable + _upscaledHires = DISPLAY_UPSCALED_640x400; + _displayScreenWidth = 640; + _displayScreenHeight = 400; + _displayFontWidth = 16; + _displayFontHeight = 16; + + _displayWidthMulAdjust = 2; + _displayHeightMulAdjust = 1; + } + + // set up mouse cursors + switch (_vm->_renderMode) { + case Common::kRenderEGA: + case Common::kRenderCGA: + case Common::kRenderVGA: + case Common::kRenderHercG: + case Common::kRenderHercA: + initMouseCursor(&_mouseCursor, MOUSECURSOR_SCI, 11, 16, 0, 0); + initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8); + break; + case Common::kRenderAmiga: + initMouseCursor(&_mouseCursor, MOUSECURSOR_AMIGA, 8, 11, 0, 0); + initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_AMIGA_BUSY, 13, 16, 7, 8); + break; + case Common::kRenderApple2GS: + // had no special busy mouse cursor + initMouseCursor(&_mouseCursor, MOUSECURSOR_APPLE_II_GS, 9, 11, 0, 0); + initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8); + break; + case Common::kRenderAtariST: + initMouseCursor(&_mouseCursor, MOUSECURSOR_ATARI_ST, 11, 16, 0, 0); + initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8); + break; + case Common::kRenderMacintosh: + // It looks like Atari ST + Macintosh used the same standard mouse cursor + // TODO: Verify by checking actual hardware + initMouseCursor(&_mouseCursor, MOUSECURSOR_ATARI_ST, 11, 16, 0, 0); + initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_MACINTOSH_BUSY, 10, 14, 7, 8); + break; + default: + error("initVideo: unsupported render mode"); + break; + } + + _pixels = SCRIPT_WIDTH * SCRIPT_HEIGHT; + _gameScreen = (byte *)calloc(_pixels, 1); + _priorityScreen = (byte *)calloc(_pixels, 1); + _activeScreen = _gameScreen; + //_activeScreen = _priorityScreen; + + _displayPixels = _displayScreenWidth * _displayScreenHeight; + _displayScreen = (byte *)calloc(_displayPixels, 1); + + initGraphics(_displayScreenWidth, _displayScreenHeight, _displayScreenWidth > 320); + + setPalette(true); // set gfx-mode palette + + // set up mouse cursor palette + CursorMan.replaceCursorPalette(MOUSECURSOR_PALETTE, 1, ARRAYSIZE(MOUSECURSOR_PALETTE) / 3); + setMouseCursor(); + + return errOK; +} /** - * Third generation Amiga AGI palette. - * A 16-color, 12-bit RGB palette. + * Deinitialize graphics device. * - * Used by at least the following Amiga AGI versions: - * 2.310 (Police Quest I v2.0B 1989-02-22) - * 2.316 (Gold Rush! v2.05 1989-03-09) - * x.yyy (Manhunter I v1.06 1989-03-18) - * 2.333 (Manhunter II v3.06 1989-08-17) - * 2.333 (King's Quest III v2.15 1989-11-15) + * @see init_video() */ -static const uint8 amigaAgiPaletteV3[16 * 3] = { - 0x0, 0x0, 0x0, - 0x0, 0x0, 0xB, - 0x0, 0xB, 0x0, - 0x0, 0xB, 0xB, - 0xB, 0x0, 0x0, - 0xB, 0x0, 0xB, - 0xC, 0x7, 0x0, - 0xB, 0xB, 0xB, - 0x7, 0x7, 0x7, - 0x0, 0x0, 0xF, - 0x0, 0xF, 0x0, - 0x0, 0xF, 0xF, - 0xF, 0x0, 0x0, - 0xF, 0x0, 0xF, - 0xF, 0xF, 0x0, - 0xF, 0xF, 0xF -}; +int GfxMgr::deinitVideo() { + // Free mouse cursors in case they were allocated + if (_mouseCursor.bitmapDataAllocated) + free(_mouseCursor.bitmapDataAllocated); + if (_mouseCursorBusy.bitmapDataAllocated) + free(_mouseCursorBusy.bitmapDataAllocated); -/** - * 16 color amiga-ish palette. - */ -static const uint8 altAmigaPalette[16 * 3] = { - 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3f, - 0x00, 0x2A, 0x00, - 0x00, 0x2A, 0x2A, - 0x33, 0x00, 0x00, - 0x2f, 0x1c, 0x37, - 0x23, 0x14, 0x00, - 0x2f, 0x2f, 0x2f, - 0x15, 0x15, 0x15, - 0x00, 0x2f, 0x3f, - 0x00, 0x33, 0x15, - 0x15, 0x3F, 0x3F, - 0x3f, 0x27, 0x23, - 0x3f, 0x15, 0x3f, - 0x3b, 0x3b, 0x00, - 0x3F, 0x3F, 0x3F -}; + free(_displayScreen); + free(_gameScreen); + free(_priorityScreen); -/** - * 256 color palette used with AGI256 and AGI256-2 games. - * Uses full 8 bits per color component. - */ -static const uint8 vgaPalette[256 * 3] = { - 0x00, 0x00, 0x00, - 0x00, 0x00, 0xA8, - 0x00, 0xA8, 0x00, - 0x00, 0xA8, 0xA8, - 0xA8, 0x00, 0x00, - 0xA8, 0x00, 0xA8, - 0xA8, 0x54, 0x00, - 0xA8, 0xA8, 0xA8, - 0x54, 0x54, 0x54, - 0x54, 0x54, 0xFC, - 0x54, 0xFC, 0x54, - 0x54, 0xFC, 0xFC, - 0xFC, 0x54, 0x54, - 0xFC, 0x54, 0xFC, - 0xFC, 0xFC, 0x54, - 0xFC, 0xFC, 0xFC, - 0x00, 0x00, 0x00, - 0x14, 0x14, 0x14, - 0x20, 0x20, 0x20, - 0x2C, 0x2C, 0x2C, - 0x38, 0x38, 0x38, - 0x44, 0x44, 0x44, - 0x50, 0x50, 0x50, - 0x60, 0x60, 0x60, - 0x70, 0x70, 0x70, - 0x80, 0x80, 0x80, - 0x90, 0x90, 0x90, - 0xA0, 0xA0, 0xA0, - 0xB4, 0xB4, 0xB4, - 0xC8, 0xC8, 0xC8, - 0xE0, 0xE0, 0xE0, - 0xFC, 0xFC, 0xFC, - 0x00, 0x00, 0xFC, - 0x40, 0x00, 0xFC, - 0x7C, 0x00, 0xFC, - 0xBC, 0x00, 0xFC, - 0xFC, 0x00, 0xFC, - 0xFC, 0x00, 0xBC, - 0xFC, 0x00, 0x7C, - 0xFC, 0x00, 0x40, - 0xFC, 0x00, 0x00, - 0xFC, 0x40, 0x00, - 0xFC, 0x7C, 0x00, - 0xFC, 0xBC, 0x00, - 0xFC, 0xFC, 0x00, - 0xBC, 0xFC, 0x00, - 0x7C, 0xFC, 0x00, - 0x40, 0xFC, 0x00, - 0x00, 0xFC, 0x00, - 0x00, 0xFC, 0x40, - 0x00, 0xFC, 0x7C, - 0x00, 0xFC, 0xBC, - 0x00, 0xFC, 0xFC, - 0x00, 0xBC, 0xFC, - 0x00, 0x7C, 0xFC, - 0x00, 0x40, 0xFC, - 0x7C, 0x7C, 0xFC, - 0x9C, 0x7C, 0xFC, - 0xBC, 0x7C, 0xFC, - 0xDC, 0x7C, 0xFC, - 0xFC, 0x7C, 0xFC, - 0xFC, 0x7C, 0xDC, - 0xFC, 0x7C, 0xBC, - 0xFC, 0x7C, 0x9C, - 0xFC, 0x7C, 0x7C, - 0xFC, 0x9C, 0x7C, - 0xFC, 0xBC, 0x7C, - 0xFC, 0xDC, 0x7C, - 0xFC, 0xFC, 0x7C, - 0xDC, 0xFC, 0x7C, - 0xBC, 0xFC, 0x7C, - 0x9C, 0xFC, 0x7C, - 0x7C, 0xFC, 0x7C, - 0x7C, 0xFC, 0x9C, - 0x7C, 0xFC, 0xBC, - 0x7C, 0xFC, 0xDC, - 0x7C, 0xFC, 0xFC, - 0x7C, 0xDC, 0xFC, - 0x7C, 0xBC, 0xFC, - 0x7C, 0x9C, 0xFC, - 0xB4, 0xB4, 0xFC, - 0xC4, 0xB4, 0xFC, - 0xD8, 0xB4, 0xFC, - 0xE8, 0xB4, 0xFC, - 0xFC, 0xB4, 0xFC, - 0xFC, 0xB4, 0xE8, - 0xFC, 0xB4, 0xD8, - 0xFC, 0xB4, 0xC4, - 0xFC, 0xB4, 0xB4, - 0xFC, 0xC4, 0xB4, - 0xFC, 0xD8, 0xB4, - 0xFC, 0xE8, 0xB4, - 0xFC, 0xFC, 0xB4, - 0xE8, 0xFC, 0xB4, - 0xD8, 0xFC, 0xB4, - 0xC4, 0xFC, 0xB4, - 0xB4, 0xFC, 0xB4, - 0xB4, 0xFC, 0xC4, - 0xB4, 0xFC, 0xD8, - 0xB4, 0xFC, 0xE8, - 0xB4, 0xFC, 0xFC, - 0xB4, 0xE8, 0xFC, - 0xB4, 0xD8, 0xFC, - 0xB4, 0xC4, 0xFC, - 0x00, 0x00, 0x70, - 0x1C, 0x00, 0x70, - 0x38, 0x00, 0x70, - 0x54, 0x00, 0x70, - 0x70, 0x00, 0x70, - 0x70, 0x00, 0x54, - 0x70, 0x00, 0x38, - 0x70, 0x00, 0x1C, - 0x70, 0x00, 0x00, - 0x70, 0x1C, 0x00, - 0x70, 0x38, 0x00, - 0x70, 0x54, 0x00, - 0x70, 0x70, 0x00, - 0x54, 0x70, 0x00, - 0x38, 0x70, 0x00, - 0x1C, 0x70, 0x00, - 0x00, 0x70, 0x00, - 0x00, 0x70, 0x1C, - 0x00, 0x70, 0x38, - 0x00, 0x70, 0x54, - 0x00, 0x70, 0x70, - 0x00, 0x54, 0x70, - 0x00, 0x38, 0x70, - 0x00, 0x1C, 0x70, - 0x38, 0x38, 0x70, - 0x44, 0x38, 0x70, - 0x54, 0x38, 0x70, - 0x60, 0x38, 0x70, - 0x70, 0x38, 0x70, - 0x70, 0x38, 0x60, - 0x70, 0x38, 0x54, - 0x70, 0x38, 0x44, - 0x70, 0x38, 0x38, - 0x70, 0x44, 0x38, - 0x70, 0x54, 0x38, - 0x70, 0x60, 0x38, - 0x70, 0x70, 0x38, - 0x60, 0x70, 0x38, - 0x54, 0x70, 0x38, - 0x44, 0x70, 0x38, - 0x38, 0x70, 0x38, - 0x38, 0x70, 0x44, - 0x38, 0x70, 0x54, - 0x38, 0x70, 0x60, - 0x38, 0x70, 0x70, - 0x38, 0x60, 0x70, - 0x38, 0x54, 0x70, - 0x38, 0x44, 0x70, - 0x50, 0x50, 0x70, - 0x58, 0x50, 0x70, - 0x60, 0x50, 0x70, - 0x68, 0x50, 0x70, - 0x70, 0x50, 0x70, - 0x70, 0x50, 0x68, - 0x70, 0x50, 0x60, - 0x70, 0x50, 0x58, - 0x70, 0x50, 0x50, - 0x70, 0x58, 0x50, - 0x70, 0x60, 0x50, - 0x70, 0x68, 0x50, - 0x70, 0x70, 0x50, - 0x68, 0x70, 0x50, - 0x60, 0x70, 0x50, - 0x58, 0x70, 0x50, - 0x50, 0x70, 0x50, - 0x50, 0x70, 0x58, - 0x50, 0x70, 0x60, - 0x50, 0x70, 0x68, - 0x50, 0x70, 0x70, - 0x50, 0x68, 0x70, - 0x50, 0x60, 0x70, - 0x50, 0x58, 0x70, - 0x00, 0x00, 0x40, - 0x10, 0x00, 0x40, - 0x20, 0x00, 0x40, - 0x30, 0x00, 0x40, - 0x40, 0x00, 0x40, - 0x40, 0x00, 0x30, - 0x40, 0x00, 0x20, - 0x40, 0x00, 0x10, - 0x40, 0x00, 0x00, - 0x40, 0x10, 0x00, - 0x40, 0x20, 0x00, - 0x40, 0x30, 0x00, - 0x40, 0x40, 0x00, - 0x30, 0x40, 0x00, - 0x20, 0x40, 0x00, - 0x10, 0x40, 0x00, - 0x00, 0x40, 0x00, - 0x00, 0x40, 0x10, - 0x00, 0x40, 0x20, - 0x00, 0x40, 0x30, - 0x00, 0x40, 0x40, - 0x00, 0x30, 0x40, - 0x00, 0x20, 0x40, - 0x00, 0x10, 0x40, - 0x20, 0x20, 0x40, - 0x28, 0x20, 0x40, - 0x30, 0x20, 0x40, - 0x38, 0x20, 0x40, - 0x40, 0x20, 0x40, - 0x40, 0x20, 0x38, - 0x40, 0x20, 0x30, - 0x40, 0x20, 0x28, - 0x40, 0x20, 0x20, - 0x40, 0x28, 0x20, - 0x40, 0x30, 0x20, - 0x40, 0x38, 0x20, - 0x40, 0x40, 0x20, - 0x38, 0x40, 0x20, - 0x30, 0x40, 0x20, - 0x28, 0x40, 0x20, - 0x20, 0x40, 0x20, - 0x20, 0x40, 0x28, - 0x20, 0x40, 0x30, - 0x20, 0x40, 0x38, - 0x20, 0x40, 0x40, - 0x20, 0x38, 0x40, - 0x20, 0x30, 0x40, - 0x20, 0x28, 0x40, - 0x2C, 0x2C, 0x40, - 0x30, 0x2C, 0x40, - 0x34, 0x2C, 0x40, - 0x3C, 0x2C, 0x40, - 0x40, 0x2C, 0x40, - 0x40, 0x2C, 0x3C, - 0x40, 0x2C, 0x34, - 0x40, 0x2C, 0x30, - 0x40, 0x2C, 0x2C, - 0x40, 0x30, 0x2C, - 0x40, 0x34, 0x2C, - 0x40, 0x3C, 0x2C, - 0x40, 0x40, 0x2C, - 0x3C, 0x40, 0x2C, - 0x34, 0x40, 0x2C, - 0x30, 0x40, 0x2C, - 0x2C, 0x40, 0x2C, - 0x2C, 0x40, 0x30, - 0x2C, 0x40, 0x34, - 0x2C, 0x40, 0x3C, - 0x2C, 0x40, 0x40, - 0x2C, 0x3C, 0x40, - 0x2C, 0x34, 0x40, - 0x2C, 0x30, 0x40, - 0x40, 0x40, 0x40, - 0x38, 0x38, 0x38, - 0x30, 0x30, 0x30, - 0x28, 0x28, 0x28, - 0x24, 0x24, 0x24, - 0x1C, 0x1C, 0x1C, - 0x14, 0x14, 0x14, - 0x0C, 0x0C, 0x0C -}; + return errOK; +} -static const uint16 cgaMap[16] = { - 0x0000, // 0 - black - 0x0d00, // 1 - blue - 0x0b00, // 2 - green - 0x0f00, // 3 - cyan - 0x000b, // 4 - red - 0x0b0d, // 5 - magenta - 0x000d, // 6 - brown - 0x0b0b, // 7 - gray - 0x0d0d, // 8 - dark gray - 0x0b0f, // 9 - light blue - 0x0b0d, // 10 - light green - 0x0f0d, // 11 - light cyan - 0x0f0d, // 12 - light red - 0x0f00, // 13 - light magenta - 0x0f0b, // 14 - yellow - 0x0f0f // 15 - white -}; +void GfxMgr::setRenderStartOffset(uint16 offsetY) { + if (offsetY >= (VISUAL_HEIGHT - SCRIPT_HEIGHT)) + error("invalid render start offset"); -struct UpdateBlock { - int x1, y1; - int x2, y2; -}; + _renderStartVisualOffsetY = offsetY; + _renderStartDisplayOffsetY = offsetY * (1 + _displayHeightMulAdjust); +} +uint16 GfxMgr::getRenderStartDisplayOffsetY() { + return _renderStartDisplayOffsetY; +} -static struct UpdateBlock update = { - MAX_INT, MAX_INT, 0, 0 -}; +// Translates a game screen coordinate to a display screen coordinate +// Game screen to 320x200 -> x * 2, y + renderStart +// Game screen to 640x400 -> x * 4, (y * 2) + renderStart +void GfxMgr::translateGamePosToDisplayScreen(int16 &x, int16 &y) { + x = x * (2 + _displayWidthMulAdjust); + y = y * (1 + _displayHeightMulAdjust) + _renderStartDisplayOffsetY; +} -GfxMgr::GfxMgr(AgiBase *vm) : _vm(vm) { - _shakeH = NULL; - _shakeV = NULL; - _agipalFileNum = 0; - _currentCursorPalette = 0; // cursor palette not set +// Translates a visual coordinate to a display screen coordinate +// Visual to 320x200 -> x * 2, y +// Visual to 640x400 -> x * 4, y * 2 +void GfxMgr::translateVisualPosToDisplayScreen(int16 &x, int16 &y) { + x = x * (2 + _displayWidthMulAdjust); + y = y * (1 + _displayHeightMulAdjust); } +// Translates a display screen coordinate to a game screen coordinate +// Display screen to 320x200 -> x / 2, y - renderStart +// Display screen to 640x400 -> x / 4, (y / 2) - renderStart +void GfxMgr::translateDisplayPosToGameScreen(int16 &x, int16 &y) { + y -= _renderStartDisplayOffsetY; // remove status bar line + x = x / (2 + _displayWidthMulAdjust); + y = y / (1 + _displayHeightMulAdjust); + if (y < 0) + y = 0; + if (y >= SCRIPT_HEIGHT) + y = SCRIPT_HEIGHT + 1; // 1 beyond +} -// -// Layer 4: 640x480? ================== User display -// ^ -// | do_update(), put_block() -// | -// Layer 3: 640x480? ================== Framebuffer -// ^ -// | flush_block(), put_pixels() -// | -// Layer 2: 320x200 ================== AGI engine screen (console), put_pixel() -// | -// Layer 1: 160x336 ================== AGI screen -// -// Upper half (160x168) of Layer 1 is for the normal 16 color & control line/priority info. -// Lower half (160x168) of Layer 1 is for the 256 color information (Used with AGI256 & AGI256-2). -// +// Translates dimension from visual screen to display screen +void GfxMgr::translateVisualDimensionToDisplayScreen(int16 &width, int16 &height) { + width = width * (2 + _displayWidthMulAdjust); + height = height * (1 + _displayHeightMulAdjust); +} + +// Translates dimension from display screen to visual screen +void GfxMgr::translateDisplayDimensionToVisualScreen(int16 &width, int16 &height) { + width = width / (2 + _displayWidthMulAdjust); + height = height / (1 + _displayHeightMulAdjust); +} -#define SHAKE_MAG 3 +// Translates a rect from game screen to display screen +void GfxMgr::translateGameRectToDisplayScreen(int16 &x, int16 &y, int16 &width, int16 &height) { + translateGamePosToDisplayScreen(x, y); + translateVisualDimensionToDisplayScreen(width, height); +} -void GfxMgr::shakeStart() { - int i; +// Translates a rect from visual screen to display screen +void GfxMgr::translateVisualRectToDisplayScreen(int16 &x, int16 &y, int16 &width, int16 &height) { + translateVisualPosToDisplayScreen(x, y); + translateVisualDimensionToDisplayScreen(width, height); +} - if ((_shakeH = (uint8 *)malloc(GFX_WIDTH * SHAKE_MAG)) == NULL) - return; +uint32 GfxMgr::getDisplayOffsetToGameScreenPos(int16 x, int16 y) { + translateGamePosToDisplayScreen(x, y); + return (y * _displayScreenWidth) + x; +} + +uint32 GfxMgr::getDisplayOffsetToVisualScreenPos(int16 x, int16 y) { + translateVisualPosToDisplayScreen(x, y); + return (y * _displayScreenWidth) + x; +} + +// Attention: uses display screen coordinates! +void GfxMgr::copyDisplayRectToScreen(int16 x, int16 y, int16 width, int16 height) { + g_system->copyRectToScreen(_displayScreen + y * _displayScreenWidth + x, _displayScreenWidth, x, y, width, height); +} +void GfxMgr::copyDisplayRectToScreen(int16 x, int16 adjX, int16 y, int16 adjY, int16 width, int16 adjWidth, int16 height, int16 adjHeight) { + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + break; + case DISPLAY_UPSCALED_640x400: + adjX *= 2; adjY *= 2; + adjWidth *= 2; adjHeight *= 2; + break; + default: + assert(0); + break; + } + x += adjX; y += adjY; + width += adjWidth; height += adjHeight; + g_system->copyRectToScreen(_displayScreen + y * _displayScreenWidth + x, _displayScreenWidth, x, y, width, height); +} +void GfxMgr::copyDisplayRectToScreenUsingGamePos(int16 x, int16 y, int16 width, int16 height) { + translateGameRectToDisplayScreen(x, y, width, height); + g_system->copyRectToScreen(_displayScreen + (y * _displayScreenWidth) + x, _displayScreenWidth, x, y, width, height); +} +void GfxMgr::copyDisplayRectToScreenUsingVisualPos(int16 x, int16 y, int16 width, int16 height) { + translateVisualRectToDisplayScreen(x, y, width, height); + g_system->copyRectToScreen(_displayScreen + (y * _displayScreenWidth) + x, _displayScreenWidth, x, y, width, height); +} +void GfxMgr::copyDisplayToScreen() { + g_system->copyRectToScreen(_displayScreen, _displayScreenWidth, 0, 0, _displayScreenWidth, _displayScreenHeight); +} + +void GfxMgr::translateFontPosToDisplayScreen(int16 &x, int16 &y) { + x *= _displayFontWidth; + y *= _displayFontHeight; +} +void GfxMgr::translateDisplayPosToFontScreen(int16 &x, int16 &y) { + x /= _displayFontWidth; + y /= _displayFontHeight; +} +void GfxMgr::translateFontDimensionToDisplayScreen(int16 &width, int16 &height) { + width *= _displayFontWidth; + height *= _displayFontHeight; +} +void GfxMgr::translateFontRectToDisplayScreen(int16 &x, int16 &y, int16 &width, int16 &height) { + translateFontPosToDisplayScreen(x, y); + translateFontDimensionToDisplayScreen(width, height); +} +Common::Rect GfxMgr::getFontRectForDisplayScreen(int16 column, int16 row, int16 width, int16 height) { + Common::Rect displayRect(width * _displayFontWidth, height * _displayFontHeight); + displayRect.moveTo(column * _displayFontWidth, row * _displayFontHeight); + return displayRect; +} + +void GfxMgr::debugShowMap(int mapNr) { + switch (mapNr) { + case 0: + _activeScreen = _gameScreen; + break; + case 1: + _activeScreen = _priorityScreen; + break; + default: + break; + } + + render_Block(0, 0, SCRIPT_WIDTH, SCRIPT_HEIGHT); +} + +void GfxMgr::clear(byte color, byte priority) { + memset(_gameScreen, color, _pixels); + memset(_priorityScreen, priority, _pixels); +} - if ((_shakeV = (uint8 *)malloc(SHAKE_MAG * (GFX_HEIGHT - SHAKE_MAG))) == NULL) { - free(_shakeH); +void GfxMgr::clearDisplay(byte color, bool copyToScreen) { + memset(_displayScreen, color, _displayPixels); + + if (copyToScreen) { + copyDisplayToScreen(); + } +} + +void GfxMgr::putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority) { + int offset = y * SCRIPT_WIDTH + x; + + if (drawMask & GFX_SCREEN_MASK_VISUAL) { + _gameScreen[offset] = color; + } + if (drawMask & GFX_SCREEN_MASK_PRIORITY) { + _priorityScreen[offset] = priority; + } +} + +void GfxMgr::putPixelOnDisplay(int16 x, int16 y, byte color) { + uint32 offset = 0; + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + offset = y * _displayScreenWidth + x; + + _displayScreen[offset] = color; + break; + case DISPLAY_UPSCALED_640x400: + offset = (y * _displayScreenWidth) + x; + + _displayScreen[offset + 0] = color; + _displayScreen[offset + 1] = color; + _displayScreen[offset + _displayScreenWidth + 0] = color; + _displayScreen[offset + _displayScreenWidth + 1] = color; + break; + default: + break; + } +} + +void GfxMgr::putPixelOnDisplay(int16 x, int16 adjX, int16 y, int16 adjY, byte color) { + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + break; + case DISPLAY_UPSCALED_640x400: + adjX *= 2; adjY *= 2; + break; + default: + assert(0); + break; + } + x += adjX; + y += adjY; + putPixelOnDisplay(x, y, color); +} + +void GfxMgr::putFontPixelOnDisplay(int16 baseX, int16 baseY, int16 addX, int16 addY, byte color, bool isHires) { + uint32 offset = 0; + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + offset = ((baseY + addY) * _displayScreenWidth) + (baseX + addX); + _displayScreen[offset] = color; + break; + case DISPLAY_UPSCALED_640x400: + if (isHires) { + offset = ((baseY + addY) * _displayScreenWidth) + (baseX + addX); + _displayScreen[offset] = color; + } else { + offset = ((baseY + addY * 2) * _displayScreenWidth) + (baseX + addX * 2); + _displayScreen[offset + 0] = color; + _displayScreen[offset + 1] = color; + _displayScreen[offset + _displayScreenWidth + 0] = color; + _displayScreen[offset + _displayScreenWidth + 1] = color; + } + break; + default: + break; + } +} + +byte GfxMgr::getColor(int16 x, int16 y) { + int offset = y * SCRIPT_WIDTH + x; + + return _gameScreen[offset]; +} + +byte GfxMgr::getPriority(int16 x, int16 y) { + int offset = y * SCRIPT_WIDTH + x; + + return _priorityScreen[offset]; +} + +// used, when a control pixel is found +// will search downwards and compare priority in case any is found +bool GfxMgr::checkControlPixel(int16 x, int16 y, byte viewPriority) { + int offset = y * SCRIPT_WIDTH + x; + byte curPriority; + + while (1) { + y++; + offset += SCRIPT_WIDTH; + if (y >= SCRIPT_HEIGHT) { + // end of screen, nothing but control pixels found + return true; // draw view pixel + } + curPriority = _priorityScreen[offset]; + if (curPriority > 2) // valid priority found? + break; + } + if (curPriority <= viewPriority) + return true; // view priority is higher, draw + return false; // view priority is lower, don't draw +} + +static byte CGA_MixtureColorTable[] = { + 0x00, 0x08, 0x04, 0x0C, 0x01, 0x09, 0x02, 0x05, + 0x0A, 0x0D, 0x06, 0x0E, 0x0B, 0x03, 0x07, 0x0F +}; + +byte GfxMgr::getCGAMixtureColor(byte color) { + return CGA_MixtureColorTable[color & 0x0F]; +} + +// Attention: in our implementation, y-coordinate is upper left. +// Sierra passed the lower left instead. We changed it to make upscaling easier. +void GfxMgr::render_Block(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) { + if (!render_Clip(x, y, width, height)) return; + + switch (_vm->_renderMode) { + case Common::kRenderHercG: + case Common::kRenderHercA: + render_BlockHercules(x, y, width, height, copyToScreen); + break; + case Common::kRenderCGA: + render_BlockCGA(x, y, width, height, copyToScreen); + break; + case Common::kRenderEGA: + default: + render_BlockEGA(x, y, width, height, copyToScreen); + break; + } + + if (copyToScreen) { + copyDisplayRectToScreenUsingGamePos(x, y, width, height); + } +} + +bool GfxMgr::render_Clip(int16 &x, int16 &y, int16 &width, int16 &height, int16 clipAgainstWidth, int16 clipAgainstHeight) { + if ((x >= clipAgainstWidth) || ((x + width - 1) < 0) || + (y < 0) || ((y + (height - 1)) >= clipAgainstHeight)) { + return false; + } + + if (y < 0) { + height += y; + y = 0; + } + + if ((y + height - 1) >= clipAgainstHeight) { + height = clipAgainstHeight - y; } - for (i = 0; i < GFX_HEIGHT - SHAKE_MAG; i++) { - memcpy(_shakeV + i * SHAKE_MAG, _agiScreen + i * GFX_WIDTH, SHAKE_MAG); +#if 0 + if ((y - height + 1) < 0) + height = y + 1; + + if (y >= clipAgainstHeight) { + height -= y - (clipAgainstHeight - 1); + y = clipAgainstHeight - 1; } +#endif - for (i = 0; i < SHAKE_MAG; i++) { - memcpy(_shakeH + i * GFX_WIDTH, _agiScreen + i * GFX_WIDTH, GFX_WIDTH); + if (x < 0) { + width += x; + x = 0; } + + if ((x + width - 1) >= clipAgainstWidth) { + width = clipAgainstWidth - x; + } + return true; } -void GfxMgr::shakeScreen(int n) { - int i; +void GfxMgr::render_BlockEGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) { + uint32 offsetVisual = SCRIPT_WIDTH * y + x; + uint32 offsetDisplay = getDisplayOffsetToGameScreenPos(x, y); + int16 remainingWidth = width; + int16 remainingHeight = height; + byte curColor = 0; + int16 displayWidth = width * (2 + _displayWidthMulAdjust); + + while (remainingHeight) { + remainingWidth = width; + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + while (remainingWidth) { + curColor = _activeScreen[offsetVisual++]; + _displayScreen[offsetDisplay++] = curColor; + _displayScreen[offsetDisplay++] = curColor; + remainingWidth--; + } + break; + case DISPLAY_UPSCALED_640x400: + while (remainingWidth) { + curColor = _activeScreen[offsetVisual++]; + memset(&_displayScreen[offsetDisplay], curColor, 4); + memset(&_displayScreen[offsetDisplay + _displayScreenWidth], curColor, 4); + offsetDisplay += 4; + remainingWidth--; + } + break; + default: + assert(0); + break; + } - if (n == 0) { - for (i = 0; i < (GFX_HEIGHT - SHAKE_MAG); i++) { - memmove(&_agiScreen[GFX_WIDTH * i], - &_agiScreen[GFX_WIDTH * (i + SHAKE_MAG) + SHAKE_MAG], - GFX_WIDTH - SHAKE_MAG); + offsetVisual += SCRIPT_WIDTH - width; + offsetDisplay += _displayScreenWidth - displayWidth; + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_640x400: + offsetDisplay += _displayScreenWidth; + break; + default: + break; } - } else { - for (i = GFX_HEIGHT - SHAKE_MAG - 1; i >= 0; i--) { - memmove(&_agiScreen[GFX_WIDTH * (i + SHAKE_MAG) + SHAKE_MAG], - &_agiScreen[GFX_WIDTH * i], GFX_WIDTH - SHAKE_MAG); + + remainingHeight--; + } +} + +void GfxMgr::render_BlockCGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) { + uint32 offsetVisual = SCRIPT_WIDTH * y + x; + uint32 offsetDisplay = getDisplayOffsetToGameScreenPos(x, y); + int16 remainingWidth = width; + int16 remainingHeight = height; + byte curColor = 0; + int16 displayWidth = width * (2 + _displayWidthMulAdjust); + + while (remainingHeight) { + remainingWidth = width; + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + while (remainingWidth) { + curColor = _activeScreen[offsetVisual++]; + _displayScreen[offsetDisplay++] = curColor & 0x03; // we process CGA mixture + _displayScreen[offsetDisplay++] = curColor >> 2; + remainingWidth--; + } + break; + case DISPLAY_UPSCALED_640x400: + while (remainingWidth) { + curColor = _activeScreen[offsetVisual++]; + _displayScreen[offsetDisplay + 0] = curColor & 0x03; // we process CGA mixture + _displayScreen[offsetDisplay + 1] = curColor >> 2; + _displayScreen[offsetDisplay + 2] = curColor & 0x03; + _displayScreen[offsetDisplay + 3] = curColor >> 2; + _displayScreen[offsetDisplay + _displayScreenWidth + 0] = curColor & 0x03; + _displayScreen[offsetDisplay + _displayScreenWidth + 1] = curColor >> 2; + _displayScreen[offsetDisplay + _displayScreenWidth + 2] = curColor & 0x03; + _displayScreen[offsetDisplay + _displayScreenWidth + 3] = curColor >> 2; + offsetDisplay += 4; + remainingWidth--; + } + break; + default: + assert(0); + break; } + + offsetVisual += SCRIPT_WIDTH - width; + offsetDisplay += _displayScreenWidth - displayWidth; + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_640x400: + offsetDisplay += _displayScreenWidth; + break; + default: + break; + } + + remainingHeight--; } } -void GfxMgr::shakeEnd() { - int i; +static const uint8 herculesColorMapping[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x88, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x80, 0x10, 0x02, 0x20, 0x01, 0x08, 0x40, 0x04, + 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, + 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, + 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, + 0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88, + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, + 0x22, 0x00, 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, + 0xD7, 0xFF, 0x7D, 0xFF, 0xD7, 0xFF, 0x7D, 0xFF, + 0xDD, 0x55, 0x77, 0xAA, 0xDD, 0x55, 0x77, 0xAA, + 0x7F, 0xEF, 0xFD, 0xDF, 0xFE, 0xF7, 0xBF, 0xFB, + 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, + 0x77, 0xBB, 0xDD, 0xEE, 0x77, 0xBB, 0xDD, 0xEE, + 0x77, 0xFF, 0xFF, 0xFF, 0xDD, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +// Sierra actually seems to have rendered the whole screen all the time +void GfxMgr::render_BlockHercules(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) { + uint32 offsetVisual = SCRIPT_WIDTH * y + x; + uint32 offsetDisplay = getDisplayOffsetToGameScreenPos(x, y); + int16 remainingWidth = width; + int16 remainingHeight = height; + byte curColor = 0; + int16 displayWidth = width * (2 + _displayWidthMulAdjust); + + assert(_upscaledHires == DISPLAY_UPSCALED_640x400); + + uint16 lookupOffset1 = (y * 2 & 0x07); + uint16 lookupOffset2 = 0; + bool getUpperNibble = false; + byte herculesColors1 = 0; + byte herculesColors2 = 0; + + while (remainingHeight) { + remainingWidth = width; + + lookupOffset1 = (lookupOffset1 + 0) & 0x07; + lookupOffset2 = (lookupOffset1 + 1) & 0x07; + + getUpperNibble = (x & 1) ? false : true; + while (remainingWidth) { + curColor = _activeScreen[offsetVisual++] & 0x0F; + + if (getUpperNibble) { + herculesColors1 = herculesColorMapping[curColor * 8 + lookupOffset1] & 0x0F; + herculesColors2 = herculesColorMapping[curColor * 8 + lookupOffset2] & 0x0F; + } else { + herculesColors1 = herculesColorMapping[curColor * 8 + lookupOffset1] >> 4; + herculesColors2 = herculesColorMapping[curColor * 8 + lookupOffset2] >> 4; + } + getUpperNibble ^= true; + + _displayScreen[offsetDisplay + 0] = (herculesColors1 & 0x08) ? 1 : 0; + _displayScreen[offsetDisplay + 1] = (herculesColors1 & 0x04) ? 1 : 0; + _displayScreen[offsetDisplay + 2] = (herculesColors1 & 0x02) ? 1 : 0; + _displayScreen[offsetDisplay + 3] = (herculesColors1 & 0x01) ? 1 : 0; + + _displayScreen[offsetDisplay + _displayScreenWidth + 0] = (herculesColors2 & 0x08) ? 1 : 0; + _displayScreen[offsetDisplay + _displayScreenWidth + 1] = (herculesColors2 & 0x04) ? 1 : 0; + _displayScreen[offsetDisplay + _displayScreenWidth + 2] = (herculesColors2 & 0x02) ? 1 : 0; + _displayScreen[offsetDisplay + _displayScreenWidth + 3] = (herculesColors2 & 0x01) ? 1 : 0; + + offsetDisplay += 4; + remainingWidth--; + } + + lookupOffset1 += 2; + + offsetVisual += SCRIPT_WIDTH - width; + offsetDisplay += _displayScreenWidth - displayWidth; + offsetDisplay += _displayScreenWidth; - for (i = 0; i < GFX_HEIGHT - SHAKE_MAG; i++) { - memcpy(_agiScreen + i * GFX_WIDTH, _shakeV + i * SHAKE_MAG, SHAKE_MAG); + remainingHeight--; } +} - for (i = 0; i < SHAKE_MAG; i++) { - memcpy(_agiScreen + i * GFX_WIDTH, _shakeH + i * GFX_WIDTH, GFX_WIDTH); +// Table used for at least Manhunter 2, it renders 2 lines -> 3 lines instead of 4 +// Manhunter 1 is shipped with a broken Hercules font +// King's Quest 4 aborts right at the start, when Hercules rendering is active +#if 0 +static const uint8 herculesCoordinateOffset[] = { + 0x00, 0x01, 0x03, 0x04, 0x06, 0x07, 0x01, 0x02, + 0x04, 0x05, 0x07, 0x00, 0x02, 0x03, 0x05, 0x06 +}; + +static const uint8 herculesColorMapping[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x02, 0x00, 0x40, 0x00, 0x08, 0x00, + 0x80, 0x10, 0x02, 0x20, 0x01, 0x08, 0x40, 0x04, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, + 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, + 0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, + 0x22, 0x00, 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0xD7, 0xFF, 0x7D, 0xFF, 0xD7, 0xFF, 0x7D, 0xFF, + 0xDD, 0x55, 0x77, 0xAA, 0xDD, 0x55, 0x77, 0xAA, 0x7F, 0xEF, 0xFD, 0xDF, 0xFE, 0xF7, 0xBF, 0xFB, + 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0x77, 0xBB, 0xDD, 0xEE, 0x77, 0xBB, 0xDD, 0xEE, + 0x7F, 0xEF, 0xFB, 0xBF, 0xEF, 0xFE, 0xBF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; +#endif + +void GfxMgr::transition_Amiga() { + uint16 screenPos = 1; + uint32 screenStepPos = 1; + int16 posY = 0, posX = 0; + int16 stepCount = 0; + + // disable mouse while transition is taking place + if ((_vm->_game.mouseEnabled) && (!_vm->_game.mouseHidden)) { + CursorMan.showMouse(false); } - flushBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); + do { + if (screenPos & 1) { + screenPos = screenPos >> 1; + screenPos = screenPos ^ 0x3500; // 13568d + } else { + screenPos = screenPos >> 1; + } + + if ((screenPos < 13440) && (screenPos & 1)) { + screenStepPos = screenPos >> 1; + posY = screenStepPos / SCRIPT_WIDTH; + posX = screenStepPos - (posY * SCRIPT_WIDTH); + + // Adjust to only update the game screen, not the status bar + translateGamePosToDisplayScreen(posX, posY); + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + for (int16 multiPixel = 0; multiPixel < 4; multiPixel++) { + screenStepPos = (posY * _displayScreenWidth) + posX; + g_system->copyRectToScreen(_displayScreen + screenStepPos, _displayScreenWidth, posX, posY, 2, 1); + posY += 42; + } + break; + case DISPLAY_UPSCALED_640x400: + for (int16 multiPixel = 0; multiPixel < 4; multiPixel++) { + screenStepPos = (posY * _displayScreenWidth) + posX; + g_system->copyRectToScreen(_displayScreen + screenStepPos, _displayScreenWidth, posX, posY, 4, 2); + posY += 42 * 2; + } + break; + default: + assert(0); + break; + } + + stepCount++; + if (stepCount == 220) { + // 30 times for the whole transition, so should take around 0.5 seconds + g_system->updateScreen(); + g_system->delayMillis(16); + stepCount = 0; + } + } + } while (screenPos != 1); + + // Enable mouse again + if ((_vm->_game.mouseEnabled) && (!_vm->_game.mouseHidden)) { + CursorMan.showMouse(true); + } - free(_shakeV); - free(_shakeH); + g_system->updateScreen(); } -void GfxMgr::putTextCharacter(int l, int x, int y, unsigned char c, int fg, int bg, bool checkerboard, const uint8 *font) { - int x1, y1, xx, yy, cc; - const uint8 *p; +// This transition code was not reverse engineered, but created based on the Amiga transition code +// Atari ST definitely had a hi-res transition using the full resolution unlike the Amiga transition. +void GfxMgr::transition_AtariSt() { + uint16 screenPos = 1; + uint32 screenStepPos = 1; + int16 posY = 0, posX = 0; + int16 stepCount = 0; + + // disable mouse while transition is taking place + if ((_vm->_game.mouseEnabled) && (!_vm->_game.mouseHidden)) { + CursorMan.showMouse(false); + } - assert(font); + do { + if (screenPos & 1) { + screenPos = screenPos >> 1; + screenPos = screenPos ^ 0x3500; // 13568d + } else { + screenPos = screenPos >> 1; + } - p = font + ((unsigned int)c * CHAR_LINES); - for (y1 = 0; y1 < CHAR_LINES; y1++) { - for (x1 = 0; x1 < CHAR_COLS; x1++) { - xx = x + x1; - yy = y + y1; - cc = (*p & (1 << (7 - x1))) ? fg : bg; - _agiScreen[xx + yy * GFX_WIDTH] = cc; + if ((screenPos < 13440) && (screenPos & 1)) { + screenStepPos = screenPos >> 1; + posY = screenStepPos / DISPLAY_DEFAULT_WIDTH; + posX = screenStepPos - (posY * DISPLAY_DEFAULT_WIDTH); + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + posY += _renderStartDisplayOffsetY; // adjust to only update the main area, not the status bar + for (int16 multiPixel = 0; multiPixel < 8; multiPixel++) { + screenStepPos = (posY * _displayScreenWidth) + posX; + g_system->copyRectToScreen(_displayScreen + screenStepPos, _displayScreenWidth, posX, posY, 1, 1); + posY += 21; + } + break; + case DISPLAY_UPSCALED_640x400: + posX *= 2; posY *= 2; + posY += _renderStartDisplayOffsetY; // adjust to only update the main area, not the status bar + for (int16 multiPixel = 0; multiPixel < 8; multiPixel++) { + screenStepPos = (posY * _displayScreenWidth) + posX; + g_system->copyRectToScreen(_displayScreen + screenStepPos, _displayScreenWidth, posX, posY, 2, 2); + posY += 21 * 2; + } + break; + default: + break; + } + + stepCount++; + if (stepCount == 168) { + // 40 times for the whole transition, so should take around 0.7 seconds + // When using an Atari ST emulator, the transition seems to be even slower than this + // TODO: should get checked on real hardware + g_system->updateScreen(); + g_system->delayMillis(16); + stepCount = 0; + } } + } while (screenPos != 1); + + // Enable mouse again + if ((_vm->_game.mouseEnabled) && (!_vm->_game.mouseHidden)) { + CursorMan.showMouse(true); + } + + g_system->updateScreen(); +} + +// Attention: y coordinate is here supposed to be the upper one! +void GfxMgr::block_save(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr) { + int16 startOffset = y * SCRIPT_WIDTH + x; + int16 offset = startOffset; + int16 remainingHeight = height; + byte *curBufferPtr = bufferPtr; + + //warning("block_save: %d, %d -> %d, %d", x, y, width, height); + + while (remainingHeight) { + memcpy(curBufferPtr, _gameScreen + offset, width); + offset += SCRIPT_WIDTH; + curBufferPtr += width; + remainingHeight--; + } + + remainingHeight = height; + offset = startOffset; + while (remainingHeight) { + memcpy(curBufferPtr, _priorityScreen + offset, width); + offset += SCRIPT_WIDTH; + curBufferPtr += width; + remainingHeight--; + } +} + +// Attention: y coordinate is here supposed to be the upper one! +void GfxMgr::block_restore(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr) { + int16 startOffset = y * SCRIPT_WIDTH + x; + int16 offset = startOffset; + int16 remainingHeight = height; + byte *curBufferPtr = bufferPtr; - p++; + //warning("block_restore: %d, %d -> %d, %d", x, y, width, height); + + while (remainingHeight) { + memcpy(_gameScreen + offset, curBufferPtr, width); + offset += SCRIPT_WIDTH; + curBufferPtr += width; + remainingHeight--; } - // Simple checkerboard effect to simulate "greyed out" text. - // This is what Sierra's interpreter does for things like menu items - // that aren't selectable (such as separators). -- dsymonds - if (checkerboard) { - for (yy = y; yy < y + CHAR_LINES; yy++) - for (xx = x + (~yy & 1); xx < x + CHAR_COLS; xx += 2) - _agiScreen[xx + yy * GFX_WIDTH] = 15; + remainingHeight = height; + offset = startOffset; + while (remainingHeight) { + memcpy(_priorityScreen + offset, curBufferPtr, width); + offset += SCRIPT_WIDTH; + curBufferPtr += width; + remainingHeight--; } +} - // FIXME: we don't want this when we're writing on the - // console! - flushBlock(x, y, x + CHAR_COLS - 1, y + CHAR_LINES - 1); -} - -void GfxMgr::drawRectangle(int x1, int y1, int x2, int y2, int c) { - int y, w, h; - uint8 *p0; - - if (x1 >= GFX_WIDTH) - x1 = GFX_WIDTH - 1; - if (y1 >= GFX_HEIGHT) - y1 = GFX_HEIGHT - 1; - if (x2 >= GFX_WIDTH) - x2 = GFX_WIDTH - 1; - if (y2 >= GFX_HEIGHT) - y2 = GFX_HEIGHT - 1; - - w = x2 - x1 + 1; - h = y2 - y1 + 1; - p0 = &_agiScreen[x1 + y1 * GFX_WIDTH]; - for (y = 0; y < h; y++) { - memset(p0, c, w); - p0 += GFX_WIDTH; +// coordinates are for visual screen, but are supposed to point somewhere inside the playscreen +// x, y is the upper left. Sierra passed them as lower left. We change that to make upscaling easier. +// attention: Clipping is done here against 160x200 instead of 160x168 +// Original interpreter didn't do any clipping, we do it for security. +// Clipping against the regular script width/height must not be done, +// because at least during the intro one message box goes beyond playscreen +// Going beyond 160x168 will result in messageboxes not getting fully removed +// In KQ4's case, the scripts clear the screen that's why it works. +void GfxMgr::drawBox(int16 x, int16 y, int16 width, int16 height, byte backgroundColor, byte lineColor) { + if (!render_Clip(x, y, width, height, VISUAL_WIDTH, VISUAL_HEIGHT - _renderStartVisualOffsetY)) + return; + + // coordinate translation: visual-screen -> display-screen + translateVisualRectToDisplayScreen(x, y, width, height); + + y = y + _renderStartDisplayOffsetY; // drawDisplayRect paints anywhere on the whole screen, our coordinate is within playscreen + + // draw box background + drawDisplayRect(x, y, width, height, backgroundColor); + + // draw lines + switch (_vm->_renderMode) { + case Common::kRenderApple2GS: + case Common::kRenderAmiga: + // Slightly different window frame, and actually using 1-pixel width, which is "hi-res" + drawDisplayRect(x, +2, y, +2, width, -4, 0, 1, lineColor); + drawDisplayRect(x + width, -3, y, +2, 0, 1, height, -4, lineColor); + drawDisplayRect(x, +2, y + height, -3, width, -4, 0, 1, lineColor); + drawDisplayRect(x, +2, y, +2, 0, 1, height, -4, lineColor); + break; + case Common::kRenderMacintosh: + // 1 pixel between box and frame lines. Frame lines were black + drawDisplayRect(x, +1, y, +1, width, -2, 0, 1, 0); + drawDisplayRect(x + width, -2, y, +1, 0, 1, height, -2, 0); + drawDisplayRect(x, +1, y + height, -2, width, -2, 0, 1, 0); + drawDisplayRect(x, +1, y, +1, 0, 1, height, -2, 0); + break; + case Common::kRenderHercA: + case Common::kRenderHercG: + lineColor = 0; // change linecolor to black + // supposed to fall through + case Common::kRenderCGA: + case Common::kRenderEGA: + case Common::kRenderVGA: + case Common::kRenderAtariST: + default: + drawDisplayRect(x, +2, y, +1, width, -4, 0, 1, lineColor); + drawDisplayRect(x + width, -4, y, +2, 0, 2, height, -4, lineColor); + drawDisplayRect(x, +2, y + height, -2, width, -4, 0, 1, lineColor); + drawDisplayRect(x, +2, y, +2, 0, 2, height, -4, lineColor); + break; } } -void GfxMgr::drawFrame(int x1, int y1, int x2, int y2, int c1, int c2) { - int y, w; - uint8 *p0; +// coordinates are directly for display screen +void GfxMgr::drawDisplayRect(int16 x, int16 y, int16 width, int16 height, byte color, bool copyToScreen) { + switch (_vm->_renderMode) { + case Common::kRenderCGA: + drawDisplayRectCGA(x, y, width, height, color); + break; + case Common::kRenderHercG: + case Common::kRenderHercA: + if (color) + color = 1; // change any color except black to green/amber + // supposed to fall through + case Common::kRenderEGA: + default: + drawDisplayRectEGA(x, y, width, height, color); + break; + } + if (copyToScreen) { + copyDisplayRectToScreen(x, y, width, height); + } +} + +void GfxMgr::drawDisplayRect(int16 x, int16 adjX, int16 y, int16 adjY, int16 width, int16 adjWidth, int16 height, int16 adjHeight, byte color, bool copyToScreen) { + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + x += adjX; y += adjY; + width += adjWidth; height += adjHeight; + break; + case DISPLAY_UPSCALED_640x400: + x += adjX * 2; y += adjY * 2; + width += adjWidth * 2; height += adjHeight * 2; + break; + default: + assert(0); + break; + } + drawDisplayRect(x, y, width, height, color, copyToScreen); +} - // top line - w = x2 - x1 + 1; - p0 = &_agiScreen[x1 + y1 * GFX_WIDTH]; - memset(p0, c1, w); +void GfxMgr::drawDisplayRectEGA(int16 x, int16 y, int16 width, int16 height, byte color) { + uint32 offsetDisplay = (y * _displayScreenWidth) + x; + int16 remainingHeight = height; - // bottom line - p0 = &_agiScreen[x1 + y2 * GFX_WIDTH]; - memset(p0, c2, w); + while (remainingHeight) { + memset(_displayScreen + offsetDisplay, color, width); - // side lines - for (y = y1; y <= y2; y++) { - _agiScreen[x1 + y * GFX_WIDTH] = c1; - _agiScreen[x2 + y * GFX_WIDTH] = c2; + offsetDisplay += _displayScreenWidth; + remainingHeight--; } } -void GfxMgr::drawBox(int x1, int y1, int x2, int y2, int color1, int color2, int m) { - x1 += m; - y1 += m; - x2 -= m; - y2 -= m; +void GfxMgr::drawDisplayRectCGA(int16 x, int16 y, int16 width, int16 height, byte color) { + uint32 offsetDisplay = (y * _displayScreenWidth) + x; + int16 remainingHeight = height; + int16 remainingWidth = width; + byte CGAMixtureColor = getCGAMixtureColor(color); + byte *displayScreen = nullptr; + + // we should never get an uneven width + assert((width & 1) == 0); - drawRectangle(x1, y1, x2, y2, color1); - drawFrame(x1 + 2, y1 + 2, x2 - 2, y2 - 2, color2, color2); - flushBlock(x1, y1, x2, y2); + while (remainingHeight) { + remainingWidth = width; + + // set up pointer + displayScreen = _displayScreen + offsetDisplay; + + while (remainingWidth) { + *displayScreen++ = CGAMixtureColor & 0x03; + *displayScreen++ = CGAMixtureColor >> 2; + remainingWidth -= 2; + } + + offsetDisplay += _displayScreenWidth; + remainingHeight--; + } } -void GfxMgr::printCharacter(int x, int y, char c, int fg, int bg) { - x *= CHAR_COLS; - y *= CHAR_LINES; +// row + column are text-coordinates +void GfxMgr::drawCharacter(int16 row, int16 column, byte character, byte foreground, byte background, bool disabledLook) { + int16 x = column; + int16 y = row; + byte transformXOR = 0; + byte transformOR = 0; + + translateFontPosToDisplayScreen(x, y); + + // Now figure out, if special handling needs to be done + if (_vm->_game.gfxMode) { + if (background & 0x08) { + // invert enabled + background &= 0x07; // remove invert bit + transformXOR = 0xFF; + } + if (disabledLook) { + transformOR = 0x55; + } + } - putTextCharacter(0, x, y, c, fg, bg, false, _vm->getFontData()); - // redundant! already inside put_text_character! - // flush_block (x, y, x + CHAR_COLS - 1, y + CHAR_LINES - 1); + drawCharacterOnDisplay(x, y, character, foreground, background, transformXOR, transformOR); } -/** - * Draw a default style button. - * Swaps background and foreground color if button is in focus or being pressed. - * @param x x coordinate of the button - * @param y y coordinate of the button - * @param a set if the button has focus - * @param p set if the button is pressed - * @param fgcolor foreground color of the button when it is neither in focus nor being pressed - * @param bgcolor background color of the button when it is neither in focus nor being pressed - */ -void GfxMgr::drawDefaultStyleButton(int x, int y, const char *s, int a, int p, int fgcolor, int bgcolor) { - int textOffset = _vm->_defaultButtonStyle.getTextOffset(a > 0, p > 0); - AgiTextColor color = _vm->_defaultButtonStyle.getColor (a > 0, p > 0, fgcolor, bgcolor); - bool border = _vm->_defaultButtonStyle.getBorder (a > 0, p > 0); +// only meant for internal use (SystemUI) +void GfxMgr::drawStringOnDisplay(int16 x, int16 y, const char *text, byte foregroundColor, byte backgroundColor) { + while (*text) { + drawCharacterOnDisplay(x, y, *text, foregroundColor, backgroundColor); + text++; + x += _displayFontWidth; + } +} - rawDrawButton(x, y, s, color.fg, color.bg, border, textOffset); +void GfxMgr::drawStringOnDisplay(int16 x, int16 adjX, int16 y, int16 adjY, const char *text, byte foregroundColor, byte backgroundColor) { + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + x += adjX; + y += adjY; + break; + case DISPLAY_UPSCALED_640x400: + x += adjX * 2; + y += adjY * 2; + break; + default: + assert(0); + break; + } + drawStringOnDisplay(x, y, text, foregroundColor, backgroundColor); } -/** - * Draw a button using the currently chosen style. - * Amiga-style is used for the Amiga-rendering mode, PC-style is used otherwise. - * @param x x coordinate of the button - * @param y y coordinate of the button - * @param hasFocus set if the button has focus - * @param pressed set if the button is pressed - * @param positive set if button is positive, otherwise button is negative (Only matters with Amiga-style buttons) - * TODO: Make Amiga-style buttons a bit wider as they were in Amiga AGI games. - */ -void GfxMgr::drawCurrentStyleButton(int x, int y, const char *label, bool hasFocus, bool pressed, bool positive) { - int textOffset = _vm->_buttonStyle.getTextOffset(hasFocus, pressed); - AgiTextColor color = _vm->_buttonStyle.getColor(hasFocus, pressed, positive); - bool border = _vm->_buttonStyle.getBorder(hasFocus, pressed); +void GfxMgr::drawCharacterOnDisplay(int16 x, int16 y, const byte character, byte foreground, byte background, byte transformXOR, byte transformOR) { + int16 curX, curY; + const byte *fontData; + bool fontIsHires = _font->isFontHires(); + int16 fontHeight = fontIsHires ? 16 : FONT_DISPLAY_HEIGHT; + int16 fontWidth = fontIsHires ? 16 : FONT_DISPLAY_WIDTH; + int16 fontBytesPerCharacter = fontIsHires ? 32 : FONT_BYTES_PER_CHARACTER; + byte curByte = 0; + uint16 curBit; + + // get font data of specified character + fontData = _font->getFontData() + character * fontBytesPerCharacter; + + curBit = 0; + for (curY = 0; curY < fontHeight; curY++) { + for (curX = 0; curX < fontWidth; curX++) { + if (!curBit) { + curByte = *fontData; + // do transformations in case they are needed (invert/disabled look) + curByte ^= transformXOR; + curByte |= transformOR; + fontData++; + curBit = 0x80; + } + if (curByte & curBit) { + putFontPixelOnDisplay(x, y, curX, curY, foreground, fontIsHires); + } else { + putFontPixelOnDisplay(x, y, curX, curY, background, fontIsHires); + } + curBit = curBit >> 1; + } + if (transformOR) + transformOR ^= 0xFF; + } + + copyDisplayRectToScreen(x, y, _displayFontWidth, _displayFontHeight); +} + +#define SHAKE_VERTICAL_PIXELS 4 +#define SHAKE_HORIZONTAL_PIXELS 4 + +// Sierra used some EGA port trickery to do it, we have to do it by copying pixels around +// +// Shaking locations: +// - Fanmade "Enclosure" right during the intro +// - Space Quest 2 almost right at the start when getting captured (after walking into the space ship) +void GfxMgr::shakeScreen(int16 repeatCount) { + int shakeNr, shakeCount; + uint8 *blackSpace; + int16 shakeHorizontalPixels = SHAKE_HORIZONTAL_PIXELS * (2 + _displayWidthMulAdjust); + int16 shakeVerticalPixels = SHAKE_VERTICAL_PIXELS * (1 + _displayHeightMulAdjust); + + if ((blackSpace = (uint8 *)calloc(shakeHorizontalPixels * _displayScreenWidth, 1)) == NULL) + return; + + shakeCount = repeatCount * 8; // effectively 4 shakes per repeat + + // it's 4 pixels down and 8 pixels to the right + // and it's also filling the remaining space with black + for (shakeNr = 0; shakeNr < shakeCount; shakeNr++) { + if (shakeNr & 1) { + // move back + copyDisplayToScreen(); + } else { + g_system->copyRectToScreen(_displayScreen, _displayScreenWidth, shakeHorizontalPixels, shakeVerticalPixels, _displayScreenWidth - shakeHorizontalPixels, _displayScreenHeight - shakeVerticalPixels); + // additionally fill the remaining space with black + g_system->copyRectToScreen(blackSpace, _displayScreenWidth, 0, 0, _displayScreenWidth, shakeVerticalPixels); + g_system->copyRectToScreen(blackSpace, shakeHorizontalPixels, 0, 0, shakeHorizontalPixels, _displayScreenHeight); + } + g_system->updateScreen(); + g_system->delayMillis(66); // Sierra waited for 4 V'Syncs, which is around 66 milliseconds + } + + free(blackSpace); +} - rawDrawButton(x, y, label, color.fg, color.bg, border, textOffset); +void GfxMgr::updateScreen() { + g_system->updateScreen(); } -void GfxMgr::rawDrawButton(int x, int y, const char *s, int fgcolor, int bgcolor, bool border, int textOffset) { - int len = strlen(s); - int x1, y1, x2, y2; +void GfxMgr::initPriorityTable() { + _priorityTableSet = false; - x1 = x - 3; - y1 = y - 3; - x2 = x + CHAR_COLS * len + 2; - y2 = y + CHAR_LINES + 2; + createDefaultPriorityTable(_priorityTable); +} - // Draw a filled rectangle that's larger than the button. Used for drawing - // a border around the button as the button itself is drawn after this. - drawRectangle(x1, y1, x2, y2, border ? BUTTON_BORDER : MSG_BOX_COLOR); +void GfxMgr::createDefaultPriorityTable(uint8 *priorityTable) { + int16 priority, step; + int16 yPos = 0; - while (*s) { - putTextCharacter(0, x + textOffset, y + textOffset, *s++, fgcolor, bgcolor, false, _vm->getFontData()); - x += CHAR_COLS; + for (priority = 1; priority < 15; priority++) { + for (step = 0; step < 12; step++) { + priorityTable[yPos++] = priority < 4 ? 4 : priority; + } } +} + +void GfxMgr::setPriorityTable(int16 priorityBase) { + int16 x, priorityY, priority; - x1 -= 2; - y1 -= 2; - x2 += 2; - y2 += 2; + _priorityTableSet = true; + x = (SCRIPT_HEIGHT - priorityBase) * SCRIPT_HEIGHT / 10; - flushBlock(x1, y1, x2, y2); + for (priorityY = 0; priorityY < SCRIPT_HEIGHT; priorityY++) { + priority = (priorityY - priorityBase) < 0 ? 4 : (priorityY - priorityBase) * SCRIPT_HEIGHT / x + 5; + if (priority > 15) + priority = 15; + _priorityTable[priorityY] = priority; + } } -int GfxMgr::testButton(int x, int y, const char *s) { - int len = strlen(s); - Common::Rect rect(x - 3, y - 3, x + CHAR_COLS * len + 3, y + CHAR_LINES + 3); - return rect.contains(_vm->_mouse.x, _vm->_mouse.y); +// used for saving +int16 GfxMgr::saveLoadGetPriority(int16 yPos) { + assert(yPos < SCRIPT_HEIGHT); + return _priorityTable[yPos]; +} +bool GfxMgr::saveLoadWasPriorityTableModified() { + return _priorityTableSet; } -void GfxMgr::putBlock(int x1, int y1, int x2, int y2) { - gfxPutBlock(x1, y1, x2, y2); +// used for restoring +void GfxMgr::saveLoadSetPriority(int16 yPos, int16 priority) { + assert(yPos < SCRIPT_HEIGHT); + _priorityTable[yPos] = priority; +} +void GfxMgr::saveLoadSetPriorityTableModifiedBool(bool wasModified) { + _priorityTableSet = wasModified; } +void GfxMgr::saveLoadFigureOutPriorityTableModifiedBool() { + uint8 defaultPriorityTable[SCRIPT_HEIGHT]; /**< priority table */ -void GfxMgr::putScreen() { - putBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); + createDefaultPriorityTable(defaultPriorityTable); + + if (memcmp(defaultPriorityTable, _priorityTable, sizeof(_priorityTable)) == 0) { + // Match, it is the default table, so reset the flag + _priorityTableSet = false; + } else { + _priorityTableSet = true; + } } -/* - * Public functions +/** + * Convert sprite priority to y value. */ +int16 GfxMgr::priorityToY(int16 priority) { + int16 currentY; + + if (!_priorityTableSet) { + // priority table wasn't set by scripts? calculate directly + return (priority - 5) * 12 + 48; + } + + // Dynamic priority bands were introduced in 2.425, but removed again until 2.936 (effectively last version of AGI2) + // They are available from 2.936 onwards. + // It seems there was a glitch, that caused priority bands to not get calculated properly. + // It was caused by this function starting with Y = 168 instead of 167, which meant it always + // returned with 168 as result. + // This glitch is required in King's Quest 4 2.0, otherwise in room 54 ego will get drawn over + // the last dwarf, that enters the house. + // Dwarf is screen object 13 (view 152), gets fixed priority of 8, which would normally + // result in a Y of 101. Ego is priority (non-fixed) 8, which would mean that dwarf is + // drawn first, followed by ego, which would then draw ego over the dwarf. + // For more information see bug #1712585 (dwarf sprite priority) + // + // This glitch is definitely present in 2.425, 2.936 and 3.002.086. + // + // Priority bands were working properly in: 3.001.098 (Black Cauldron) + uint16 agiVersion = _vm->getVersion(); + + if (agiVersion <= 0x3086) { + return 168; // Buggy behavior, see above + } + + currentY = 167; + while (_priorityTable[currentY] >= priority) { + currentY--; + if (currentY < 0) // Original AGI didn't do this, we abort in that case and return -1 + break; + } + return currentY; +} + +int16 GfxMgr::priorityFromY(int16 yPos) { + assert(yPos < SCRIPT_HEIGHT); + return _priorityTable[yPos]; +} + /** * Initialize the color palette @@ -800,18 +1341,34 @@ void GfxMgr::putScreen() { * @param fromBits Bits per source color component. * @param toBits Bits per destination color component. */ -void GfxMgr::initPalette(const uint8 *p, uint colorCount, uint fromBits, uint toBits) { +void GfxMgr::initPalette(uint8 *destPalette, const uint8 *paletteData, uint colorCount, uint fromBits, uint toBits) { const uint srcMax = (1 << fromBits) - 1; const uint destMax = (1 << toBits) - 1; - for (uint col = 0; col < colorCount; col++) { - for (uint comp = 0; comp < 3; comp++) { // Convert RGB components - _palette[col * 3 + comp] = (p[col * 3 + comp] * destMax) / srcMax; + for (uint colorNr = 0; colorNr < colorCount; colorNr++) { + for (uint componentNr = 0; componentNr < 3; componentNr++) { // Convert RGB components + destPalette[colorNr * 3 + componentNr] = (paletteData[colorNr * 3 + componentNr] * destMax) / srcMax; + } + } +} + +// Converts CLUT data to a palette, that we can use +void GfxMgr::initPaletteCLUT(uint8 *destPalette, const uint16 *paletteCLUTData, uint colorCount) { + for (uint colorNr = 0; colorNr < colorCount; colorNr++) { + for (uint componentNr = 0; componentNr < 3; componentNr++) { // RGB component + byte component = (paletteCLUTData[colorNr * 3 + componentNr] >> 8); + // Adjust gamma (1.8 to 2.2) + component = (byte)(255 * pow(component / 255.0f, 0.8181f)); + destPalette[colorNr * 3 + componentNr] = component; } } } -void GfxMgr::gfxSetPalette() { - g_system->getPaletteManager()->setPalette(_palette, 0, 256); +void GfxMgr::setPalette(bool gfxModePalette) { + if (gfxModePalette) { + g_system->getPaletteManager()->setPalette(_paletteGfxMode, 0, 256); + } else { + g_system->getPaletteManager()->setPalette(_paletteTextMode, 0, 256); + } } //Gets AGIPAL Data @@ -863,8 +1420,8 @@ void GfxMgr::setAGIPal(int p0) { _agipalFileNum = p0; - initPalette(_agipalPalette); - gfxSetPalette(); + initPalette(_paletteGfxMode, _agipalPalette); + setPalette(true); // set gfx-mode palette debug(1, "Using AGIPAL palette from '%s'", filename); } @@ -873,137 +1430,64 @@ int GfxMgr::getAGIPalFileNum() { return _agipalFileNum; } -// put a block onto the screen -void GfxMgr::gfxPutBlock(int x1, int y1, int x2, int y2) { - if (x1 >= GFX_WIDTH) - x1 = GFX_WIDTH - 1; - if (y1 >= GFX_HEIGHT) - y1 = GFX_HEIGHT - 1; - if (x2 >= GFX_WIDTH) - x2 = GFX_WIDTH - 1; - if (y2 >= GFX_HEIGHT) - y2 = GFX_HEIGHT - 1; +void GfxMgr::initMouseCursor(MouseCursorData *mouseCursor, const byte *bitmapData, uint16 width, uint16 height, int hotspotX, int hotspotY) { + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + mouseCursor->bitmapData = bitmapData; + break; + case DISPLAY_UPSCALED_640x400: { + mouseCursor->bitmapDataAllocated = (byte *)malloc(width * height * 4); + mouseCursor->bitmapData = mouseCursor->bitmapDataAllocated; + + // Upscale mouse cursor + byte *upscaledData = mouseCursor->bitmapDataAllocated; + + for (uint16 y = 0; y < height; y++) { + for (uint16 x = 0; x < width; x++) { + byte curColor = *bitmapData++; + upscaledData[x * 2 + 0] = curColor; + upscaledData[x * 2 + 1] = curColor; + upscaledData[x * 2 + (width * 2) + 0] = curColor; + upscaledData[x * 2 + (width * 2) + 1] = curColor; + } + upscaledData += width * 2 * 2; + } - g_system->copyRectToScreen(_screen + y1 * 320 + x1, 320, x1, y1, x2 - x1 + 1, y2 - y1 + 1); + width *= 2; + height *= 2; + hotspotX *= 2; + hotspotY *= 2; + break; + } + default: + assert(0); + break; + } + mouseCursor->width = width; + mouseCursor->height = height; + mouseCursor->hotspotX = hotspotX; + mouseCursor->hotspotY = hotspotY; } -/** - * A black and white SCI-style arrow cursor (11x16). - * 0 = Transparent. - * 1 = Black (#000000 in 24-bit RGB). - * 2 = White (#FFFFFF in 24-bit RGB). - */ -static const byte sciMouseCursor[] = { - 1,1,0,0,0,0,0,0,0,0,0, - 1,2,1,0,0,0,0,0,0,0,0, - 1,2,2,1,0,0,0,0,0,0,0, - 1,2,2,2,1,0,0,0,0,0,0, - 1,2,2,2,2,1,0,0,0,0,0, - 1,2,2,2,2,2,1,0,0,0,0, - 1,2,2,2,2,2,2,1,0,0,0, - 1,2,2,2,2,2,2,2,1,0,0, - 1,2,2,2,2,2,2,2,2,1,0, - 1,2,2,2,2,2,2,2,2,2,1, - 1,2,2,2,2,2,1,0,0,0,0, - 1,2,1,0,1,2,2,1,0,0,0, - 1,1,0,0,1,2,2,1,0,0,0, - 0,0,0,0,0,1,2,2,1,0,0, - 0,0,0,0,0,1,2,2,1,0,0, - 0,0,0,0,0,0,1,2,2,1,0 -}; - -#if 0 -/** - * A black and white Apple IIGS style arrow cursor (9x11). - * 0 = Transparent. - * 1 = Black (#000000 in 24-bit RGB). - * 2 = White (#FFFFFF in 24-bit RGB). - */ -static const byte appleIIgsMouseCursor[] = { - 2,2,0,0,0,0,0,0,0, - 2,1,2,0,0,0,0,0,0, - 2,1,1,2,0,0,0,0,0, - 2,1,1,1,2,0,0,0,0, - 2,1,1,1,1,2,0,0,0, - 2,1,1,1,1,1,2,0,0, - 2,1,1,1,1,1,1,2,0, - 2,1,1,1,1,1,1,1,2, - 2,1,1,2,1,1,2,2,0, - 2,2,2,0,2,1,1,2,0, - 0,0,0,0,0,2,2,2,0 -}; -#endif - -/** - * RGB-palette for the black and white SCI and Apple IIGS arrow cursors. - */ -static const byte sciMouseCursorPalette[] = { - 0x00, 0x00, 0x00, // Black - 0xFF, 0xFF, 0xFF // White -}; - -/** - * An Amiga-style arrow cursor (8x11). - * 0 = Transparent. - * 1 = Black (#000000 in 24-bit RGB). - * 2 = Red (#DE2021 in 24-bit RGB). - * 3 = Light red (#FFCFAD in 24-bit RGB). - */ -static const byte amigaMouseCursor[] = { - 2,3,1,0,0,0,0,0, - 2,2,3,1,0,0,0,0, - 2,2,2,3,1,0,0,0, - 2,2,2,2,3,1,0,0, - 2,2,2,2,2,3,1,0, - 2,2,2,2,2,2,3,1, - 2,0,2,2,3,1,0,0, - 0,0,0,2,3,1,0,0, - 0,0,0,2,2,3,1,0, - 0,0,0,0,2,3,1,0, - 0,0,0,0,2,2,3,1 -}; +void GfxMgr::setMouseCursor(bool busy) { + MouseCursorData *mouseCursor = nullptr; -/** - * RGB-palette for the Amiga-style arrow cursor - * and the Amiga-style busy cursor. - */ -static const byte amigaMouseCursorPalette[] = { - 0x00, 0x00, 0x00, // Black - 0xDE, 0x20, 0x21, // Red - 0xFF, 0xCF, 0xAD // Light red -}; + if (!busy) { + mouseCursor = &_mouseCursor; + } else { + mouseCursor = &_mouseCursorBusy; + } -/** - * An Amiga-style busy cursor showing an hourglass (13x16). - * 0 = Transparent. - * 1 = Black (#000000 in 24-bit RGB). - * 2 = Red (#DE2021 in 24-bit RGB). - * 3 = Light red (#FFCFAD in 24-bit RGB). - */ -static const byte busyAmigaMouseCursor[] = { - 1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,2,2,2,2,2,2,2,2,2,2,2,1, - 1,2,2,2,2,2,2,2,2,2,2,2,1, - 0,1,3,3,3,3,3,3,3,3,3,1,0, - 0,0,1,3,3,3,3,3,3,3,1,0,0, - 0,0,0,1,3,3,3,3,3,1,0,0,0, - 0,0,0,0,1,3,3,3,1,0,0,0,0, - 0,0,0,0,0,1,3,1,0,0,0,0,0, - 0,0,0,0,0,1,3,1,0,0,0,0,0, - 0,0,0,0,1,2,3,2,1,0,0,0,0, - 0,0,0,1,2,2,3,2,2,1,0,0,0, - 0,0,1,2,2,2,3,2,2,2,1,0,0, - 0,1,2,2,2,3,3,3,2,2,2,1,0, - 1,3,3,3,3,3,3,3,3,3,3,3,1, - 1,3,3,3,3,3,3,3,3,3,3,3,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1 -}; + if (mouseCursor) { + CursorMan.replaceCursor(mouseCursor->bitmapData, mouseCursor->width, mouseCursor->height, mouseCursor->hotspotX, mouseCursor->hotspotY, 0); + } +} +#if 0 void GfxMgr::setCursor(bool amigaStyleCursor, bool busy) { if (busy) { - CursorMan.replaceCursorPalette(amigaMouseCursorPalette, 1, ARRAYSIZE(amigaMouseCursorPalette) / 3); - CursorMan.replaceCursor(busyAmigaMouseCursor, 13, 16, 7, 8, 0); - + CursorMan.replaceCursorPalette(MOUSECURSOR_AMIGA_PALETTE, 1, ARRAYSIZE(MOUSECURSOR_AMIGA_PALETTE) / 3); + CursorMan.replaceCursor(MOUSECURSOR_AMIGA_BUSY, 13, 16, 7, 8, 0); return; } @@ -1029,241 +1513,6 @@ void GfxMgr::setCursorPalette(bool amigaStyleCursor) { } } } - -/** - * Initialize graphics device. - * - * @see deinit_video() - */ -int GfxMgr::initVideo() { - if (_vm->getFeatures() & (GF_AGI256 | GF_AGI256_2)) - initPalette(vgaPalette, 256, 8); - else if (_vm->_renderMode == Common::kRenderEGA) - initPalette(egaPalette); - else if (_vm->_renderMode == Common::kRenderAmiga) { - if (!ConfMan.getBool("altamigapalette")) { - // Set the correct Amiga palette - if (_vm->getVersion() < 0x2936) - // TODO: This palette isn't used for Apple IIGS games yet, as - // we don't set a separate render mode for them yet - initPalette(amigaAgiPaletteV1, 16, 4); - else if (_vm->getVersion() == 0x2936) - initPalette(amigaAgiPaletteV2, 16, 4); - else if (_vm->getVersion() > 0x2936) - initPalette(amigaAgiPaletteV3, 16, 4); - } else - // Set the old common alternative Amiga palette - initPalette(altAmigaPalette); - } else - error("initVideo: Unhandled render mode"); - - if ((_agiScreen = (uint8 *)calloc(GFX_WIDTH, GFX_HEIGHT)) == NULL) - return errNotEnoughMemory; - - gfxSetPalette(); - - setCursor(_vm->_renderMode == Common::kRenderAmiga); - - return errOK; -} - -/** - * Deinitialize graphics device. - * - * @see init_video() - */ -int GfxMgr::deinitVideo() { - free(_agiScreen); - - return errOK; -} - -int GfxMgr::initMachine() { - _screen = (unsigned char *)malloc(320 * 200); - _vm->_clockCount = 0; - - return errOK; -} - -int GfxMgr::deinitMachine() { - free(_screen); - - return errOK; -} - -/** - * Write pixels on the output device. - * This function writes a row of pixels on the output device. Only the - * lower 4 bits of each pixel in the row will be used, making this - * function suitable for use with rows from the AGI screen. - * @param x x coordinate of the row start (AGI coord.) - * @param y y coordinate of the row start (AGI coord.) - * @param n number of pixels in the row - * @param p pointer to the row start in the AGI screen (Always use sbuf16c as base, not sbuf256c) - * FIXME: CGA rendering doesn't work correctly with AGI256 or AGI256-2. - */ -void GfxMgr::putPixelsA(int x, int y, int n, uint8 *p) { - const uint rShift = _vm->_debug.priority ? 4 : 0; // Priority information is in the top 4 bits of a byte taken from sbuf16c. - - // Choose the correct screen to read from. If AGI256 or AGI256-2 is used and we're not trying to show the priority information, - // then choose the 256 color screen, otherwise choose the 16 color screen (Which also has the priority information). - p += ((_vm->getFeatures() & (GF_AGI256 | GF_AGI256_2)) && !_vm->_debug.priority) ? FROM_SBUF16_TO_SBUF256_OFFSET : 0; - - if (_vm->_renderMode == Common::kRenderCGA) { - for (x *= 2; n--; p++, x += 2) { - register uint16 q = (cgaMap[(*p & 0xf0) >> 4] << 4) | cgaMap[*p & 0x0f]; - *(uint16 *)&_agiScreen[x + y * GFX_WIDTH] = (q >> rShift) & 0x0f0f; - } - } else { - const uint16 mask = ((_vm->getFeatures() & (GF_AGI256 | GF_AGI256_2)) && !_vm->_debug.priority) ? 0xffff : 0x0f0f; - for (x *= 2; n--; p++, x += 2) { - register uint16 q = ((uint16)*p << 8) | *p; - *(uint16 *)&_agiScreen[x + y * GFX_WIDTH] = (q >> rShift) & mask; - } - } -} - -/** - * Schedule blocks for blitting on the output device. - * This function gets the coordinates of a block in the AGI screen and - * schedule it to be updated in the output device. - * @param x1 x coordinate of the upper left corner of the block (AGI coord.) - * @param y1 y coordinate of the upper left corner of the block (AGI coord.) - * @param x2 x coordinate of the lower right corner of the block (AGI coord.) - * @param y2 y coordinate of the lower right corner of the block (AGI coord.) - * - * @see do_update() - */ -void GfxMgr::scheduleUpdate(int x1, int y1, int x2, int y2) { - if (x1 < update.x1) - update.x1 = x1; - if (y1 < update.y1) - update.y1 = y1; - if (x2 > update.x2) - update.x2 = x2; - if (y2 > update.y2) - update.y2 = y2; -} - -/** - * Update scheduled blocks on the output device. - * This function exposes the blocks scheduled for updating to the output - * device. Blocks can be scheduled at any point of the AGI cycle. - * - * @see schedule_update() - */ -void GfxMgr::doUpdate() { - if (update.x1 <= update.x2 && update.y1 <= update.y2) { - gfxPutBlock(update.x1, update.y1, update.x2, update.y2); - } - - // reset update block variables - update.x1 = MAX_INT; - update.y1 = MAX_INT; - update.x2 = 0; - update.y2 = 0; - - g_system->updateScreen(); -} - -/** - * Updates a block of the framebuffer with contents of the AGI engine screen. - * This function updates a block in the output device with the contents of - * the AGI engine screen, handling console transparency. - * @param x1 x coordinate of the upper left corner of the block - * @param y1 y coordinate of the upper left corner of the block - * @param x2 x coordinate of the lower right corner of the block - * @param y2 y coordinate of the lower right corner of the block - * - * @see flush_block_a() - */ -void GfxMgr::flushBlock(int x1, int y1, int x2, int y2) { - int y, w; - uint8 *p0; - - scheduleUpdate(x1, y1, x2, y2); - - p0 = &_agiScreen[x1 + y1 * GFX_WIDTH]; - w = x2 - x1 + 1; - - for (y = y1; y <= y2; y++) { - memcpy(_screen + 320 * y + x1, p0, w); - p0 += GFX_WIDTH; - } -} - -/** - * Updates a block of the framebuffer receiving AGI picture coordinates. - * @param x1 x AGI picture coordinate of the upper left corner of the block - * @param y1 y AGI picture coordinate of the upper left corner of the block - * @param x2 x AGI picture coordinate of the lower right corner of the block - * @param y2 y AGI picture coordinate of the lower right corner of the block - * - * @see flush_block() - */ -void GfxMgr::flushBlockA(int x1, int y1, int x2, int y2) { - //y1 += 8; - //y2 += 8; - flushBlock(DEV_X0(x1), DEV_Y(y1), DEV_X1(x2), DEV_Y(y2)); -} - -/** - * Updates the framebuffer with contents of the AGI engine screen (console-aware). - * This function updates the output device with the contents of the AGI - * screen, handling console transparency. - */ -void GfxMgr::flushScreen() { - flushBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); - - doUpdate(); -} - -/** - * Clear the output device screen (console-aware). - * This function clears the output device screen and updates the - * output device. Contents of the AGI screen are left untouched. This - * function can be used to simulate a switch to a text mode screen in - * a graphic-only device. - * @param c color to clear the screen - */ -void GfxMgr::clearScreen(int c) { - memset(_agiScreen, c, GFX_WIDTH * GFX_HEIGHT); - flushScreen(); -} - -/** - * Save a block of the AGI engine screen - */ -void GfxMgr::saveBlock(int x1, int y1, int x2, int y2, uint8 *b) { - uint8 *p0; - int w, h; - - p0 = &_agiScreen[x1 + GFX_WIDTH * y1]; - w = x2 - x1 + 1; - h = y2 - y1 + 1; - while (h--) { - memcpy(b, p0, w); - b += w; - p0 += GFX_WIDTH; - } -} - -/** - * Restore a block of the AGI engine screen - */ -void GfxMgr::restoreBlock(int x1, int y1, int x2, int y2, uint8 *b) { - uint8 *p0; - int w, h; - - p0 = &_agiScreen[x1 + GFX_WIDTH * y1]; - w = x2 - x1 + 1; - h = y2 - y1 + 1; - while (h--) { - memcpy(p0, b, w); - b += w; - p0 += GFX_WIDTH; - } - flushBlock(x1, y1, x2, y2); -} +#endif } // End of namespace Agi |