diff options
Diffstat (limited to 'engines/agi/graphics.cpp')
-rw-r--r-- | engines/agi/graphics.cpp | 1701 |
1 files changed, 666 insertions, 1035 deletions
diff --git a/engines/agi/graphics.cpp b/engines/agi/graphics.cpp index b46e6f726c..e9ae1295e9 100644 --- a/engines/agi/graphics.cpp +++ b/engines/agi/graphics.cpp @@ -23,773 +23,739 @@ #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) : _vm(vm) { + _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 + _renderStartOffsetY = 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) + * Initialize graphics device. * - * Also used by at least the following Apple IIGS AGI versions: - * 1.002 (Space Quest I, intro says v2.2 1987) + * @see deinit_video() */ -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 -}; +int GfxMgr::initVideo() { + // Set up palettes + initPalette(_paletteTextMode, PALETTE_EGA); + + switch (_vm->_renderMode) { + case RENDERMODE_EGA: + initPalette(_paletteGfxMode, PALETTE_EGA); + break; + case RENDERMODE_CGA: + initPalette(_paletteGfxMode, PALETTE_CGA, 4, 8); + break; + case RENDERMODE_VGA: + initPalette(_paletteGfxMode, PALETTE_VGA, 256, 8); + break; + case RENDERMODE_AMIGA: + 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 RENDERMODE_APPLE_II_GS: + initPalette(_paletteGfxMode, PALETTE_APPLE_II_GS, 16, 4); + break; + case RENDERMODE_ATARI_ST: + initPalette(_paletteGfxMode, PALETTE_ATARI_ST, 16, 3); + break; + default: + error("initVideo: unsupported render mode"); + break; + } -/** - * Second generation Amiga AGI palette. - * A 16-color, 12-bit RGB palette. - * - * 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) - */ -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 -}; + // set up mouse cursors + switch (_vm->_renderMode) { + case RENDERMODE_EGA: + case RENDERMODE_CGA: + case RENDERMODE_VGA: + initMouseCursor(&_mouseCursor, MOUSECURSOR_SCI, 11, 16, 1, 1); + initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8); + break; + case RENDERMODE_AMIGA: + initMouseCursor(&_mouseCursor, MOUSECURSOR_AMIGA, 8, 11, 1, 1); + initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_AMIGA_BUSY, 13, 16, 7, 8); + break; + case RENDERMODE_APPLE_II_GS: + // had no special busy mouse cursor + initMouseCursor(&_mouseCursor, MOUSECURSOR_APPLE_II_GS, 9, 11, 1, 1); + initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8); + break; + case RENDERMODE_ATARI_ST: + initMouseCursor(&_mouseCursor, MOUSECURSOR_ATARI_ST, 11, 16, 1, 1); + initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8); + break; + default: + error("initVideo: unsupported render mode"); + break; + } -/** - * Third generation Amiga AGI palette. - * A 16-color, 12-bit RGB palette. - * - * 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) - */ -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 -}; + _pixels = SCRIPT_WIDTH * SCRIPT_HEIGHT; + _visualScreen = (byte *)calloc(_pixels, 1); + _priorityScreen = (byte *)calloc(_pixels, 1); + _activeScreen = _visualScreen; + //_activeScreen = _priorityScreen; -/** - * 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 -}; + _displayPixels = DISPLAY_WIDTH * DISPLAY_HEIGHT; + _displayScreen = (byte *)calloc(_displayPixels, 1); + + initGraphics(DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_WIDTH > 320); + + setPalette(true); // set gfx-mode palette + + // set up mouse cursor palette + CursorMan.replaceCursorPalette(MOUSECURSOR_PALETTE, 1, ARRAYSIZE(MOUSECURSOR_PALETTE) / 3); + setMouseCursor(); + + return errOK; +} /** - * 256 color palette used with AGI256 and AGI256-2 games. - * Uses full 8 bits per color component. + * Deinitialize graphics device. + * + * @see init_video() */ -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 -}; +int GfxMgr::deinitVideo() { + free(_displayScreen); + free(_visualScreen); + free(_priorityScreen); -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 -}; + return errOK; +} -struct UpdateBlock { - int x1, y1; - int x2, y2; -}; +int GfxMgr::initMachine() { + _vm->_clockCount = 0; -static struct UpdateBlock update = { - MAX_INT, MAX_INT, 0, 0 -}; + return errOK; +} -GfxMgr::GfxMgr(AgiBase *vm) : _vm(vm) { - _shakeH = NULL; - _shakeV = NULL; - _agipalFileNum = 0; - _currentCursorPalette = 0; // cursor palette not set +int GfxMgr::deinitMachine() { + return errOK; } +void GfxMgr::setRenderStartOffset(uint16 offsetY) { + if (offsetY >= (DISPLAY_HEIGHT - SCRIPT_HEIGHT)) + error("invalid render start offset"); -// -// 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). -// - -#define SHAKE_MAG 3 - -void GfxMgr::shakeStart() { - int i; - - if ((_shakeH = (uint8 *)malloc(GFX_WIDTH * SHAKE_MAG)) == NULL) - return; + _renderStartOffsetY = offsetY; +} - if ((_shakeV = (uint8 *)malloc(SHAKE_MAG * (GFX_HEIGHT - SHAKE_MAG))) == NULL) { - free(_shakeH); - return; +void GfxMgr::debugShowMap(int mapNr) { + switch (mapNr) { + case 0: + _activeScreen = _visualScreen; + break; + case 1: + _activeScreen = _priorityScreen; + break; + default: + break; } - for (i = 0; i < GFX_HEIGHT - SHAKE_MAG; i++) { - memcpy(_shakeV + i * SHAKE_MAG, _agiScreen + i * GFX_WIDTH, SHAKE_MAG); + render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT); +} + +void GfxMgr::clear(byte color, byte priority) { + memset(_visualScreen, color, _pixels); + memset(_priorityScreen, priority, _pixels); +} + +void GfxMgr::clearDisplay(byte color, bool copyToScreen) { + memset(_displayScreen, color, _displayPixels); + + if (copyToScreen) { + g_system->copyRectToScreen(_displayScreen, DISPLAY_WIDTH, 0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); } +} + +void GfxMgr::putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority) { + int offset = y * SCRIPT_WIDTH + x; - for (i = 0; i < SHAKE_MAG; i++) { - memcpy(_shakeH + i * GFX_WIDTH, _agiScreen + i * GFX_WIDTH, GFX_WIDTH); + if (drawMask & GFX_SCREEN_MASK_VISUAL) { + _visualScreen[offset] = color; + } + if (drawMask & GFX_SCREEN_MASK_PRIORITY) { + _priorityScreen[offset] = priority; } } -void GfxMgr::shakeScreen(int n) { - int i; +void GfxMgr::putPixelOnDisplay(int16 x, int16 y, byte color) { + int offset = y * DISPLAY_WIDTH + x; - 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); - } - } 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); + _displayScreen[offset] = color; +} + +byte GfxMgr::getColor(int16 x, int16 y) { + int offset = y * SCRIPT_WIDTH + x; + + return _visualScreen[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: y-coordinate points to the LOWER left! +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 RENDERMODE_CGA: + render_BlockCGA(x, y, width, height, copyToScreen); + break; + case RENDERMODE_EGA: + default: + render_BlockEGA(x, y, width, height, copyToScreen); + break; + } + + if (copyToScreen) { + int16 upperY = y - height + 1 + _renderStartOffsetY; + g_system->copyRectToScreen(_displayScreen + upperY * DISPLAY_WIDTH + x * 2, DISPLAY_WIDTH, x * 2, upperY, width * 2, height); } } -void GfxMgr::shakeEnd() { - int i; +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 - height + 1) < 0) + height = y + 1; - for (i = 0; i < GFX_HEIGHT - SHAKE_MAG; i++) { - memcpy(_agiScreen + i * GFX_WIDTH, _shakeV + i * SHAKE_MAG, SHAKE_MAG); + if (y >= clipAgainstHeight) { + height -= y - (clipAgainstHeight - 1); + y = clipAgainstHeight - 1; } - for (i = 0; i < SHAKE_MAG; i++) { - memcpy(_agiScreen + i * GFX_WIDTH, _shakeH + i * GFX_WIDTH, GFX_WIDTH); + if (x < 0) { + width += x; + x = 0; } - flushBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); + if ((x + width - 1) >= clipAgainstWidth) { + width = clipAgainstWidth - x; + } + return true; +} + +void GfxMgr::render_BlockEGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) { + int offsetVisual = SCRIPT_WIDTH * y + x; + int offsetDisplay = (DISPLAY_WIDTH * (y + _renderStartOffsetY)) + x * 2; + int16 remainingWidth = width; + int16 remainingHeight = height; + byte curColor = 0; + + while (remainingHeight) { + remainingWidth = width; + while (remainingWidth) { + curColor = _activeScreen[offsetVisual++]; + _displayScreen[offsetDisplay++] = curColor; + _displayScreen[offsetDisplay++] = curColor; + remainingWidth--; + } + offsetVisual -= SCRIPT_WIDTH + width; + offsetDisplay -= DISPLAY_WIDTH + width * 2; - free(_shakeV); - free(_shakeH); + remainingHeight--; + } } -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; +void GfxMgr::render_BlockCGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) { + int offsetVisual = SCRIPT_WIDTH * y + x; + int offsetDisplay = (DISPLAY_WIDTH * (y + _renderStartOffsetY)) + x * 2; + int16 remainingWidth = width; + int16 remainingHeight = height; + byte curColor = 0; + + while (remainingHeight) { + remainingWidth = width; + while (remainingWidth) { + curColor = _activeScreen[offsetVisual++]; + _displayScreen[offsetDisplay++] = curColor & 0x03; // we process CGA mixture + _displayScreen[offsetDisplay++] = curColor >> 2; + remainingWidth--; + } + offsetVisual -= SCRIPT_WIDTH + width; + offsetDisplay -= DISPLAY_WIDTH + width * 2; + + remainingHeight--; + } +} - assert(font); +void GfxMgr::transition_Amiga() { + uint16 screenPos = 1; + uint16 screenStepPos = 1; + int16 posY = 0, posX = 0; + int16 stepCount = 0; + + // disable mouse while transition is taking place + if (_vm->_game.mouseEnabled) { + CursorMan.showMouse(false); + } + + 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 / SCRIPT_WIDTH; + posX = screenStepPos - (posY * SCRIPT_WIDTH); + + posY += _renderStartOffsetY; // adjust to only update the main area, not the status bar + posX *= 2; // adjust for display screen + + screenStepPos = (screenStepPos * 2) + (_renderStartOffsetY * DISPLAY_WIDTH); // adjust here too for display screen + for (int16 multiPixel = 0; multiPixel < 4; multiPixel++) { + g_system->copyRectToScreen(_displayScreen + screenStepPos, DISPLAY_WIDTH, posX, posY, 2, 1); + screenStepPos += (0x1A40 * 2); // 6720d + posY += 42; + } + 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); - p++; + // Enable mouse again + if (_vm->_game.mouseEnabled) { + CursorMan.showMouse(true); } - // 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; + g_system->updateScreen(); +} + +// 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; + uint16 screenStepPos = 1; + int16 posY = 0, posX = 0; + int16 stepCount = 0; + + // disable mouse while transition is taking place + if (_vm->_game.mouseEnabled) { + CursorMan.showMouse(false); + } + + 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 / DISPLAY_WIDTH; + posX = screenStepPos - (posY * DISPLAY_WIDTH); + + posY += _renderStartOffsetY; // adjust to only update the main area, not the status bar + + screenStepPos = screenStepPos + (_renderStartOffsetY * DISPLAY_WIDTH); // adjust here too for display screen + for (int16 multiPixel = 0; multiPixel < 8; multiPixel++) { + g_system->copyRectToScreen(_displayScreen + screenStepPos, DISPLAY_WIDTH, posX, posY, 1, 1); + screenStepPos += 0x1a40; // 6720d + posY += 21; + } + 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) { + CursorMan.showMouse(true); } - // FIXME: we don't want this when we're writing on the - // console! - flushBlock(x, y, x + CHAR_COLS - 1, y + CHAR_LINES - 1); + g_system->updateScreen(); } -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; +// 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, _visualScreen + 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--; } } -void GfxMgr::drawFrame(int x1, int y1, int x2, int y2, int c1, int c2) { - int y, w; - uint8 *p0; +// 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; - // top line - w = x2 - x1 + 1; - p0 = &_agiScreen[x1 + y1 * GFX_WIDTH]; - memset(p0, c1, w); + //warning("block_restore: %d, %d -> %d, %d", x, y, width, height); - // bottom line - p0 = &_agiScreen[x1 + y2 * GFX_WIDTH]; - memset(p0, c2, w); + while (remainingHeight) { + memcpy(_visualScreen + offset, curBufferPtr, width); + offset += SCRIPT_WIDTH; + curBufferPtr += width; + remainingHeight--; + } - // side lines - for (y = y1; y <= y2; y++) { - _agiScreen[x1 + y * GFX_WIDTH] = c1; - _agiScreen[x2 + y * GFX_WIDTH] = c2; + remainingHeight = height; + offset = startOffset; + while (remainingHeight) { + memcpy(_priorityScreen + offset, curBufferPtr, width); + offset += SCRIPT_WIDTH; + curBufferPtr += width; + 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; +// Attention: uses visual screen coordinates! +void GfxMgr::copyDisplayRectToScreen(int16 x, int16 y, int16 width, int16 height) { + g_system->copyRectToScreen(_displayScreen + y * DISPLAY_WIDTH + x, DISPLAY_WIDTH, x, y, width, height); +} + +// coordinates are for visual screen, but are supposed to point somewhere inside the playscreen +void GfxMgr::drawBox(int16 x, int16 y, int16 width, int16 height, byte backgroundColor, byte lineColor) { + drawRect(x, y, width, height, backgroundColor); + drawRect(x + 1, y - 1, width - 2, 1, lineColor); + drawRect(x + width - 2, y - 2, 1, height - 4, lineColor); + drawRect(x + 1, y - height + 2, width - 2, 1, lineColor); + drawRect(x + 1, y - 2, 1, height - 4, lineColor); +} + +// coordinates for visual screen +// 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::drawRect(int16 x, int16 y, int16 width, int16 height, byte color) { + if (!render_Clip(x, y, width, height, SCRIPT_WIDTH, DISPLAY_HEIGHT - _renderStartOffsetY)) + return; - drawRectangle(x1, y1, x2, y2, color1); - drawFrame(x1 + 2, y1 + 2, x2 - 2, y2 - 2, color2, color2); - flushBlock(x1, y1, x2, y2); + // coordinate translation: visual-screen -> display-screen + x = x * 2; + y = y + _renderStartOffsetY; // drawDisplayRect paints anywhere on the whole screen, our coordinate is within playscreen + width = width * 2; // width was given as visual width, we need display width + drawDisplayRect(x, y, width, height, color); +} + +// coordinates are directly for display screen +void GfxMgr::drawDisplayRect(int16 x, int16 y, int16 width, int16 height, byte color) { + switch (_vm->_renderMode) { + case RENDERMODE_CGA: + drawDisplayRectCGA(x, y, width, height, color); + break; + case RENDERMODE_EGA: + default: + drawDisplayRectEGA(x, y, width, height, color); + break; + } + int16 upperY = y - height + 1; + g_system->copyRectToScreen(_displayScreen + upperY * DISPLAY_WIDTH + x, DISPLAY_WIDTH, x, upperY, width, height); } -void GfxMgr::printCharacter(int x, int y, char c, int fg, int bg) { - x *= CHAR_COLS; - y *= CHAR_LINES; +void GfxMgr::drawDisplayRectEGA(int16 x, int16 y, int16 width, int16 height, byte color) { + int offsetDisplay = (DISPLAY_WIDTH * y) + x; + int16 remainingHeight = height; - 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); + while (remainingHeight) { + memset(_displayScreen + offsetDisplay, color, width); + + offsetDisplay -= DISPLAY_WIDTH; + remainingHeight--; + } } -/** - * 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); +void GfxMgr::drawDisplayRectCGA(int16 x, int16 y, int16 width, int16 height, byte color) { + int offsetDisplay = (DISPLAY_WIDTH * y) + 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); + + while (remainingHeight) { + remainingWidth = width; + + // set up pointer + displayScreen = _displayScreen + offsetDisplay; - rawDrawButton(x, y, s, color.fg, color.bg, border, textOffset); + while (remainingWidth) { + *displayScreen++ = CGAMixtureColor & 0x03; + *displayScreen++ = CGAMixtureColor >> 2; + remainingWidth -= 2; + } + + offsetDisplay -= DISPLAY_WIDTH; + remainingHeight--; + } } -/** - * 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); +// row + column are text-coordinates +void GfxMgr::drawCharacter(int16 row, int16 column, byte character, byte foreground, byte background, bool disabledLook) { + int16 startX, startY; + int16 curX, curY; + const byte *fontData; + byte curByte = 0; + uint16 curBit; + byte curTransformXOR = 0; + byte curTransformOR = 0; + + startX = column * FONT_DISPLAY_HEIGHT; + startY = row * FONT_DISPLAY_WIDTH; + + // get font data of specified character + fontData = _vm->getFontData() + character * FONT_BYTES_PER_CHARACTER; + + // Now figure out, if special handling needs to be done (for graphical mode only) + if (_vm->_game.gfxMode) { + if (background & 0x08) { + // invert enabled + background &= 0x07; // remove invert bit + curTransformXOR = 0xFF; // inverse all bits of the font + } + if (disabledLook) { + curTransformOR = 0xAA; + } + } + + curBit = 0; + for (curY = 0; curY < FONT_DISPLAY_HEIGHT; curY++) { + for (curX = 0; curX < FONT_DISPLAY_WIDTH; curX++) { + if (!curBit) { + curByte = *fontData; + // do transformations in case they are needed (invert/disabled look) + curByte ^= curTransformXOR; + curByte |= curTransformOR; + fontData++; + curBit = 0x80; + } + if (curByte & curBit) { + putPixelOnDisplay(startX + curX, startY + curY, foreground); + } else { + putPixelOnDisplay(startX + curX, startY + curY, background); + } + curBit = curBit >> 1; + } + } - rawDrawButton(x, y, label, color.fg, color.bg, border, textOffset); + copyDisplayRectToScreen(startX, startY, FONT_DISPLAY_WIDTH, FONT_DISPLAY_HEIGHT); } -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; +#define SHAKE_VERTICAL_PIXELS 4 +#define SHAKE_HORIZONTAL_PIXELS 8 - x1 = x - 3; - y1 = y - 3; - x2 = x + CHAR_COLS * len + 2; - y2 = y + CHAR_LINES + 2; +// Sierra used some EGA port trickery to do it, we have to do it by copying pixels around +void GfxMgr::shakeScreen(int16 repeatCount) { + int shakeNr, shakeCount; + uint8 *blackSpace; - // 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); + if ((blackSpace = (uint8 *)calloc(SHAKE_HORIZONTAL_PIXELS * DISPLAY_WIDTH, 1)) == NULL) + return; - while (*s) { - putTextCharacter(0, x + textOffset, y + textOffset, *s++, fgcolor, bgcolor, false, _vm->getFontData()); - x += CHAR_COLS; + 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 + copyDisplayRectToScreen(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); + } else { + g_system->copyRectToScreen(_displayScreen, DISPLAY_WIDTH, SHAKE_HORIZONTAL_PIXELS, SHAKE_VERTICAL_PIXELS, DISPLAY_WIDTH - SHAKE_HORIZONTAL_PIXELS, DISPLAY_HEIGHT - SHAKE_VERTICAL_PIXELS); + // additionally fill the remaining space with black + g_system->copyRectToScreen(blackSpace, DISPLAY_WIDTH, 0, 0, DISPLAY_WIDTH, SHAKE_VERTICAL_PIXELS); + g_system->copyRectToScreen(blackSpace, SHAKE_HORIZONTAL_PIXELS, 0, 0, SHAKE_HORIZONTAL_PIXELS, DISPLAY_HEIGHT); + } + g_system->updateScreen(); + g_system->delayMillis(66); // Sierra waited for 4 V'Syncs, which is around 66 milliseconds } - x1 -= 2; - y1 -= 2; - x2 += 2; - y2 += 2; + free(blackSpace); +} - flushBlock(x1, y1, x2, y2); +void GfxMgr::updateScreen() { + g_system->updateScreen(); } -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); +void GfxMgr::initPriorityTable() { + int16 priority, step; + int16 yPos = 0; + + _priorityTableSet = false; + for (priority = 1; priority < 15; priority++) { + for (step = 0; step < 12; step++) { + _priorityTable[yPos++] = priority < 4 ? 4 : priority; + } + } } -void GfxMgr::putBlock(int x1, int y1, int x2, int y2) { - gfxPutBlock(x1, y1, x2, y2); +void GfxMgr::setPriorityTable(int16 priorityBase) { + int16 x, priorityY, priority; + + _priorityTableSet = true; + x = (SCRIPT_HEIGHT - priorityBase) * SCRIPT_HEIGHT / 10; + + 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; + } } -void GfxMgr::putScreen() { - putBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); +// used for restoring +void GfxMgr::setPriority(int16 yPos, int16 priority) { + assert(yPos < SCRIPT_HEIGHT); + _priorityTable[yPos] = priority; } -/* - * 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 3.002.086 (effectively AGI3) + // 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) + // + // 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 +766,22 @@ 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; + destPalette[col * 3 + comp] = (paletteData[col * 3 + comp] * destMax) / srcMax; } } } -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 +833,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 +843,33 @@ 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; - - g_system->copyRectToScreen(_screen + y1 * 320 + x1, 320, x1, y1, x2 - x1 + 1, y2 - y1 + 1); +void GfxMgr::initMouseCursor(MouseCursorData *mouseCursor, const byte *bitmapData, uint16 width, uint16 height, int hotspotX, int hotspotY) { + mouseCursor->bitmapData = bitmapData; + 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 +895,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 \"%s\"", Common::getRenderModeDescription(_vm->_renderMode)); - - 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 |